├── .gitignore ├── LICENSE ├── README.md ├── backend ├── .gitignore ├── package-lock.json ├── package.json ├── server.ts └── tsconfig.json └── frontend ├── .editorconfig ├── .gitignore ├── README.md ├── angular-cli.json ├── e2e ├── app.e2e-spec.ts ├── app.po.ts └── tsconfig.json ├── karma.conf.js ├── package-lock.json ├── package.json ├── protractor.conf.js ├── src ├── app │ ├── app.component.css │ ├── app.component.html │ ├── app.component.spec.ts │ ├── app.component.ts │ ├── app.module.ts │ ├── index.ts │ ├── shared │ │ ├── immutable-model.ts │ │ ├── index.ts │ │ ├── mediastream.service.ts │ │ └── shared.module.ts │ └── webrtc-chat │ │ ├── index.ts │ │ ├── shared │ │ ├── index.ts │ │ ├── webrtc-client-connection.service.ts │ │ ├── webrtc-client.model.ts │ │ ├── webrtc-client.store.service.ts │ │ ├── webrtc-custom.d.ts │ │ ├── webrtc-event-messages.ts │ │ └── webrtc-shared.module.ts │ │ ├── webrtc-chat.component.html │ │ ├── webrtc-chat.component.ts │ │ └── webrtc-chat.module.ts ├── assets │ └── .gitkeep ├── environments │ ├── environment.prod.ts │ └── environment.ts ├── favicon.ico ├── index.html ├── main.ts ├── polyfills.ts ├── styles.css ├── test.ts ├── tsconfig.json └── typings.d.ts ├── tsconfig.json ├── tslint.json └── typings.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WebRTC Video Chat Example with Angular 2 and TypeScript 2 | 3 | Small example on how to use WebRTC with TypeScript and Angular 2 to build a small video chat. 4 | Only runs in a local network due to missing configured ICE server. 5 | 6 | ## Start Server 7 | 1. Navigate to `./backend` 8 | 2. Install dependencies 9 | 10 | `$ npm install` 11 | 12 | 3. Build server 13 | 14 | `$ npm run build` 15 | 16 | 4. Start server on port 3000 17 | 18 | `$ npm start` 19 | 20 | ## Serve frontend 21 | 1. Install [Angular-CLI](https://github.com/angular/angular-cli) 22 | 23 | 2. Navigate to `./frontend` 24 | 25 | 3. Serve with Angular-CLI 26 | 27 | `$ ng serve` 28 | 29 | ## Notes 30 | - Currently no ICE servers are configured 31 | - Therefor the project only runs on a local network. 32 | - To use it from different devices inside your network, 33 | you have to change the address, that the socket is using to connect to the backend. 34 | The address can be found in the file `./frontend/src/app/webrtc-chat/shared/webrtc-client-connection.service.ts` 35 | - Angular-CLI can listen to all interfaces by using `ng serve --host 0.0.0.0` 36 | - Chrome uses deprecated API's of WebRTC due to the problem in [webrtc/adapter #361](https://github.com/webrtc/adapter/issues/361) 37 | 38 | ## ToDo's 39 | - Add ICE config 40 | - Pretty up the whole thing :-) -------------------------------------------------------------------------------- /backend/.gitignore: -------------------------------------------------------------------------------- 1 | ### Node ### 2 | # Logs 3 | logs 4 | *.log 5 | npm-debug.log* 6 | 7 | # Runtime data 8 | pids 9 | *.pid 10 | *.seed 11 | *.pid.lock 12 | 13 | # Directory for instrumented libs generated by jscoverage/JSCover 14 | lib-cov 15 | 16 | # Coverage directory used by tools like istanbul 17 | coverage 18 | 19 | # nyc test coverage 20 | .nyc_output 21 | 22 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 23 | .grunt 24 | 25 | # node-waf configuration 26 | .lock-wscript 27 | 28 | # Compiled binary addons (http://nodejs.org/api/addons.html) 29 | build/Release 30 | 31 | # Dependency directories 32 | node_modules 33 | jspm_packages 34 | 35 | # Optional npm cache directory 36 | .npm 37 | 38 | # Optional eslint cache 39 | .eslintcache 40 | 41 | # Optional REPL history 42 | .node_repl_history 43 | 44 | # Output of 'npm pack' 45 | *.tgz 46 | 47 | # Yarn Integrity file 48 | .yarn-integrity 49 | 50 | /dist -------------------------------------------------------------------------------- /backend/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "backend", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@types/body-parser": { 8 | "version": "0.0.33", 9 | "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-0.0.33.tgz", 10 | "integrity": "sha1-M8oUmPw35Rxd8MgcrjRWnnBB4CU=", 11 | "dev": true, 12 | "requires": { 13 | "@types/express": "*" 14 | } 15 | }, 16 | "@types/express": { 17 | "version": "4.11.1", 18 | "resolved": "https://registry.npmjs.org/@types/express/-/express-4.11.1.tgz", 19 | "integrity": "sha512-ttWle8cnPA5rAelauSWeWJimtY2RsUf2aspYZs7xPHiWgOlPn6nnUfBMtrkcnjFJuIHJF4gNOdVvpLK2Zmvh6g==", 20 | "dev": true, 21 | "requires": { 22 | "@types/body-parser": "*", 23 | "@types/express-serve-static-core": "*", 24 | "@types/serve-static": "*" 25 | } 26 | }, 27 | "@types/express-serve-static-core": { 28 | "version": "4.0.49", 29 | "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.0.49.tgz", 30 | "integrity": "sha512-b7mVHoURu1xaP/V6xw1sYwyv9V0EZ7euyi+sdnbnTZxEkAh4/hzPsI6Eflq+ZzHQ/Tgl7l16Jz+0oz8F46MLnA==", 31 | "dev": true, 32 | "requires": { 33 | "@types/node": "*" 34 | } 35 | }, 36 | "@types/mime": { 37 | "version": "2.0.1", 38 | "resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.1.tgz", 39 | "integrity": "sha512-FwI9gX75FgVBJ7ywgnq/P7tw+/o1GUbtP0KzbtusLigAOgIgNISRK0ZPl4qertvXSIE8YbsVJueQ90cDt9YYyw==", 40 | "dev": true 41 | }, 42 | "@types/morgan": { 43 | "version": "1.7.32", 44 | "resolved": "https://registry.npmjs.org/@types/morgan/-/morgan-1.7.32.tgz", 45 | "integrity": "sha1-+rHs5NrhcuGjd9Vj0z42NPoEkn0=", 46 | "dev": true, 47 | "requires": { 48 | "@types/express": "*" 49 | } 50 | }, 51 | "@types/node": { 52 | "version": "0.0.2", 53 | "resolved": "https://registry.npmjs.org/@types/node/-/node-0.0.2.tgz", 54 | "integrity": "sha1-DaSTSQL79oqXoPUyBKNa0iJJF7w=", 55 | "dev": true 56 | }, 57 | "@types/serve-static": { 58 | "version": "1.13.3", 59 | "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.3.tgz", 60 | "integrity": "sha512-oprSwp094zOglVrXdlo/4bAHtKTAxX6VT8FOZlBKrmyLbNvE1zxZyJ6yikMVtHIvwP45+ZQGJn+FdXGKTozq0g==", 61 | "dev": true, 62 | "requires": { 63 | "@types/express-serve-static-core": "*", 64 | "@types/mime": "*" 65 | } 66 | }, 67 | "@types/socket.io": { 68 | "version": "1.4.27", 69 | "resolved": "https://registry.npmjs.org/@types/socket.io/-/socket.io-1.4.27.tgz", 70 | "integrity": "sha1-xqnz/60h46CZkDiBOgatIKvcbpU=", 71 | "dev": true, 72 | "requires": { 73 | "@types/node": "*" 74 | } 75 | }, 76 | "accepts": { 77 | "version": "1.3.7", 78 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", 79 | "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", 80 | "requires": { 81 | "mime-types": "~2.1.24", 82 | "negotiator": "0.6.2" 83 | } 84 | }, 85 | "after": { 86 | "version": "0.8.1", 87 | "resolved": "https://registry.npmjs.org/after/-/after-0.8.1.tgz", 88 | "integrity": "sha1-q11PuIP1loFtNRX495HAr0ht1ic=" 89 | }, 90 | "array-flatten": { 91 | "version": "1.1.1", 92 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", 93 | "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" 94 | }, 95 | "arraybuffer.slice": { 96 | "version": "0.0.6", 97 | "resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.6.tgz", 98 | "integrity": "sha1-8zshWfBTKj8xB6JywMz70a0peco=" 99 | }, 100 | "backo2": { 101 | "version": "1.0.2", 102 | "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", 103 | "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc=" 104 | }, 105 | "base64-arraybuffer": { 106 | "version": "0.1.5", 107 | "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz", 108 | "integrity": "sha1-c5JncZI7Whl0etZmqlzUv5xunOg=" 109 | }, 110 | "base64id": { 111 | "version": "0.1.0", 112 | "resolved": "https://registry.npmjs.org/base64id/-/base64id-0.1.0.tgz", 113 | "integrity": "sha1-As4P3u4M709ACA4ec+g08LG/zj8=" 114 | }, 115 | "basic-auth": { 116 | "version": "1.0.4", 117 | "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-1.0.4.tgz", 118 | "integrity": "sha1-Awk1sB3nyblKgksp8/zLdQ06UpA=" 119 | }, 120 | "better-assert": { 121 | "version": "1.0.2", 122 | "resolved": "https://registry.npmjs.org/better-assert/-/better-assert-1.0.2.tgz", 123 | "integrity": "sha1-QIZrnhueC1W0gYlDEeaPr/rrxSI=", 124 | "requires": { 125 | "callsite": "1.0.0" 126 | } 127 | }, 128 | "blob": { 129 | "version": "0.0.4", 130 | "resolved": "https://registry.npmjs.org/blob/-/blob-0.0.4.tgz", 131 | "integrity": "sha1-vPEwUspURj8w+fx+lbmkdjCpSSE=" 132 | }, 133 | "body-parser": { 134 | "version": "1.15.2", 135 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.15.2.tgz", 136 | "integrity": "sha1-11eM9PHRHV9uqATO813Hp/9trmc=", 137 | "requires": { 138 | "bytes": "2.4.0", 139 | "content-type": "~1.0.2", 140 | "debug": "~2.2.0", 141 | "depd": "~1.1.0", 142 | "http-errors": "~1.5.0", 143 | "iconv-lite": "0.4.13", 144 | "on-finished": "~2.3.0", 145 | "qs": "6.2.0", 146 | "raw-body": "~2.1.7", 147 | "type-is": "~1.6.13" 148 | } 149 | }, 150 | "bytes": { 151 | "version": "2.4.0", 152 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-2.4.0.tgz", 153 | "integrity": "sha1-fZcZb51br39pNeJZhVSe3SpsIzk=" 154 | }, 155 | "callsite": { 156 | "version": "1.0.0", 157 | "resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz", 158 | "integrity": "sha1-KAOY5dZkvXQDi28JBRU+borxvCA=" 159 | }, 160 | "component-bind": { 161 | "version": "1.0.0", 162 | "resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz", 163 | "integrity": "sha1-AMYIq33Nk4l8AAllGx06jh5zu9E=" 164 | }, 165 | "component-emitter": { 166 | "version": "1.1.2", 167 | "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.1.2.tgz", 168 | "integrity": "sha1-KWWU8nU9qmOZbSrwjRWpURbJrsM=" 169 | }, 170 | "component-inherit": { 171 | "version": "0.0.3", 172 | "resolved": "https://registry.npmjs.org/component-inherit/-/component-inherit-0.0.3.tgz", 173 | "integrity": "sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM=" 174 | }, 175 | "content-disposition": { 176 | "version": "0.5.1", 177 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.1.tgz", 178 | "integrity": "sha1-h0dsamfI2qh+Muh2Ft+IO6f7Bxs=" 179 | }, 180 | "content-type": { 181 | "version": "1.0.4", 182 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", 183 | "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" 184 | }, 185 | "cookie": { 186 | "version": "0.3.1", 187 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", 188 | "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" 189 | }, 190 | "cookie-signature": { 191 | "version": "1.0.6", 192 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 193 | "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" 194 | }, 195 | "debug": { 196 | "version": "2.2.0", 197 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", 198 | "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", 199 | "requires": { 200 | "ms": "0.7.1" 201 | } 202 | }, 203 | "depd": { 204 | "version": "1.1.2", 205 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", 206 | "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" 207 | }, 208 | "destroy": { 209 | "version": "1.0.4", 210 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", 211 | "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" 212 | }, 213 | "ee-first": { 214 | "version": "1.1.1", 215 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 216 | "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" 217 | }, 218 | "encodeurl": { 219 | "version": "1.0.2", 220 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", 221 | "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" 222 | }, 223 | "engine.io": { 224 | "version": "1.8.1", 225 | "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-1.8.1.tgz", 226 | "integrity": "sha1-IAZuqQMwTxPuN/EPqv9rR4T2Q3M=", 227 | "requires": { 228 | "accepts": "1.3.3", 229 | "base64id": "0.1.0", 230 | "cookie": "0.3.1", 231 | "debug": "2.3.3", 232 | "engine.io-parser": "1.3.1", 233 | "ws": "1.1.1" 234 | }, 235 | "dependencies": { 236 | "accepts": { 237 | "version": "1.3.3", 238 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.3.tgz", 239 | "integrity": "sha1-w8p0NJOGSMPg2cHjKN1otiLChMo=", 240 | "requires": { 241 | "mime-types": "~2.1.11", 242 | "negotiator": "0.6.1" 243 | } 244 | }, 245 | "debug": { 246 | "version": "2.3.3", 247 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.3.3.tgz", 248 | "integrity": "sha1-QMRT5n5uE8kB3ewxeviYbNqe/4w=", 249 | "requires": { 250 | "ms": "0.7.2" 251 | } 252 | }, 253 | "ms": { 254 | "version": "0.7.2", 255 | "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz", 256 | "integrity": "sha1-riXPJRKziFodldfwN4aNhDESR2U=" 257 | }, 258 | "negotiator": { 259 | "version": "0.6.1", 260 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", 261 | "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=" 262 | } 263 | } 264 | }, 265 | "engine.io-client": { 266 | "version": "1.8.1", 267 | "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-1.8.1.tgz", 268 | "integrity": "sha1-cSN+m7zgSGJnXU1r++81HItqNaM=", 269 | "requires": { 270 | "component-emitter": "1.2.1", 271 | "component-inherit": "0.0.3", 272 | "debug": "2.3.3", 273 | "engine.io-parser": "1.3.1", 274 | "has-cors": "1.1.0", 275 | "indexof": "0.0.1", 276 | "parsejson": "0.0.3", 277 | "parseqs": "0.0.5", 278 | "parseuri": "0.0.5", 279 | "ws": "1.1.1", 280 | "xmlhttprequest-ssl": "1.5.3", 281 | "yeast": "0.1.2" 282 | }, 283 | "dependencies": { 284 | "component-emitter": { 285 | "version": "1.2.1", 286 | "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", 287 | "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=" 288 | }, 289 | "debug": { 290 | "version": "2.3.3", 291 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.3.3.tgz", 292 | "integrity": "sha1-QMRT5n5uE8kB3ewxeviYbNqe/4w=", 293 | "requires": { 294 | "ms": "0.7.2" 295 | } 296 | }, 297 | "ms": { 298 | "version": "0.7.2", 299 | "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz", 300 | "integrity": "sha1-riXPJRKziFodldfwN4aNhDESR2U=" 301 | } 302 | } 303 | }, 304 | "engine.io-parser": { 305 | "version": "1.3.1", 306 | "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-1.3.1.tgz", 307 | "integrity": "sha1-lVTxrjMQfW+9FwylRm0vgz9qB88=", 308 | "requires": { 309 | "after": "0.8.1", 310 | "arraybuffer.slice": "0.0.6", 311 | "base64-arraybuffer": "0.1.5", 312 | "blob": "0.0.4", 313 | "has-binary": "0.1.6", 314 | "wtf-8": "1.0.0" 315 | }, 316 | "dependencies": { 317 | "has-binary": { 318 | "version": "0.1.6", 319 | "resolved": "https://registry.npmjs.org/has-binary/-/has-binary-0.1.6.tgz", 320 | "integrity": "sha1-JTJvOc+k9hath4eJTjryz7x7bhA=", 321 | "requires": { 322 | "isarray": "0.0.1" 323 | } 324 | } 325 | } 326 | }, 327 | "escape-html": { 328 | "version": "1.0.3", 329 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 330 | "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" 331 | }, 332 | "etag": { 333 | "version": "1.7.0", 334 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.7.0.tgz", 335 | "integrity": "sha1-A9MLX2fdbmMtKUXTDWZScxo01dg=" 336 | }, 337 | "express": { 338 | "version": "4.14.0", 339 | "resolved": "https://registry.npmjs.org/express/-/express-4.14.0.tgz", 340 | "integrity": "sha1-we4/Qs3Ikfs9xlCoki1R7IR9DWY=", 341 | "requires": { 342 | "accepts": "~1.3.3", 343 | "array-flatten": "1.1.1", 344 | "content-disposition": "0.5.1", 345 | "content-type": "~1.0.2", 346 | "cookie": "0.3.1", 347 | "cookie-signature": "1.0.6", 348 | "debug": "~2.2.0", 349 | "depd": "~1.1.0", 350 | "encodeurl": "~1.0.1", 351 | "escape-html": "~1.0.3", 352 | "etag": "~1.7.0", 353 | "finalhandler": "0.5.0", 354 | "fresh": "0.3.0", 355 | "merge-descriptors": "1.0.1", 356 | "methods": "~1.1.2", 357 | "on-finished": "~2.3.0", 358 | "parseurl": "~1.3.1", 359 | "path-to-regexp": "0.1.7", 360 | "proxy-addr": "~1.1.2", 361 | "qs": "6.2.0", 362 | "range-parser": "~1.2.0", 363 | "send": "0.14.1", 364 | "serve-static": "~1.11.1", 365 | "type-is": "~1.6.13", 366 | "utils-merge": "1.0.0", 367 | "vary": "~1.1.0" 368 | } 369 | }, 370 | "finalhandler": { 371 | "version": "0.5.0", 372 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-0.5.0.tgz", 373 | "integrity": "sha1-6VCKvs6bbbqHGmlCodeRG5GRGsc=", 374 | "requires": { 375 | "debug": "~2.2.0", 376 | "escape-html": "~1.0.3", 377 | "on-finished": "~2.3.0", 378 | "statuses": "~1.3.0", 379 | "unpipe": "~1.0.0" 380 | }, 381 | "dependencies": { 382 | "statuses": { 383 | "version": "1.3.1", 384 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", 385 | "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=" 386 | } 387 | } 388 | }, 389 | "forwarded": { 390 | "version": "0.1.2", 391 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", 392 | "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" 393 | }, 394 | "fresh": { 395 | "version": "0.3.0", 396 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.3.0.tgz", 397 | "integrity": "sha1-ZR+DjiJCTnVm3hYdg1jKoZn4PU8=" 398 | }, 399 | "has-binary": { 400 | "version": "0.1.7", 401 | "resolved": "https://registry.npmjs.org/has-binary/-/has-binary-0.1.7.tgz", 402 | "integrity": "sha1-aOYesWIQyVRaClzOBqhzkS/h5ow=", 403 | "requires": { 404 | "isarray": "0.0.1" 405 | } 406 | }, 407 | "has-cors": { 408 | "version": "1.1.0", 409 | "resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz", 410 | "integrity": "sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk=" 411 | }, 412 | "http-errors": { 413 | "version": "1.5.1", 414 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.5.1.tgz", 415 | "integrity": "sha1-eIwNLB3iyBuebowBhDtrl+uSB1A=", 416 | "requires": { 417 | "inherits": "2.0.3", 418 | "setprototypeof": "1.0.2", 419 | "statuses": ">= 1.3.1 < 2" 420 | } 421 | }, 422 | "iconv-lite": { 423 | "version": "0.4.13", 424 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.13.tgz", 425 | "integrity": "sha1-H4irpKsLFQjoMSrMOTRfNumS4vI=" 426 | }, 427 | "indexof": { 428 | "version": "0.0.1", 429 | "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", 430 | "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=" 431 | }, 432 | "inherits": { 433 | "version": "2.0.3", 434 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 435 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 436 | }, 437 | "ipaddr.js": { 438 | "version": "1.4.0", 439 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.4.0.tgz", 440 | "integrity": "sha1-KWrKh4qCGBbluF0KKFqZvP9FgvA=" 441 | }, 442 | "isarray": { 443 | "version": "0.0.1", 444 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", 445 | "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" 446 | }, 447 | "json3": { 448 | "version": "3.3.2", 449 | "resolved": "https://registry.npmjs.org/json3/-/json3-3.3.2.tgz", 450 | "integrity": "sha1-PAQ0dD35Pi9cQq7nsZvLSDV19OE=" 451 | }, 452 | "media-typer": { 453 | "version": "0.3.0", 454 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 455 | "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" 456 | }, 457 | "merge-descriptors": { 458 | "version": "1.0.1", 459 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", 460 | "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" 461 | }, 462 | "methods": { 463 | "version": "1.1.2", 464 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 465 | "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" 466 | }, 467 | "mime": { 468 | "version": "1.3.4", 469 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.3.4.tgz", 470 | "integrity": "sha1-EV+eO2s9rylZmDyzjxSaLUDrXVM=" 471 | }, 472 | "mime-db": { 473 | "version": "1.44.0", 474 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", 475 | "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==" 476 | }, 477 | "mime-types": { 478 | "version": "2.1.27", 479 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", 480 | "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", 481 | "requires": { 482 | "mime-db": "1.44.0" 483 | } 484 | }, 485 | "morgan": { 486 | "version": "1.7.0", 487 | "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.7.0.tgz", 488 | "integrity": "sha1-6xDKjlDRq+D409rVwCAdBS2YHGI=", 489 | "requires": { 490 | "basic-auth": "~1.0.3", 491 | "debug": "~2.2.0", 492 | "depd": "~1.1.0", 493 | "on-finished": "~2.3.0", 494 | "on-headers": "~1.0.1" 495 | } 496 | }, 497 | "ms": { 498 | "version": "0.7.1", 499 | "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", 500 | "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=" 501 | }, 502 | "negotiator": { 503 | "version": "0.6.2", 504 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", 505 | "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" 506 | }, 507 | "object-assign": { 508 | "version": "4.1.0", 509 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.0.tgz", 510 | "integrity": "sha1-ejs9DpgGPUP0wD8uiubNUahog6A=" 511 | }, 512 | "object-component": { 513 | "version": "0.0.3", 514 | "resolved": "https://registry.npmjs.org/object-component/-/object-component-0.0.3.tgz", 515 | "integrity": "sha1-8MaapQ78lbhmwYb0AKM3acsvEpE=" 516 | }, 517 | "on-finished": { 518 | "version": "2.3.0", 519 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", 520 | "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", 521 | "requires": { 522 | "ee-first": "1.1.1" 523 | } 524 | }, 525 | "on-headers": { 526 | "version": "1.0.2", 527 | "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", 528 | "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==" 529 | }, 530 | "options": { 531 | "version": "0.0.6", 532 | "resolved": "https://registry.npmjs.org/options/-/options-0.0.6.tgz", 533 | "integrity": "sha1-7CLTEoBrtT5zF3Pnza788cZDEo8=" 534 | }, 535 | "parsejson": { 536 | "version": "0.0.3", 537 | "resolved": "https://registry.npmjs.org/parsejson/-/parsejson-0.0.3.tgz", 538 | "integrity": "sha1-q343WfIJ7OmUN5c/fQ8fZK4OZKs=", 539 | "requires": { 540 | "better-assert": "~1.0.0" 541 | } 542 | }, 543 | "parseqs": { 544 | "version": "0.0.5", 545 | "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.5.tgz", 546 | "integrity": "sha1-1SCKNzjkZ2bikbouoXNoSSGouJ0=", 547 | "requires": { 548 | "better-assert": "~1.0.0" 549 | } 550 | }, 551 | "parseuri": { 552 | "version": "0.0.5", 553 | "resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.5.tgz", 554 | "integrity": "sha1-gCBKUNTbt3m/3G6+J3jZDkvOMgo=", 555 | "requires": { 556 | "better-assert": "~1.0.0" 557 | } 558 | }, 559 | "parseurl": { 560 | "version": "1.3.3", 561 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", 562 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" 563 | }, 564 | "path-to-regexp": { 565 | "version": "0.1.7", 566 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", 567 | "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" 568 | }, 569 | "proxy-addr": { 570 | "version": "1.1.5", 571 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-1.1.5.tgz", 572 | "integrity": "sha1-ccDuOxAt4/IC87ZPYI0XP8uhqRg=", 573 | "requires": { 574 | "forwarded": "~0.1.0", 575 | "ipaddr.js": "1.4.0" 576 | } 577 | }, 578 | "qs": { 579 | "version": "6.2.0", 580 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.2.0.tgz", 581 | "integrity": "sha1-O3hIwDwt7OaalSKw+ujEEm10Xzs=" 582 | }, 583 | "range-parser": { 584 | "version": "1.2.1", 585 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", 586 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" 587 | }, 588 | "raw-body": { 589 | "version": "2.1.7", 590 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.1.7.tgz", 591 | "integrity": "sha1-rf6s4uT7MJgFgBTQjActzFl1h3Q=", 592 | "requires": { 593 | "bytes": "2.4.0", 594 | "iconv-lite": "0.4.13", 595 | "unpipe": "1.0.0" 596 | } 597 | }, 598 | "send": { 599 | "version": "0.14.1", 600 | "resolved": "https://registry.npmjs.org/send/-/send-0.14.1.tgz", 601 | "integrity": "sha1-qVSYQyU5L1FTKndgdg5FlZjIn3o=", 602 | "requires": { 603 | "debug": "~2.2.0", 604 | "depd": "~1.1.0", 605 | "destroy": "~1.0.4", 606 | "encodeurl": "~1.0.1", 607 | "escape-html": "~1.0.3", 608 | "etag": "~1.7.0", 609 | "fresh": "0.3.0", 610 | "http-errors": "~1.5.0", 611 | "mime": "1.3.4", 612 | "ms": "0.7.1", 613 | "on-finished": "~2.3.0", 614 | "range-parser": "~1.2.0", 615 | "statuses": "~1.3.0" 616 | }, 617 | "dependencies": { 618 | "statuses": { 619 | "version": "1.3.1", 620 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", 621 | "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=" 622 | } 623 | } 624 | }, 625 | "serve-static": { 626 | "version": "1.11.2", 627 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.11.2.tgz", 628 | "integrity": "sha1-LPmIm9RDWjIMw2iVyapXvWYuasc=", 629 | "requires": { 630 | "encodeurl": "~1.0.1", 631 | "escape-html": "~1.0.3", 632 | "parseurl": "~1.3.1", 633 | "send": "0.14.2" 634 | }, 635 | "dependencies": { 636 | "ms": { 637 | "version": "0.7.2", 638 | "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz", 639 | "integrity": "sha1-riXPJRKziFodldfwN4aNhDESR2U=" 640 | }, 641 | "send": { 642 | "version": "0.14.2", 643 | "resolved": "https://registry.npmjs.org/send/-/send-0.14.2.tgz", 644 | "integrity": "sha1-ObBDiz9RC+Xcb2Z6EfcWiTaM3u8=", 645 | "requires": { 646 | "debug": "~2.2.0", 647 | "depd": "~1.1.0", 648 | "destroy": "~1.0.4", 649 | "encodeurl": "~1.0.1", 650 | "escape-html": "~1.0.3", 651 | "etag": "~1.7.0", 652 | "fresh": "0.3.0", 653 | "http-errors": "~1.5.1", 654 | "mime": "1.3.4", 655 | "ms": "0.7.2", 656 | "on-finished": "~2.3.0", 657 | "range-parser": "~1.2.0", 658 | "statuses": "~1.3.1" 659 | } 660 | }, 661 | "statuses": { 662 | "version": "1.3.1", 663 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", 664 | "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=" 665 | } 666 | } 667 | }, 668 | "setprototypeof": { 669 | "version": "1.0.2", 670 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.2.tgz", 671 | "integrity": "sha1-gaVSFB7BBLiOic44MQOtXGZWTQg=" 672 | }, 673 | "socket.io": { 674 | "version": "1.7.1", 675 | "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-1.7.1.tgz", 676 | "integrity": "sha1-o012P9Is2XVkPC8MfF8Uum2oCq8=", 677 | "requires": { 678 | "debug": "2.3.3", 679 | "engine.io": "1.8.1", 680 | "has-binary": "0.1.7", 681 | "object-assign": "4.1.0", 682 | "socket.io-adapter": "0.5.0", 683 | "socket.io-client": "1.7.1", 684 | "socket.io-parser": "2.3.1" 685 | }, 686 | "dependencies": { 687 | "debug": { 688 | "version": "2.3.3", 689 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.3.3.tgz", 690 | "integrity": "sha1-QMRT5n5uE8kB3ewxeviYbNqe/4w=", 691 | "requires": { 692 | "ms": "0.7.2" 693 | } 694 | }, 695 | "ms": { 696 | "version": "0.7.2", 697 | "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz", 698 | "integrity": "sha1-riXPJRKziFodldfwN4aNhDESR2U=" 699 | } 700 | } 701 | }, 702 | "socket.io-adapter": { 703 | "version": "0.5.0", 704 | "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-0.5.0.tgz", 705 | "integrity": "sha1-y21LuL7IHhB4uZZ3+c7QBGBmu4s=", 706 | "requires": { 707 | "debug": "2.3.3", 708 | "socket.io-parser": "2.3.1" 709 | }, 710 | "dependencies": { 711 | "debug": { 712 | "version": "2.3.3", 713 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.3.3.tgz", 714 | "integrity": "sha1-QMRT5n5uE8kB3ewxeviYbNqe/4w=", 715 | "requires": { 716 | "ms": "0.7.2" 717 | } 718 | }, 719 | "ms": { 720 | "version": "0.7.2", 721 | "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz", 722 | "integrity": "sha1-riXPJRKziFodldfwN4aNhDESR2U=" 723 | } 724 | } 725 | }, 726 | "socket.io-client": { 727 | "version": "1.7.1", 728 | "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-1.7.1.tgz", 729 | "integrity": "sha1-bCNyXt/4BPORnBzk67JeWRxuYdc=", 730 | "requires": { 731 | "backo2": "1.0.2", 732 | "component-bind": "1.0.0", 733 | "component-emitter": "1.2.1", 734 | "debug": "2.3.3", 735 | "engine.io-client": "1.8.1", 736 | "has-binary": "0.1.7", 737 | "indexof": "0.0.1", 738 | "object-component": "0.0.3", 739 | "parseuri": "0.0.5", 740 | "socket.io-parser": "2.3.1", 741 | "to-array": "0.1.4" 742 | }, 743 | "dependencies": { 744 | "component-emitter": { 745 | "version": "1.2.1", 746 | "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", 747 | "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=" 748 | }, 749 | "debug": { 750 | "version": "2.3.3", 751 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.3.3.tgz", 752 | "integrity": "sha1-QMRT5n5uE8kB3ewxeviYbNqe/4w=", 753 | "requires": { 754 | "ms": "0.7.2" 755 | } 756 | }, 757 | "ms": { 758 | "version": "0.7.2", 759 | "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz", 760 | "integrity": "sha1-riXPJRKziFodldfwN4aNhDESR2U=" 761 | } 762 | } 763 | }, 764 | "socket.io-parser": { 765 | "version": "2.3.1", 766 | "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-2.3.1.tgz", 767 | "integrity": "sha1-3VMgJRA85Clpcya+/WQAX8/ltKA=", 768 | "requires": { 769 | "component-emitter": "1.1.2", 770 | "debug": "2.2.0", 771 | "isarray": "0.0.1", 772 | "json3": "3.3.2" 773 | } 774 | }, 775 | "statuses": { 776 | "version": "1.5.0", 777 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", 778 | "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" 779 | }, 780 | "to-array": { 781 | "version": "0.1.4", 782 | "resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz", 783 | "integrity": "sha1-F+bBH3PdTz10zaek/zI46a2b+JA=" 784 | }, 785 | "type-is": { 786 | "version": "1.6.18", 787 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", 788 | "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", 789 | "requires": { 790 | "media-typer": "0.3.0", 791 | "mime-types": "~2.1.24" 792 | } 793 | }, 794 | "typescript": { 795 | "version": "2.1.6", 796 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.1.6.tgz", 797 | "integrity": "sha1-QMfm6eXaeWG3cYtVUF+crJSHpgc=", 798 | "dev": true 799 | }, 800 | "ultron": { 801 | "version": "1.0.2", 802 | "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.0.2.tgz", 803 | "integrity": "sha1-rOEWq1V80Zc4ak6I9GhTeMiy5Po=" 804 | }, 805 | "unpipe": { 806 | "version": "1.0.0", 807 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 808 | "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" 809 | }, 810 | "utils-merge": { 811 | "version": "1.0.0", 812 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.0.tgz", 813 | "integrity": "sha1-ApT7kiu5N1FTVBxPcJYjHyh8ivg=" 814 | }, 815 | "vary": { 816 | "version": "1.1.2", 817 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 818 | "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" 819 | }, 820 | "ws": { 821 | "version": "1.1.1", 822 | "resolved": "https://registry.npmjs.org/ws/-/ws-1.1.1.tgz", 823 | "integrity": "sha1-CC3bbGQehdS7RR8D1S8G6r2x8Bg=", 824 | "requires": { 825 | "options": ">=0.0.5", 826 | "ultron": "1.0.x" 827 | } 828 | }, 829 | "wtf-8": { 830 | "version": "1.0.0", 831 | "resolved": "https://registry.npmjs.org/wtf-8/-/wtf-8-1.0.0.tgz", 832 | "integrity": "sha1-OS2LotDxw00e4tYw8V0O+2jhBIo=" 833 | }, 834 | "xmlhttprequest-ssl": { 835 | "version": "1.5.3", 836 | "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.3.tgz", 837 | "integrity": "sha1-GFqIjATspGw+QHDZn3tJ3jUomS0=" 838 | }, 839 | "yeast": { 840 | "version": "0.1.2", 841 | "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz", 842 | "integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk=" 843 | } 844 | } 845 | } 846 | -------------------------------------------------------------------------------- /backend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "backend", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "./dist/server.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "build": "tsc -p .", 9 | "start": "node ./dist/server.js" 10 | }, 11 | "keywords": [], 12 | "author": "Philipp Jäcks", 13 | "license": "ISC", 14 | "devDependencies": { 15 | "@types/body-parser": "0.0.33", 16 | "@types/express": "4.11.1", 17 | "@types/express-serve-static-core": "4.0.49", 18 | "@types/morgan": "1.7.32", 19 | "@types/node": "0.0.2", 20 | "@types/socket.io": "1.4.27", 21 | "typescript": "2.1.6" 22 | }, 23 | "dependencies": { 24 | "body-parser": "1.15.2", 25 | "express": "4.14.0", 26 | "morgan": "1.7.0", 27 | "socket.io": "1.7.1" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /backend/server.ts: -------------------------------------------------------------------------------- 1 | import * as express from 'express'; 2 | import * as morgan from 'morgan'; 3 | import * as bodyParser from 'body-parser'; 4 | import * as socketio from 'socket.io'; 5 | import * as http from 'http'; 6 | 7 | class Server { 8 | public app: express.Application; 9 | private server: http.Server; 10 | public io: SocketIO.Server; 11 | private allowedOrigins = [ 'http://localhost:4200' ]; 12 | 13 | 14 | public static bootstrap(): Server { 15 | return new Server(); 16 | } 17 | 18 | constructor() { 19 | this.app = express(); 20 | this.server = http.createServer(this.app); 21 | this.io = socketio().listen(this.server); 22 | this.config(); 23 | this.setupSocket(); 24 | } 25 | 26 | private config() { 27 | this.app.use((req: express.Request, res: express.Response, next: express.NextFunction) => { 28 | const origin = req.header('Origin'); 29 | 30 | for (let o of this.allowedOrigins) { 31 | if (o === origin) { 32 | res.header('Access-Control-Allow-Origin', origin); 33 | break; 34 | } 35 | } 36 | }); 37 | 38 | this.app.use(bodyParser.json()); 39 | this.app.use(bodyParser.urlencoded({ extended: true })); 40 | 41 | this.app.use(morgan('dev')); 42 | 43 | this.app.get('/', (req, res) => { 44 | res.send('Hello!'); 45 | }); 46 | 47 | this.server.listen(3000, () => { 48 | console.log('Server is listening on port 3000'); 49 | }); 50 | } 51 | 52 | private setupSocket() { 53 | this.io.on('connection', (socket: SocketIO.Socket) => { 54 | console.log(`Socket ${socket.id} connected`); 55 | // forward WebRTC messages 56 | socket.on('api/v1/webrtc/peermessage', (data: any) => { 57 | console.log('Forward WebRTC peer message:', JSON.stringify(data)); 58 | socket.broadcast.emit('api/v1/webrtc/peermessage', data); 59 | }); 60 | 61 | socket.on('api/v1/webrtc/connect-to-chat', (data: any) => { 62 | console.log(`Client ${socket.id} connected to room`); 63 | socket.broadcast.emit('api/v1/webrtc/peer-connected', { 64 | id: socket.id 65 | }); 66 | }); 67 | 68 | }); 69 | } 70 | } 71 | 72 | Server.bootstrap(); -------------------------------------------------------------------------------- /backend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "commonjs", 5 | "outDir": "dist", 6 | "noImplicitAny": true, 7 | "removeComments": true, 8 | "preserveConstEnums": true, 9 | "sourceMap": true, 10 | "typeRoots": [ 11 | "node_modules/@types" 12 | ] 13 | }, 14 | "exclude": [ 15 | "node_modules" 16 | ] 17 | } -------------------------------------------------------------------------------- /frontend/.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | 7 | # dependencies 8 | /node_modules 9 | /bower_components 10 | 11 | # IDEs and editors 12 | /.idea 13 | /.vscode 14 | .project 15 | .classpath 16 | .c9/ 17 | *.launch 18 | .settings/ 19 | 20 | # misc 21 | /.sass-cache 22 | /connect.lock 23 | /coverage/* 24 | /libpeerconnection.log 25 | npm-debug.log 26 | testem.log 27 | /typings 28 | 29 | # e2e 30 | /e2e/*.js 31 | /e2e/*.map 32 | 33 | #System Files 34 | .DS_Store 35 | Thumbs.db 36 | -------------------------------------------------------------------------------- /frontend/README.md: -------------------------------------------------------------------------------- 1 | # Frontend 2 | 3 | This project was generated with [angular-cli](https://github.com/angular/angular-cli) version 1.0.0-beta.22-1. 4 | 5 | ## Development server 6 | Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files. 7 | 8 | ## Code scaffolding 9 | 10 | Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive/pipe/service/class`. 11 | 12 | ## Build 13 | 14 | Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `-prod` flag for a production build. 15 | 16 | ## Running unit tests 17 | 18 | Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). 19 | 20 | ## Running end-to-end tests 21 | 22 | Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/). 23 | Before running the tests make sure you are serving the app via `ng serve`. 24 | 25 | ## Deploying to Github Pages 26 | 27 | Run `ng github-pages:deploy` to deploy to Github Pages. 28 | 29 | ## Further help 30 | 31 | To get more help on the `angular-cli` use `ng --help` or go check out the [Angular-CLI README](https://github.com/angular/angular-cli/blob/master/README.md). 32 | -------------------------------------------------------------------------------- /frontend/angular-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "project": { 3 | "version": "1.0.0-beta.22-1", 4 | "name": "frontend" 5 | }, 6 | "apps": [ 7 | { 8 | "root": "src", 9 | "outDir": "dist", 10 | "assets": [ 11 | "assets", 12 | "favicon.ico" 13 | ], 14 | "index": "index.html", 15 | "main": "main.ts", 16 | "test": "test.ts", 17 | "tsconfig": "tsconfig.json", 18 | "prefix": "app", 19 | "mobile": false, 20 | "styles": [ 21 | "styles.css" 22 | ], 23 | "scripts": [], 24 | "environments": { 25 | "source": "environments/environment.ts", 26 | "dev": "environments/environment.ts", 27 | "prod": "environments/environment.prod.ts" 28 | } 29 | } 30 | ], 31 | "addons": [], 32 | "packages": [], 33 | "e2e": { 34 | "protractor": { 35 | "config": "./protractor.conf.js" 36 | } 37 | }, 38 | "test": { 39 | "karma": { 40 | "config": "./karma.conf.js" 41 | } 42 | }, 43 | "defaults": { 44 | "styleExt": "css", 45 | "prefixInterfaces": false, 46 | "inline": { 47 | "style": false, 48 | "template": false 49 | }, 50 | "spec": { 51 | "class": false, 52 | "component": true, 53 | "directive": true, 54 | "module": false, 55 | "pipe": true, 56 | "service": true 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /frontend/e2e/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { FrontendPage } from './app.po'; 2 | 3 | describe('frontend App', function() { 4 | let page: FrontendPage; 5 | 6 | beforeEach(() => { 7 | page = new FrontendPage(); 8 | }); 9 | 10 | it('should display message saying app works', () => { 11 | page.navigateTo(); 12 | expect(page.getParagraphText()).toEqual('app works!'); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /frontend/e2e/app.po.ts: -------------------------------------------------------------------------------- 1 | import { browser, element, by } from 'protractor'; 2 | 3 | export class FrontendPage { 4 | navigateTo() { 5 | return browser.get('/'); 6 | } 7 | 8 | getParagraphText() { 9 | return element(by.css('app-root h1')).getText(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /frontend/e2e/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "declaration": false, 5 | "emitDecoratorMetadata": true, 6 | "experimentalDecorators": true, 7 | "module": "commonjs", 8 | "moduleResolution": "node", 9 | "outDir": "../dist/out-tsc-e2e", 10 | "sourceMap": true, 11 | "target": "es5", 12 | "typeRoots": [ 13 | "../node_modules/@types" 14 | ] 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /frontend/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/0.13/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', 'angular-cli'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-remap-istanbul'), 12 | require('angular-cli/plugins/karma') 13 | ], 14 | files: [ 15 | { pattern: './src/test.ts', watched: false } 16 | ], 17 | preprocessors: { 18 | './src/test.ts': ['angular-cli'] 19 | }, 20 | mime: { 21 | 'text/x-typescript': ['ts','tsx'] 22 | }, 23 | remapIstanbulReporter: { 24 | reports: { 25 | html: 'coverage', 26 | lcovonly: './coverage/coverage.lcov' 27 | } 28 | }, 29 | angularCli: { 30 | config: './angular-cli.json', 31 | environment: 'dev' 32 | }, 33 | reporters: config.angularCli && config.angularCli.codeCoverage 34 | ? ['progress', 'karma-remap-istanbul'] 35 | : ['progress'], 36 | port: 9876, 37 | colors: true, 38 | logLevel: config.LOG_INFO, 39 | autoWatch: true, 40 | browsers: ['Chrome'], 41 | singleRun: false 42 | }); 43 | }; 44 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend", 3 | "version": "0.0.0", 4 | "license": "MIT", 5 | "author": "Philipp Jäcks", 6 | "angular-cli": {}, 7 | "scripts": { 8 | "start": "ng serve", 9 | "lint": "tslint \"src/**/*.ts\"", 10 | "test": "ng test", 11 | "pree2e": "webdriver-manager update", 12 | "e2e": "protractor", 13 | "postinstall": "typings install" 14 | }, 15 | "private": true, 16 | "dependencies": { 17 | "@angular/common": "2.3.1", 18 | "@angular/compiler": "2.3.1", 19 | "@angular/core": "2.3.1", 20 | "@angular/forms": "2.3.1", 21 | "@angular/http": "2.3.1", 22 | "@angular/material": "2.0.0-alpha.10", 23 | "@angular/platform-browser": "2.3.1", 24 | "@angular/platform-browser-dynamic": "2.3.1", 25 | "@angular/router": "3.2.3", 26 | "core-js": "^2.4.1", 27 | "immutable": "3.8.1", 28 | "rxjs": "5.0.0-beta.12", 29 | "socket.io": "1.7.1", 30 | "socket.io-client": "1.7.1", 31 | "ts-helpers": "^1.1.1", 32 | "webrtc-adapter": "2.0.8", 33 | "zone.js": "^0.6.23" 34 | }, 35 | "devDependencies": { 36 | "@angular/compiler-cli": "2.3.1", 37 | "@types/jasmine": "2.5.38", 38 | "@types/node": "^6.0.42", 39 | "@types/socket.io": "1.4.27", 40 | "@types/socket.io-client": "1.4.29", 41 | "@types/webrtc": "0.0.21", 42 | "angular-cli": "1.0.0-beta.28.3", 43 | "codelyzer": "~2.0.0-beta.1", 44 | "jasmine-core": "2.5.2", 45 | "jasmine-spec-reporter": "2.5.0", 46 | "karma": "1.2.0", 47 | "karma-chrome-launcher": "^2.0.0", 48 | "karma-cli": "^1.0.1", 49 | "karma-jasmine": "^1.0.2", 50 | "karma-remap-istanbul": "^0.2.1", 51 | "protractor": "4.0.9", 52 | "ts-node": "1.2.1", 53 | "tslint": "^4.0.2", 54 | "typescript": "~2.0.3", 55 | "typings": "2.0.0", 56 | "webdriver-manager": "10.2.5" 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /frontend/protractor.conf.js: -------------------------------------------------------------------------------- 1 | // Protractor configuration file, see link for more information 2 | // https://github.com/angular/protractor/blob/master/docs/referenceConf.js 3 | 4 | /*global jasmine */ 5 | var SpecReporter = require('jasmine-spec-reporter'); 6 | 7 | exports.config = { 8 | allScriptsTimeout: 11000, 9 | specs: [ 10 | './e2e/**/*.e2e-spec.ts' 11 | ], 12 | capabilities: { 13 | 'browserName': 'chrome' 14 | }, 15 | directConnect: true, 16 | baseUrl: 'http://localhost:4200/', 17 | framework: 'jasmine', 18 | jasmineNodeOpts: { 19 | showColors: true, 20 | defaultTimeoutInterval: 30000, 21 | print: function() {} 22 | }, 23 | useAllAngular2AppRoots: true, 24 | beforeLaunch: function() { 25 | require('ts-node').register({ 26 | project: 'e2e' 27 | }); 28 | }, 29 | onPrepare: function() { 30 | jasmine.getEnv().addReporter(new SpecReporter()); 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /frontend/src/app/app.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pj035/angular2-webrtc-video-example/b7d76503de9cf20f844ec7143a76e300585a1ee3/frontend/src/app/app.component.css -------------------------------------------------------------------------------- /frontend/src/app/app.component.html: -------------------------------------------------------------------------------- 1 |

2 | {{title}} 3 |

4 |
5 | 6 |
7 | -------------------------------------------------------------------------------- /frontend/src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:no-unused-variable */ 2 | 3 | import { TestBed, async } from '@angular/core/testing'; 4 | import { AppComponent } from './app.component'; 5 | 6 | describe('AppComponent', () => { 7 | beforeEach(() => { 8 | TestBed.configureTestingModule({ 9 | declarations: [ 10 | AppComponent 11 | ], 12 | }); 13 | TestBed.compileComponents(); 14 | }); 15 | 16 | it('should create the app', async(() => { 17 | let fixture = TestBed.createComponent(AppComponent); 18 | let app = fixture.debugElement.componentInstance; 19 | expect(app).toBeTruthy(); 20 | })); 21 | 22 | it(`should have as title 'app works!'`, async(() => { 23 | let fixture = TestBed.createComponent(AppComponent); 24 | let app = fixture.debugElement.componentInstance; 25 | expect(app.title).toEqual('app works!'); 26 | })); 27 | 28 | it('should render title in a h1 tag', async(() => { 29 | let fixture = TestBed.createComponent(AppComponent); 30 | fixture.detectChanges(); 31 | let compiled = fixture.debugElement.nativeElement; 32 | expect(compiled.querySelector('h1').textContent).toContain('app works!'); 33 | })); 34 | }); 35 | -------------------------------------------------------------------------------- /frontend/src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | 4 | @Component({ 5 | selector: 'app-root', 6 | templateUrl: './app.component.html', 7 | styleUrls: ['./app.component.css'] 8 | }) 9 | export class AppComponent { 10 | title = 'app works!'; 11 | } 12 | -------------------------------------------------------------------------------- /frontend/src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { BrowserModule } from '@angular/platform-browser'; 2 | import { NgModule } from '@angular/core'; 3 | import { FormsModule } from '@angular/forms'; 4 | import { HttpModule } from '@angular/http'; 5 | require('webrtc-adapter'); 6 | 7 | import { AppComponent } from './app.component'; 8 | import { SharedModule } from './shared'; 9 | import { WebRTCChatModule, WebRTCChatComponent } from './webrtc-chat'; 10 | 11 | @NgModule({ 12 | declarations: [ 13 | AppComponent 14 | ], 15 | imports: [ 16 | BrowserModule, 17 | FormsModule, 18 | HttpModule, 19 | SharedModule, 20 | WebRTCChatModule 21 | ], 22 | entryComponents: [ 23 | AppComponent, 24 | WebRTCChatComponent 25 | ], 26 | bootstrap: [AppComponent] 27 | }) 28 | export class AppModule { } 29 | -------------------------------------------------------------------------------- /frontend/src/app/index.ts: -------------------------------------------------------------------------------- 1 | export * from './app.component'; 2 | export * from './app.module'; 3 | -------------------------------------------------------------------------------- /frontend/src/app/shared/immutable-model.ts: -------------------------------------------------------------------------------- 1 | import { Map } from "immutable"; 2 | 3 | /** 4 | * Base class for immutable model classes. Instances of immutable models can not be changed. 5 | * Changing the value of a property results in the creation of a new model instance based on the 6 | * previous instance with the single value change applied. The initial model instance remains 7 | * unchanged. 8 | * 9 | * @export 10 | * @abstract 11 | * @class ImmutableModel 12 | * @template TDocument The interface describing the data structure of the model. 13 | * @template TModel The class that inherits from ImmutableModel 14 | * @example 15 | * interface IPerson { 16 | * name: string; 17 | * age: number; 18 | * } 19 | * 20 | * class Person extends ImmutableModel { 21 | * constructor(data: IPerson|Map) { 22 | * super(Person, data); 23 | * } 24 | * 25 | * get name(): string { 26 | * return this.data.get("name"); 27 | * } 28 | * 29 | * setName(name: string): Person { 30 | * return this.setValue("name", name); 31 | * } 32 | * 33 | * get age(): number { 34 | * return this.data.get("age"); 35 | * } 36 | * 37 | * setAge(age: number): Person { 38 | * return this.setValue("age", age); 39 | * } 40 | * } 41 | */ 42 | export abstract class ImmutableModel> { 43 | protected data: Map; 44 | 45 | constructor( 46 | private ctor: new(data: TDocument|Map) => TModel, 47 | data_: TDocument|Map 48 | ) { 49 | this.data = Map(data_); 50 | } 51 | 52 | toJS(): any { 53 | return this.data.toJS(); 54 | } 55 | 56 | protected setValue(key: string, value: any): TModel { 57 | return new this.ctor(this.data.set(key, value)); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /frontend/src/app/shared/index.ts: -------------------------------------------------------------------------------- 1 | export * from './mediastream.service'; 2 | export * from './immutable-model'; 3 | export * from './shared.module'; -------------------------------------------------------------------------------- /frontend/src/app/shared/mediastream.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | 3 | @Injectable() 4 | export class MediaStreamService { 5 | private mediaStream: MediaStream = undefined; 6 | 7 | public getMediaStream(): Promise { 8 | if (!this.mediaStream) { 9 | return navigator.mediaDevices 10 | .getUserMedia({ 11 | audio: true, 12 | video: true 13 | }) 14 | .then((stream: MediaStream) => { 15 | return Promise.resolve(stream); 16 | }) 17 | .catch((err: MediaStreamError) => { 18 | console.error('Error accessing the hardware:', err); 19 | return Promise.reject(err); 20 | }); 21 | } else { 22 | return Promise.resolve(this.mediaStream); 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /frontend/src/app/shared/shared.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | 3 | import { MediaStreamService } from './mediastream.service'; 4 | import { WebRTCSharedModule } from '../webrtc-chat/shared'; 5 | 6 | @NgModule({ 7 | providers: [ 8 | MediaStreamService 9 | ], 10 | imports: [ 11 | WebRTCSharedModule 12 | ] 13 | }) 14 | export class SharedModule { } -------------------------------------------------------------------------------- /frontend/src/app/webrtc-chat/index.ts: -------------------------------------------------------------------------------- 1 | export * from './webrtc-chat.component'; 2 | export * from './webrtc-chat.module'; -------------------------------------------------------------------------------- /frontend/src/app/webrtc-chat/shared/index.ts: -------------------------------------------------------------------------------- 1 | export * from './webrtc-client.store.service'; 2 | export * from './webrtc-client-connection.service'; 3 | export * from './webrtc-shared.module'; -------------------------------------------------------------------------------- /frontend/src/app/webrtc-chat/shared/webrtc-client-connection.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Subject, Observable, Observer } from 'rxjs'; 3 | import * as io from 'socket.io-client'; 4 | 5 | import { WebRTCClientStore } from './webrtc-client.store.service'; 6 | import { WebRTCClient } from './webrtc-client.model'; 7 | import { 8 | SOCKET_EVENT_PEER_CONNECTED, 9 | SOCKET_EVENT_PEER_DISCONNECTED, 10 | SOCKET_EVENT_PEER_MESSAGE, 11 | SOCKET_EVENT_CONNECT_TO_ROOM, 12 | RTC_PEER_MESSAGE_ICE, 13 | RTC_PEER_MESSAGE_SDP_ANSWER, 14 | RTC_PEER_MESSAGE_SDP_OFFER 15 | } from './webrtc-event-messages'; 16 | import { MediaStreamService } from '../../shared/mediastream.service'; 17 | 18 | // TODO 19 | // Configure ICE Server 20 | 21 | @Injectable() 22 | export class WebRTCConnectionService { 23 | private socket: SocketIOClient.Socket; 24 | private peerConnections: RTCPeerConnection[] = []; 25 | private myMediaStream: MediaStream = undefined; 26 | private peerId: string; 27 | 28 | constructor( 29 | private webrtcClientStore: WebRTCClientStore, 30 | private mediaStream: MediaStreamService 31 | ) { 32 | this.socket = io.connect('http://localhost:3000'); 33 | 34 | this.socket.on('connect', () => { 35 | console.log('Socket connected. I am', this.socket.id); 36 | this.peerId = this.socket.id; 37 | }); 38 | 39 | this.socket.on('disconnect', () => { 40 | console.log('Socket disconnected. I was', this.socket.id); 41 | }); 42 | 43 | this.socket.on(SOCKET_EVENT_PEER_CONNECTED, (data) => { 44 | this.makeOffer(data.id); 45 | }); 46 | 47 | this.socket.on(SOCKET_EVENT_PEER_DISCONNECTED, (data) => { 48 | // todo 49 | }); 50 | 51 | this.socket.on(SOCKET_EVENT_PEER_MESSAGE, (data) => { 52 | this.handleRTCPeerMessage(data); 53 | }); 54 | } 55 | 56 | public connectToRoom() { 57 | this.mediaStream 58 | .getMediaStream() 59 | .then((stream: MediaStream) => { 60 | this.myMediaStream = stream; 61 | this.socket.emit(SOCKET_EVENT_CONNECT_TO_ROOM); 62 | 63 | // add myself to the list 64 | const me = new WebRTCClient({ id : this.socket.id, stream: this.myMediaStream}); 65 | this.webrtcClientStore.addClient(me); 66 | }) 67 | .catch(err => console.error('Can\'t get media stream', err)); 68 | } 69 | 70 | private makeOffer(clientId: string) { 71 | const peerConnection = this.getPeerConnection(clientId); 72 | const options = { 73 | mandatory: { 74 | offerToReceiveVideo: true, 75 | offerToReceiveAudio: true 76 | } 77 | }; 78 | 79 | peerConnection 80 | .createOffer(options) 81 | .then((sdp: RTCSessionDescriptionInit) => { 82 | return peerConnection 83 | .setLocalDescription(sdp) 84 | .then(() => { 85 | this.socket.emit(SOCKET_EVENT_PEER_MESSAGE, { 86 | by: this.peerId, 87 | to: clientId, 88 | sdp: sdp, 89 | type: RTC_PEER_MESSAGE_SDP_OFFER 90 | }) 91 | }) 92 | }) 93 | } 94 | 95 | private handleRTCPeerMessage(message) { 96 | 97 | const peerConnection = this.getPeerConnection(message.by); 98 | 99 | switch( message.type ) { 100 | case RTC_PEER_MESSAGE_SDP_OFFER: 101 | peerConnection 102 | .setRemoteDescription(new RTCSessionDescription(message.sdp)) 103 | .then(() => { 104 | console.log('Setting remote description by offer'); 105 | return peerConnection 106 | .createAnswer() 107 | .then((sdp: RTCSessionDescriptionInit) => { 108 | return peerConnection.setLocalDescription(sdp) 109 | .then(() => { 110 | this.socket.emit(SOCKET_EVENT_PEER_MESSAGE, { 111 | by: this.peerId, 112 | to: message.by, 113 | sdp: sdp, 114 | type: RTC_PEER_MESSAGE_SDP_ANSWER 115 | }); 116 | }) 117 | }); 118 | }) 119 | .catch(err => { 120 | console.error('Error on SDP-Offer:', err); 121 | }); 122 | break; 123 | case RTC_PEER_MESSAGE_SDP_ANSWER: 124 | peerConnection 125 | .setRemoteDescription(new RTCSessionDescription(message.sdp)) 126 | .then(() => console.log('Setting remote description by answer')) 127 | .catch(err => console.error('Error on SDP-Answer:', err)); 128 | break; 129 | case RTC_PEER_MESSAGE_ICE: 130 | if (message.ice) { 131 | console.log('Adding ice candidate'); 132 | peerConnection.addIceCandidate(message.ice); 133 | } 134 | break; 135 | } 136 | } 137 | 138 | private getPeerConnection(id): RTCPeerConnection { 139 | if (this.peerConnections[id]) { 140 | return this.peerConnections[id]; 141 | } 142 | 143 | const peerConnection = new RTCPeerConnection(); 144 | this.peerConnections[id] = peerConnection; 145 | 146 | peerConnection.onicecandidate = (event: RTCPeerConnectionIceEvent) => { 147 | this.socket.emit(SOCKET_EVENT_PEER_MESSAGE, { 148 | by: this.peerId, 149 | to: id, 150 | ice: event.candidate, 151 | type: RTC_PEER_MESSAGE_ICE 152 | }) 153 | }; 154 | 155 | peerConnection.onnegotiationneeded = () => { 156 | console.log('Need negotiation:', id); 157 | } 158 | 159 | peerConnection.onsignalingstatechange = () => { 160 | console.log('ICE signaling state changed to:', peerConnection.signalingState, 'for client', id); 161 | } 162 | 163 | // Workaround for Chrome 164 | // see: https://github.com/webrtc/adapter/issues/361 165 | if (window.navigator.userAgent.toLowerCase().indexOf('chrome') > - 1) { // Chrome 166 | // DEPECRATED https://developer.mozilla.org/de/docs/Web/API/RTCPeerConnection/addStream 167 | (peerConnection as any).addStream(this.myMediaStream); 168 | (peerConnection as any).onaddstream = (event) => { 169 | console.log('Received new stream'); 170 | const client = new WebRTCClient({id: id, stream: event.stream}); 171 | this.webrtcClientStore.addClient(client); 172 | }; 173 | } else { // Firefox 174 | peerConnection.addTrack(this.myMediaStream.getVideoTracks()[0], this.myMediaStream); 175 | peerConnection.ontrack = (event: RTCTrackEvent) => { 176 | console.log('Received new stream'); 177 | const client = new WebRTCClient({id: id, stream: event.streams[0]}); 178 | this.webrtcClientStore.addClient(client); 179 | } 180 | } 181 | 182 | return peerConnection; 183 | } 184 | 185 | } -------------------------------------------------------------------------------- /frontend/src/app/webrtc-chat/shared/webrtc-client.model.ts: -------------------------------------------------------------------------------- 1 | import { ImmutableModel } from '../../shared/immutable-model'; 2 | 3 | export interface IWebRTCClient { 4 | id?: string; 5 | stream: MediaStream; 6 | } 7 | 8 | export class WebRTCClient extends ImmutableModel { 9 | constructor(data: IWebRTCClient) { 10 | super(WebRTCClient, data); 11 | } 12 | 13 | get id(): string { 14 | return this.data.get('id'); 15 | } 16 | 17 | get stream(): MediaStream { 18 | return this.data.get('stream'); 19 | } 20 | 21 | setId(val: string): WebRTCClient { 22 | return this.setValue('id', val); 23 | } 24 | 25 | setStream(val: MediaStream): WebRTCClient { 26 | return this.setValue('stream', val); 27 | } 28 | } -------------------------------------------------------------------------------- /frontend/src/app/webrtc-chat/shared/webrtc-client.store.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from "@angular/core"; 2 | import { Observable } from "rxjs/Observable"; 3 | import { Subject } from "rxjs/Subject"; 4 | import { List } from "immutable"; 5 | import { BehaviorSubject } from "rxjs/Rx"; 6 | 7 | import { WebRTCClient } from './webrtc-client.model'; 8 | 9 | @Injectable() 10 | export class WebRTCClientStore { 11 | private _clients: BehaviorSubject> = new BehaviorSubject(List([])); 12 | 13 | constructor(){} 14 | 15 | public get clients$(): Observable> { 16 | return this._clients.asObservable(); 17 | } 18 | 19 | public addClient(newClient: WebRTCClient): void { 20 | this._clients.next(this._clients.getValue().push(newClient)); 21 | } 22 | 23 | public removeClient(clientId: string): void { 24 | let clientList = this._clients.getValue(); 25 | const removeIndex = clientList.findIndex(c => c.id === clientId); 26 | this._clients.next(clientList.remove(removeIndex)); 27 | } 28 | } -------------------------------------------------------------------------------- /frontend/src/app/webrtc-chat/shared/webrtc-custom.d.ts: -------------------------------------------------------------------------------- 1 | interface ERTCPeerConnection extends RTCPeerConnection { 2 | 3 | } -------------------------------------------------------------------------------- /frontend/src/app/webrtc-chat/shared/webrtc-event-messages.ts: -------------------------------------------------------------------------------- 1 | export const SOCKET_EVENT_PEER_CONNECTED = 'api/v1/webrtc/peer-connected'; 2 | 3 | export const SOCKET_EVENT_PEER_DISCONNECTED = 'api/v1/webrtc/peer-disconnected'; 4 | 5 | export const SOCKET_EVENT_PEER_MESSAGE = 'api/v1/webrtc/peermessage'; 6 | 7 | export const SOCKET_EVENT_CONNECT_TO_ROOM = 'api/v1/webrtc/connect-to-chat'; 8 | 9 | export const RTC_PEER_MESSAGE_SDP_OFFER = 'sdp-offer'; 10 | 11 | export const RTC_PEER_MESSAGE_SDP_ANSWER = 'sdp-answer'; 12 | 13 | export const RTC_PEER_MESSAGE_ICE = 'ice'; 14 | -------------------------------------------------------------------------------- /frontend/src/app/webrtc-chat/shared/webrtc-shared.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | 3 | import { WebRTCClientStore } from './webrtc-client.store.service'; 4 | import { WebRTCConnectionService } from './webrtc-client-connection.service'; 5 | 6 | @NgModule({ 7 | providers: [ 8 | WebRTCClientStore, 9 | WebRTCConnectionService 10 | ] 11 | }) 12 | export class WebRTCSharedModule { } -------------------------------------------------------------------------------- /frontend/src/app/webrtc-chat/webrtc-chat.component.html: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /frontend/src/app/webrtc-chat/webrtc-chat.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { SafeResourceUrl, DomSanitizer } from '@angular/platform-browser'; 3 | 4 | import { WebRTCClientStore, WebRTCConnectionService } from './shared'; 5 | import { WebRTCClient } from './shared/webrtc-client.model'; 6 | 7 | @Component({ 8 | selector: 'webrtc-chat', 9 | templateUrl: './webrtc-chat.component.html' 10 | }) 11 | export class WebRTCChatComponent { 12 | public webrtcClients: WebRTCClient[]; 13 | private blobs: string[] = []; 14 | 15 | constructor( 16 | private webrtcClientStoreService: WebRTCClientStore, 17 | private webrtcConnectionService: WebRTCConnectionService, 18 | private sanitizer: DomSanitizer 19 | ) { 20 | this.webrtcClientStoreService.clients$.subscribe( 21 | clientList => this.webrtcClients = clientList.toArray(), 22 | err => console.error('Error updating the client list:', err) 23 | ); 24 | } 25 | 26 | public onClickConnectToRoom() { 27 | this.webrtcConnectionService.connectToRoom(); 28 | } 29 | 30 | // DEPRECATED 31 | public getVideoStreamURL(stream: MediaStream): SafeResourceUrl { 32 | return this.sanitizer.bypassSecurityTrustResourceUrl(URL.createObjectURL(stream)); 33 | } 34 | } -------------------------------------------------------------------------------- /frontend/src/app/webrtc-chat/webrtc-chat.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { MaterialModule } from '@angular/material'; 4 | 5 | import { SharedModule } from '../shared'; 6 | import { WebRTCChatComponent } from './webrtc-chat.component'; 7 | 8 | @NgModule({ 9 | declarations: [ 10 | WebRTCChatComponent 11 | ], 12 | imports: [ 13 | MaterialModule.forRoot(), 14 | CommonModule 15 | ], 16 | exports: [ 17 | WebRTCChatComponent 18 | ] 19 | }) 20 | export class WebRTCChatModule { } -------------------------------------------------------------------------------- /frontend/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pj035/angular2-webrtc-video-example/b7d76503de9cf20f844ec7143a76e300585a1ee3/frontend/src/assets/.gitkeep -------------------------------------------------------------------------------- /frontend/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /frontend/src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // The file contents for the current environment will overwrite these during build. 2 | // The build system defaults to the dev environment which uses `environment.ts`, but if you do 3 | // `ng build --env=prod` then `environment.prod.ts` will be used instead. 4 | // The list of which env maps to which file can be found in `angular-cli.json`. 5 | 6 | export const environment = { 7 | production: false 8 | }; 9 | -------------------------------------------------------------------------------- /frontend/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pj035/angular2-webrtc-video-example/b7d76503de9cf20f844ec7143a76e300585a1ee3/frontend/src/favicon.ico -------------------------------------------------------------------------------- /frontend/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Frontend 6 | 7 | 8 | 9 | 10 | 11 | 12 | Loading... 13 | 14 | 15 | -------------------------------------------------------------------------------- /frontend/src/main.ts: -------------------------------------------------------------------------------- 1 | import './polyfills.ts'; 2 | 3 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 4 | import { enableProdMode } from '@angular/core'; 5 | import { environment } from './environments/environment'; 6 | import { AppModule } from './app/'; 7 | 8 | if (environment.production) { 9 | enableProdMode(); 10 | } 11 | 12 | platformBrowserDynamic().bootstrapModule(AppModule); 13 | -------------------------------------------------------------------------------- /frontend/src/polyfills.ts: -------------------------------------------------------------------------------- 1 | // This file includes polyfills needed by Angular 2 and is loaded before 2 | // the app. You can add your own extra polyfills to this file. 3 | import 'core-js/es6/symbol'; 4 | import 'core-js/es6/object'; 5 | import 'core-js/es6/function'; 6 | import 'core-js/es6/parse-int'; 7 | import 'core-js/es6/parse-float'; 8 | import 'core-js/es6/number'; 9 | import 'core-js/es6/math'; 10 | import 'core-js/es6/string'; 11 | import 'core-js/es6/date'; 12 | import 'core-js/es6/array'; 13 | import 'core-js/es6/regexp'; 14 | import 'core-js/es6/map'; 15 | import 'core-js/es6/set'; 16 | import 'core-js/es6/reflect'; 17 | 18 | import 'core-js/es7/reflect'; 19 | import 'zone.js/dist/zone'; 20 | -------------------------------------------------------------------------------- /frontend/src/styles.css: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | -------------------------------------------------------------------------------- /frontend/src/test.ts: -------------------------------------------------------------------------------- 1 | import './polyfills.ts'; 2 | 3 | import 'zone.js/dist/long-stack-trace-zone'; 4 | import 'zone.js/dist/proxy.js'; 5 | import 'zone.js/dist/sync-test'; 6 | import 'zone.js/dist/jasmine-patch'; 7 | import 'zone.js/dist/async-test'; 8 | import 'zone.js/dist/fake-async-test'; 9 | import { getTestBed } from '@angular/core/testing'; 10 | import { 11 | BrowserDynamicTestingModule, 12 | platformBrowserDynamicTesting 13 | } from '@angular/platform-browser-dynamic/testing'; 14 | 15 | // Unfortunately there's no typing for the `__karma__` variable. Just declare it as any. 16 | declare var __karma__: any; 17 | declare var require: any; 18 | 19 | // Prevent Karma from running prematurely. 20 | __karma__.loaded = function () {}; 21 | 22 | // First, initialize the Angular testing environment. 23 | getTestBed().initTestEnvironment( 24 | BrowserDynamicTestingModule, 25 | platformBrowserDynamicTesting() 26 | ); 27 | // Then we find all the tests. 28 | let context = require.context('./', true, /\.spec\.ts/); 29 | // And load the modules. 30 | context.keys().map(context); 31 | // Finally, start Karma to run the tests. 32 | __karma__.start(); 33 | -------------------------------------------------------------------------------- /frontend/src/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "", 4 | "declaration": false, 5 | "emitDecoratorMetadata": true, 6 | "experimentalDecorators": true, 7 | "lib": ["es6", "dom"], 8 | "mapRoot": "./", 9 | "module": "es6", 10 | "moduleResolution": "node", 11 | "outDir": "../dist/out-tsc", 12 | "sourceMap": true, 13 | "target": "es5", 14 | "typeRoots": [ 15 | "../node_modules/@types" 16 | ] 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /frontend/src/typings.d.ts: -------------------------------------------------------------------------------- 1 | // Typings reference file, you can add your own global typings here 2 | // https://www.typescriptlang.org/docs/handbook/writing-declaration-files.html 3 | -------------------------------------------------------------------------------- /frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "commonjs", 5 | "outDir": "dist", 6 | "noImplicitAny": true, 7 | "removeComments": true, 8 | "preserveConstEnums": true, 9 | "sourceMap": true, 10 | "lib": [ 11 | "es6", "dom" 12 | ], 13 | "typeRoots": [ 14 | "node_modules/@types" 15 | ] 16 | }, 17 | "exclude": [ 18 | "node_modules" 19 | ] 20 | } -------------------------------------------------------------------------------- /frontend/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rulesDirectory": [ 3 | "node_modules/codelyzer" 4 | ], 5 | "rules": { 6 | "class-name": true, 7 | "comment-format": [ 8 | true, 9 | "check-space" 10 | ], 11 | "curly": true, 12 | "eofline": true, 13 | "forin": true, 14 | "indent": [ 15 | true, 16 | "spaces" 17 | ], 18 | "label-position": true, 19 | "max-line-length": [ 20 | true, 21 | 140 22 | ], 23 | "member-access": false, 24 | "member-ordering": [ 25 | true, 26 | "static-before-instance", 27 | "variables-before-functions" 28 | ], 29 | "no-arg": true, 30 | "no-bitwise": true, 31 | "no-console": [ 32 | true, 33 | "debug", 34 | "info", 35 | "time", 36 | "timeEnd", 37 | "trace" 38 | ], 39 | "no-construct": true, 40 | "no-debugger": true, 41 | "no-duplicate-variable": true, 42 | "no-empty": false, 43 | "no-eval": true, 44 | "no-inferrable-types": true, 45 | "no-shadowed-variable": true, 46 | "no-string-literal": false, 47 | "no-switch-case-fall-through": true, 48 | "no-trailing-whitespace": true, 49 | "no-unused-expression": true, 50 | "no-use-before-declare": true, 51 | "no-var-keyword": true, 52 | "object-literal-sort-keys": false, 53 | "one-line": [ 54 | true, 55 | "check-open-brace", 56 | "check-catch", 57 | "check-else", 58 | "check-whitespace" 59 | ], 60 | "quotemark": [ 61 | true, 62 | "single" 63 | ], 64 | "radix": true, 65 | "semicolon": [ 66 | "always" 67 | ], 68 | "triple-equals": [ 69 | true, 70 | "allow-null-check" 71 | ], 72 | "typedef-whitespace": [ 73 | true, 74 | { 75 | "call-signature": "nospace", 76 | "index-signature": "nospace", 77 | "parameter": "nospace", 78 | "property-declaration": "nospace", 79 | "variable-declaration": "nospace" 80 | } 81 | ], 82 | "variable-name": false, 83 | "whitespace": [ 84 | true, 85 | "check-branch", 86 | "check-decl", 87 | "check-operator", 88 | "check-separator", 89 | "check-type" 90 | ], 91 | 92 | "directive-selector": [true, "attribute", "app", "camelCase"], 93 | "component-selector": [true, "element", "app", "kebab-case"], 94 | "use-input-property-decorator": true, 95 | "use-output-property-decorator": true, 96 | "use-host-property-decorator": true, 97 | "no-input-rename": true, 98 | "no-output-rename": true, 99 | "use-life-cycle-interface": true, 100 | "use-pipe-transform-interface": true, 101 | "component-class-suffix": true, 102 | "directive-class-suffix": true, 103 | "no-access-missing-member": true, 104 | "templates-use-public": true, 105 | "invoke-injectable": true 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /frontend/typings.json: -------------------------------------------------------------------------------- 1 | { 2 | "globalDependencies": { 3 | "webrtc/mediastream": "registry:dt/webrtc/mediastream#0.0.0+20160317120654", 4 | "webrtc/rtcpeerconnection": "registry:dt/webrtc/rtcpeerconnection#0.0.0+20160719235950" 5 | } 6 | } 7 | --------------------------------------------------------------------------------