Client prototype, signaling server & PWA client
5
client/.editorconfig
Normal file
@@ -0,0 +1,5 @@
|
||||
[*.{js,jsx,ts,tsx,vue}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
21
client/.gitignore
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
/dist
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Log files
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Editor directories and files
|
||||
.idea
|
||||
.vscode
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
24
client/README.md
Normal file
@@ -0,0 +1,24 @@
|
||||
# client
|
||||
|
||||
## Project setup
|
||||
```
|
||||
npm install
|
||||
```
|
||||
|
||||
### Compiles and hot-reloads for development
|
||||
```
|
||||
npm run serve
|
||||
```
|
||||
|
||||
### Compiles and minifies for production
|
||||
```
|
||||
npm run build
|
||||
```
|
||||
|
||||
### Lints and fixes files
|
||||
```
|
||||
npm run lint
|
||||
```
|
||||
|
||||
### Customize configuration
|
||||
See [Configuration Reference](https://cli.vuejs.org/config/).
|
||||
5
client/babel.config.js
Normal file
@@ -0,0 +1,5 @@
|
||||
module.exports = {
|
||||
presets: [
|
||||
'@vue/cli-plugin-babel/preset'
|
||||
]
|
||||
}
|
||||
12638
client/package-lock.json
generated
Normal file
49
client/package.json
Normal file
@@ -0,0 +1,49 @@
|
||||
{
|
||||
"name": "client",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service serve",
|
||||
"build": "vue-cli-service build",
|
||||
"lint": "vue-cli-service lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"buefy": "^0.8.13",
|
||||
"core-js": "^3.6.4",
|
||||
"register-service-worker": "^1.6.2",
|
||||
"vue": "^2.6.11"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vue/cli-plugin-babel": "~4.2.0",
|
||||
"@vue/cli-plugin-eslint": "~4.2.0",
|
||||
"@vue/cli-plugin-pwa": "~4.2.0",
|
||||
"@vue/cli-service": "~4.2.0",
|
||||
"@vue/eslint-config-standard": "^5.1.0",
|
||||
"babel-eslint": "^10.0.3",
|
||||
"eslint": "^6.7.2",
|
||||
"eslint-plugin-import": "^2.20.1",
|
||||
"eslint-plugin-node": "^11.0.0",
|
||||
"eslint-plugin-promise": "^4.2.1",
|
||||
"eslint-plugin-standard": "^4.0.0",
|
||||
"eslint-plugin-vue": "^6.1.2",
|
||||
"vue-template-compiler": "^2.6.11"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"root": true,
|
||||
"env": {
|
||||
"node": true
|
||||
},
|
||||
"extends": [
|
||||
"plugin:vue/essential",
|
||||
"@vue/standard"
|
||||
],
|
||||
"parserOptions": {
|
||||
"parser": "babel-eslint"
|
||||
},
|
||||
"rules": {}
|
||||
},
|
||||
"browserslist": [
|
||||
"> 1%",
|
||||
"last 2 versions"
|
||||
]
|
||||
}
|
||||
BIN
client/public/favicon.ico
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
BIN
client/public/img/icons/android-chrome-192x192.png
Normal file
|
After Width: | Height: | Size: 9.2 KiB |
BIN
client/public/img/icons/android-chrome-512x512.png
Normal file
|
After Width: | Height: | Size: 29 KiB |
BIN
client/public/img/icons/android-chrome-maskable-192x192.png
Normal file
|
After Width: | Height: | Size: 6.3 KiB |
BIN
client/public/img/icons/android-chrome-maskable-512x512.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
client/public/img/icons/apple-touch-icon-120x120.png
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
BIN
client/public/img/icons/apple-touch-icon-152x152.png
Normal file
|
After Width: | Height: | Size: 4.0 KiB |
BIN
client/public/img/icons/apple-touch-icon-180x180.png
Normal file
|
After Width: | Height: | Size: 4.6 KiB |
BIN
client/public/img/icons/apple-touch-icon-60x60.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
client/public/img/icons/apple-touch-icon-76x76.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
client/public/img/icons/apple-touch-icon.png
Normal file
|
After Width: | Height: | Size: 4.6 KiB |
BIN
client/public/img/icons/favicon-16x16.png
Normal file
|
After Width: | Height: | Size: 799 B |
BIN
client/public/img/icons/favicon-32x32.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
client/public/img/icons/msapplication-icon-144x144.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
client/public/img/icons/mstile-150x150.png
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
149
client/public/img/icons/safari-pinned-tab.svg
Normal file
@@ -0,0 +1,149 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
||||
width="16.000000pt" height="16.000000pt" viewBox="0 0 16.000000 16.000000"
|
||||
preserveAspectRatio="xMidYMid meet">
|
||||
<metadata>
|
||||
Created by potrace 1.11, written by Peter Selinger 2001-2013
|
||||
</metadata>
|
||||
<g transform="translate(0.000000,16.000000) scale(0.000320,-0.000320)"
|
||||
fill="#000000" stroke="none">
|
||||
<path d="M18 46618 c45 -75 122 -207 122 -211 0 -2 25 -45 55 -95 30 -50 55
|
||||
-96 55 -102 0 -5 5 -10 10 -10 6 0 10 -4 10 -9 0 -5 73 -135 161 -288 89 -153
|
||||
173 -298 187 -323 14 -25 32 -57 41 -72 88 -149 187 -324 189 -335 2 -7 8 -13
|
||||
13 -13 5 0 9 -4 9 -10 0 -5 46 -89 103 -187 175 -302 490 -846 507 -876 8 -16
|
||||
20 -36 25 -45 28 -46 290 -498 339 -585 13 -23 74 -129 136 -236 61 -107 123
|
||||
-215 137 -240 14 -25 29 -50 33 -56 5 -5 23 -37 40 -70 18 -33 38 -67 44 -75
|
||||
11 -16 21 -33 63 -109 14 -25 29 -50 33 -56 4 -5 21 -35 38 -65 55 -100 261
|
||||
-455 269 -465 4 -5 14 -21 20 -35 15 -29 41 -75 103 -180 24 -41 52 -88 60
|
||||
-105 9 -16 57 -100 107 -185 112 -193 362 -626 380 -660 8 -14 23 -38 33 -55
|
||||
11 -16 23 -37 27 -45 4 -8 26 -46 48 -85 23 -38 53 -90 67 -115 46 -81 64
|
||||
-113 178 -310 62 -107 121 -210 132 -227 37 -67 56 -99 85 -148 16 -27 32 -57
|
||||
36 -65 4 -8 15 -27 25 -42 9 -15 53 -89 96 -165 44 -76 177 -307 296 -513 120
|
||||
-206 268 -463 330 -570 131 -227 117 -203 200 -348 36 -62 73 -125 82 -140 10
|
||||
-15 21 -34 25 -42 4 -8 20 -37 36 -65 17 -27 38 -65 48 -82 49 -85 64 -111 87
|
||||
-153 13 -25 28 -49 32 -55 4 -5 78 -134 165 -285 87 -151 166 -288 176 -305
|
||||
10 -16 26 -43 35 -59 9 -17 125 -217 257 -445 132 -229 253 -441 270 -471 17
|
||||
-30 45 -79 64 -108 18 -29 33 -54 33 -57 0 -2 20 -37 44 -77 24 -40 123 -212
|
||||
221 -383 97 -170 190 -330 205 -355 16 -25 39 -65 53 -90 13 -25 81 -144 152
|
||||
-265 70 -121 137 -238 150 -260 12 -22 37 -65 55 -95 18 -30 43 -73 55 -95 12
|
||||
-22 48 -85 80 -140 77 -132 163 -280 190 -330 13 -22 71 -123 130 -225 59
|
||||
-102 116 -199 126 -217 10 -17 29 -50 43 -72 15 -22 26 -43 26 -45 0 -2 27
|
||||
-50 60 -106 33 -56 60 -103 60 -105 0 -2 55 -98 90 -155 8 -14 182 -316 239
|
||||
-414 13 -22 45 -79 72 -124 27 -46 49 -86 49 -89 0 -2 14 -24 30 -48 16 -24
|
||||
30 -46 30 -49 0 -5 74 -135 100 -176 5 -8 24 -42 43 -75 50 -88 58 -101 262
|
||||
-455 104 -179 199 -345 213 -370 14 -25 28 -49 32 -55 4 -5 17 -26 28 -45 10
|
||||
-19 62 -109 114 -200 114 -197 133 -230 170 -295 16 -27 33 -57 38 -65 17 -28
|
||||
96 -165 103 -180 4 -8 16 -28 26 -45 10 -16 77 -131 148 -255 72 -124 181
|
||||
-313 243 -420 62 -107 121 -209 131 -227 35 -62 323 -560 392 -678 38 -66 83
|
||||
-145 100 -175 16 -30 33 -59 37 -65 4 -5 17 -27 29 -47 34 -61 56 -100 90
|
||||
-156 17 -29 31 -55 31 -57 0 -2 17 -32 39 -67 21 -35 134 -229 251 -433 117
|
||||
-203 235 -407 261 -451 27 -45 49 -85 49 -88 0 -4 8 -19 19 -34 15 -21 200
|
||||
-341 309 -533 10 -19 33 -58 51 -87 17 -29 31 -54 31 -56 0 -2 25 -44 55 -94
|
||||
30 -50 55 -95 55 -98 0 -4 6 -15 14 -23 7 -9 27 -41 43 -71 17 -30 170 -297
|
||||
342 -594 171 -296 311 -542 311 -547 0 -5 5 -9 10 -9 6 0 10 -4 10 -10 0 -5
|
||||
22 -47 49 -92 27 -46 58 -99 68 -118 24 -43 81 -140 93 -160 5 -8 66 -114 135
|
||||
-235 69 -121 130 -227 135 -235 12 -21 259 -447 283 -490 10 -19 28 -47 38
|
||||
-62 11 -14 19 -29 19 -32 0 -3 37 -69 83 -148 99 -170 305 -526 337 -583 13
|
||||
-22 31 -53 41 -70 11 -16 22 -37 26 -45 7 -14 82 -146 103 -180 14 -24 181
|
||||
-311 205 -355 13 -22 46 -80 75 -130 29 -49 64 -110 78 -135 14 -25 51 -88 82
|
||||
-140 31 -52 59 -102 63 -110 4 -8 18 -33 31 -55 205 -353 284 -489 309 -535
|
||||
17 -30 45 -78 62 -106 18 -28 36 -60 39 -72 4 -12 12 -22 17 -22 5 0 9 -4 9
|
||||
-10 0 -5 109 -197 241 -427 133 -230 250 -431 259 -448 51 -90 222 -385 280
|
||||
-485 37 -63 78 -135 92 -160 14 -25 67 -117 118 -205 51 -88 101 -175 111
|
||||
-193 34 -58 55 -95 149 -257 51 -88 101 -173 110 -190 9 -16 76 -131 147 -255
|
||||
72 -124 140 -241 151 -260 61 -108 281 -489 355 -615 38 -66 77 -133 87 -150
|
||||
35 -63 91 -161 100 -175 14 -23 99 -169 128 -220 54 -97 135 -235 142 -245 4
|
||||
-5 20 -32 35 -60 26 -48 238 -416 276 -480 10 -16 26 -46 37 -65 30 -53 382
|
||||
-661 403 -695 10 -16 22 -37 26 -45 4 -8 26 -48 50 -88 24 -41 43 -75 43 -77
|
||||
0 -2 22 -40 50 -85 27 -45 50 -84 50 -86 0 -3 38 -69 83 -147 84 -142 302
|
||||
-520 340 -587 10 -19 34 -60 52 -90 18 -30 44 -75 57 -100 14 -25 45 -79 70
|
||||
-120 25 -41 56 -96 70 -121 14 -25 77 -133 138 -240 62 -107 122 -210 132
|
||||
-229 25 -43 310 -535 337 -581 11 -19 26 -45 34 -59 17 -32 238 -414 266 -460
|
||||
11 -19 24 -41 28 -49 3 -7 75 -133 160 -278 84 -146 153 -269 153 -274 0 -5 5
|
||||
-9 10 -9 6 0 10 -4 10 -10 0 -5 82 -150 181 -322 182 -314 201 -346 240 -415
|
||||
12 -21 80 -139 152 -263 71 -124 141 -245 155 -270 14 -25 28 -49 32 -55 6 -8
|
||||
145 -248 220 -380 37 -66 209 -362 229 -395 11 -19 24 -42 28 -49 4 -8 67
|
||||
-118 140 -243 73 -125 133 -230 133 -233 0 -2 15 -28 33 -57 19 -29 47 -78 64
|
||||
-108 17 -30 53 -93 79 -139 53 -90 82 -141 157 -272 82 -142 115 -199 381
|
||||
-659 142 -245 268 -463 281 -485 12 -22 71 -125 132 -230 60 -104 172 -298
|
||||
248 -430 76 -132 146 -253 156 -270 11 -16 22 -36 26 -44 3 -8 30 -54 60 -103
|
||||
29 -49 53 -91 53 -93 0 -3 18 -34 40 -70 22 -36 40 -67 40 -69 0 -2 37 -66 81
|
||||
-142 45 -77 98 -168 119 -204 20 -36 47 -81 58 -100 12 -19 27 -47 33 -62 6
|
||||
-16 15 -28 20 -28 5 0 9 -4 9 -9 0 -6 63 -118 140 -251 77 -133 140 -243 140
|
||||
-245 0 -2 18 -33 41 -70 22 -37 49 -83 60 -101 10 -19 29 -51 40 -71 25 -45
|
||||
109 -189 126 -218 7 -11 17 -29 22 -40 6 -11 22 -38 35 -60 14 -22 37 -62 52
|
||||
-90 14 -27 35 -62 45 -77 11 -14 19 -29 19 -32 0 -3 18 -35 40 -71 22 -36 40
|
||||
-67 40 -69 0 -2 19 -35 42 -72 23 -38 55 -94 72 -124 26 -47 139 -244 171
|
||||
-298 6 -9 21 -36 34 -60 28 -48 37 -51 51 -19 6 12 19 36 29 52 10 17 27 46
|
||||
38 65 11 19 104 181 208 360 103 179 199 345 213 370 14 25 42 74 64 109 21
|
||||
34 38 65 38 67 0 2 18 33 40 69 22 36 40 67 40 69 0 3 177 310 199 346 16 26
|
||||
136 234 140 244 2 5 25 44 52 88 27 44 49 81 49 84 0 2 18 34 40 70 22 36 40
|
||||
67 40 69 0 2 20 36 43 77 35 58 169 289 297 513 9 17 50 86 90 155 40 69 86
|
||||
150 103 180 16 30 35 62 41 70 6 8 16 24 22 35 35 64 72 129 167 293 59 100
|
||||
116 199 127 220 11 20 30 53 41 72 43 72 1070 1850 1121 1940 14 25 65 113
|
||||
113 195 48 83 96 166 107 185 10 19 28 50 38 68 11 18 73 124 137 235 64 111
|
||||
175 303 246 427 71 124 173 299 225 390 52 91 116 202 143 248 27 45 49 85 49
|
||||
89 0 4 6 14 14 22 7 9 28 43 46 76 26 47 251 436 378 655 11 19 29 51 40 70
|
||||
11 19 101 176 201 348 99 172 181 317 181 323 0 5 5 9 10 9 6 0 10 5 10 11 0
|
||||
6 8 23 18 37 11 15 32 52 49 82 16 30 130 228 253 440 122 212 234 405 248
|
||||
430 13 25 39 70 57 100 39 65 69 117 130 225 25 44 50 87 55 95 12 19 78 134
|
||||
220 380 61 107 129 224 150 260 161 277 222 382 246 425 15 28 47 83 71 123
|
||||
24 41 43 78 43 83 0 5 4 9 8 9 4 0 13 12 19 28 7 15 23 45 36 67 66 110 277
|
||||
478 277 483 0 3 6 13 14 21 7 9 27 41 43 71 17 30 45 80 63 110 34 57 375 649
|
||||
394 685 6 11 16 27 22 35 6 8 26 42 44 75 18 33 41 74 51 90 10 17 24 41 32
|
||||
55 54 97 72 128 88 152 11 14 19 28 19 30 0 3 79 141 175 308 96 167 175 305
|
||||
175 308 0 3 6 13 14 21 7 9 26 39 41 66 33 60 276 483 338 587 24 40 46 80 50
|
||||
88 4 8 13 24 20 35 14 23 95 163 125 215 11 19 52 91 92 160 40 69 80 139 90
|
||||
155 9 17 103 179 207 360 105 182 200 346 211 365 103 181 463 802 489 845 7
|
||||
11 15 27 19 35 4 8 29 51 55 95 64 110 828 1433 848 1470 9 17 24 41 33 55 9
|
||||
14 29 48 45 77 15 28 52 93 82 145 30 51 62 107 71 123 17 30 231 398 400 690
|
||||
51 88 103 179 115 202 12 23 26 48 32 55 6 7 24 38 40 68 17 30 61 107 98 170
|
||||
37 63 84 144 103 180 19 36 41 72 48 81 8 8 14 18 14 21 0 4 27 51 59 106 32
|
||||
55 72 124 89 154 16 29 71 125 122 213 51 88 104 180 118 205 13 25 28 50 32
|
||||
55 4 6 17 26 28 45 11 19 45 80 77 135 31 55 66 116 77 135 11 19 88 152 171
|
||||
295 401 694 620 1072 650 1125 11 19 87 152 170 295 83 143 158 273 166 288 9
|
||||
16 21 36 26 45 6 9 31 52 55 96 25 43 54 94 66 115 11 20 95 164 186 321 91
|
||||
157 173 299 182 315 9 17 26 46 37 65 12 19 66 114 121 210 56 96 108 186 117
|
||||
200 8 14 24 40 34 59 24 45 383 664 412 713 5 9 17 29 26 45 15 28 120 210
|
||||
241 419 36 61 68 117 72 125 4 8 12 23 19 34 35 57 245 420 262 453 11 20 35
|
||||
61 53 90 17 29 32 54 32 56 0 3 28 51 62 108 33 57 70 119 80 138 10 19 23 42
|
||||
28 50 5 8 32 53 59 100 27 47 149 258 271 470 122 212 234 405 248 430 30 53
|
||||
62 108 80 135 6 11 15 27 19 35 4 8 85 150 181 315 96 165 187 323 202 350 31
|
||||
56 116 202 130 225 5 8 25 42 43 75 19 33 92 159 162 280 149 257 157 271 202
|
||||
350 19 33 38 67 43 75 9 14 228 392 275 475 12 22 55 96 95 165 40 69 80 139
|
||||
90 155 24 42 202 350 221 383 9 15 27 47 41 72 14 25 75 131 136 236 61 106
|
||||
121 210 134 232 99 172 271 470 279 482 5 8 23 40 40 70 18 30 81 141 142 245
|
||||
60 105 121 210 135 235 14 25 71 124 127 220 56 96 143 247 194 335 51 88 96
|
||||
167 102 175 14 24 180 311 204 355 23 43 340 590 356 615 5 8 50 87 101 175
|
||||
171 301 517 898 582 1008 25 43 46 81 46 83 0 2 12 23 27 47 14 23 40 67 56
|
||||
97 16 30 35 62 42 70 7 8 15 22 18 30 4 8 20 38 37 65 16 28 33 57 37 65 6 12
|
||||
111 196 143 250 5 8 55 95 112 193 57 98 113 195 126 215 12 20 27 46 32 57 6
|
||||
11 14 27 20 35 5 8 76 130 156 270 80 140 165 287 187 325 23 39 52 90 66 115
|
||||
13 25 30 52 37 61 8 8 14 18 14 21 0 4 41 77 92 165 50 87 175 302 276 478
|
||||
101 176 208 360 236 408 28 49 67 117 86 152 19 35 41 70 48 77 6 6 12 15 12
|
||||
19 0 7 124 224 167 291 12 21 23 40 23 42 0 2 21 40 46 83 26 43 55 92 64 109
|
||||
54 95 327 568 354 614 19 30 45 75 59 100 71 128 82 145 89 148 4 2 8 8 8 13
|
||||
0 5 42 82 94 172 311 538 496 858 518 897 14 25 40 70 58 100 18 30 42 71 53
|
||||
90 10 19 79 139 152 265 73 127 142 246 153 265 10 19 43 76 72 125 29 50 63
|
||||
108 75 130 65 116 80 140 87 143 4 2 8 8 8 12 0 8 114 212 140 250 6 8 14 24
|
||||
20 35 5 11 54 97 108 190 l100 170 -9611 3 c-5286 1 -9614 -1 -9618 -5 -5 -6
|
||||
-419 -719 -619 -1068 -89 -155 -267 -463 -323 -560 -38 -66 -81 -140 -95 -165
|
||||
-31 -56 -263 -457 -526 -910 -110 -190 -224 -388 -254 -440 -29 -52 -61 -109
|
||||
-71 -125 -23 -39 -243 -420 -268 -465 -11 -19 -204 -352 -428 -740 -224 -388
|
||||
-477 -826 -563 -975 -85 -148 -185 -322 -222 -385 -37 -63 -120 -207 -185
|
||||
-320 -65 -113 -177 -306 -248 -430 -72 -124 -172 -297 -222 -385 -51 -88 -142
|
||||
-245 -202 -350 -131 -226 -247 -427 -408 -705 -65 -113 -249 -432 -410 -710
|
||||
-160 -278 -388 -673 -506 -877 -118 -205 -216 -373 -219 -373 -3 0 -52 82
|
||||
-109 183 -58 100 -144 250 -192 332 -95 164 -402 696 -647 1120 -85 149 -228
|
||||
396 -317 550 -212 365 -982 1700 -1008 1745 -10 19 -43 76 -72 125 -29 50 -64
|
||||
110 -77 135 -14 25 -63 110 -110 190 -47 80 -96 165 -110 190 -14 25 -99 171
|
||||
-188 325 -89 154 -174 300 -188 325 -13 25 -64 113 -112 195 -48 83 -140 242
|
||||
-205 355 -65 113 -183 317 -263 454 -79 137 -152 264 -163 282 -50 89 -335
|
||||
583 -354 614 -12 19 -34 58 -50 85 -15 28 -129 226 -253 440 -124 215 -235
|
||||
408 -247 430 -12 22 -69 121 -127 220 -58 99 -226 389 -373 645 -148 256 -324
|
||||
561 -392 678 -67 117 -134 232 -147 255 -13 23 -33 59 -46 80 l-22 37 -9615 0
|
||||
-9615 0 20 -32z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 10 KiB |
17
client/public/index.html
Normal file
@@ -0,0 +1,17 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
||||
<title><%= htmlWebpackPlugin.options.title %></title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
||||
</noscript>
|
||||
<div id="app"></div>
|
||||
<!-- built files will be auto injected -->
|
||||
</body>
|
||||
</html>
|
||||
2
client/public/robots.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
User-agent: *
|
||||
Disallow:
|
||||
37
client/src/App.vue
Normal file
@@ -0,0 +1,37 @@
|
||||
<template>
|
||||
<div id="app">
|
||||
<Video v-if="connection" v-bind:connection="connection"/>
|
||||
<Configure v-else v-on:connect="offer"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import Video from './components/video'
|
||||
import Configure from './components/configure'
|
||||
export default {
|
||||
name: 'App',
|
||||
components: {
|
||||
Configure,
|
||||
Video
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
connectedUsers: null,
|
||||
connectedToName: null,
|
||||
connection: null
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
#app {
|
||||
font-family: Avenir, Helvetica, Arial, sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
text-align: center;
|
||||
color: #2c3e50;
|
||||
margin-top: 10px;
|
||||
}
|
||||
</style>
|
||||
BIN
client/src/assets/logo.png
Normal file
|
After Width: | Height: | Size: 6.7 KiB |
41
client/src/components/configure.vue
Normal file
@@ -0,0 +1,41 @@
|
||||
<template>
|
||||
<container>
|
||||
<img alt="Vue logo" src="../assets/logo.png">
|
||||
<h1 class="title">Lil' Streamy</h1>
|
||||
<RoomSelector/>
|
||||
<UsernameChooser/>
|
||||
</container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import RoomSelector from './roomSelector'
|
||||
import UsernameChooser from './usernameChooser'
|
||||
|
||||
export default {
|
||||
name: 'Video',
|
||||
components: {
|
||||
RoomSelector,
|
||||
UsernameChooser
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
isLoading: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async saveName (user) {
|
||||
|
||||
},
|
||||
async saveConnection (connection) {
|
||||
|
||||
},
|
||||
async connect () {
|
||||
this.$emit('connect', this.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
36
client/src/components/roomSelector.vue
Normal file
@@ -0,0 +1,36 @@
|
||||
<template>
|
||||
<div class="container">
|
||||
<section>
|
||||
<h3>Choose a room</h3>
|
||||
<b-field position="is-centered">
|
||||
<b-select placeholder="Select a room">
|
||||
<option>
|
||||
</option>
|
||||
</b-select>
|
||||
<button class="button is-success" @click="connect">Connect</button>
|
||||
</b-field>
|
||||
</section>
|
||||
<b-loading :active.sync="isLoading" :is-full-page=false />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'ClientSelector',
|
||||
data () {
|
||||
return {
|
||||
isLoading: false,
|
||||
name: ''
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async connect () {
|
||||
this.$emit('connect', this.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
29
client/src/components/usernameChooser.vue
Normal file
@@ -0,0 +1,29 @@
|
||||
<template>
|
||||
<div>
|
||||
<h3>Enter a username</h3>
|
||||
<b-field position="is-centered">
|
||||
<b-input :value="name" v-model="name" placeholder="..."></b-input>
|
||||
<button class="button is-success" @click="connect">Login</button>
|
||||
</b-field>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'UsernameChooser',
|
||||
data () {
|
||||
return {
|
||||
name: ''
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async sendName () {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
24
client/src/components/video.vue
Normal file
@@ -0,0 +1,24 @@
|
||||
<template>
|
||||
<container>
|
||||
</container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Video',
|
||||
data () {
|
||||
return {
|
||||
isLoading: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async connect () {
|
||||
this.$emit('connect', this.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
17
client/src/main.js
Normal file
@@ -0,0 +1,17 @@
|
||||
import Vue from 'vue'
|
||||
import App from './App.vue'
|
||||
import './registerServiceWorker'
|
||||
import Buefy from 'buefy'
|
||||
|
||||
import Rtc from './scripts/rtc'
|
||||
|
||||
import 'buefy/dist/buefy.css'
|
||||
|
||||
Vue.config.productionTip = false
|
||||
Vue.use(Buefy)
|
||||
|
||||
Vue.$rtc = Rtc
|
||||
|
||||
new Vue({
|
||||
render: h => h(App)
|
||||
}).$mount('#app')
|
||||
32
client/src/registerServiceWorker.js
Normal file
@@ -0,0 +1,32 @@
|
||||
/* eslint-disable no-console */
|
||||
|
||||
import { register } from 'register-service-worker'
|
||||
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
register(`${process.env.BASE_URL}service-worker.js`, {
|
||||
ready () {
|
||||
console.log(
|
||||
'App is being served from cache by a service worker.\n' +
|
||||
'For more details, visit https://goo.gl/AFskqB'
|
||||
)
|
||||
},
|
||||
registered () {
|
||||
console.log('Service worker has been registered.')
|
||||
},
|
||||
cached () {
|
||||
console.log('Content has been cached for offline use.')
|
||||
},
|
||||
updatefound () {
|
||||
console.log('New content is downloading.')
|
||||
},
|
||||
updated () {
|
||||
console.log('New content is available; please refresh.')
|
||||
},
|
||||
offline () {
|
||||
console.log('No internet connection found. App is running in offline mode.')
|
||||
},
|
||||
error (error) {
|
||||
console.error('Error during service worker registration:', error)
|
||||
}
|
||||
})
|
||||
}
|
||||
250
client/src/scripts/rtc.js
Normal file
@@ -0,0 +1,250 @@
|
||||
var name
|
||||
var connections = {}
|
||||
|
||||
const configuration = {
|
||||
iceServers: [{ urls: 'stun:stun.l.google.com:19302' }]
|
||||
}
|
||||
|
||||
const offerOptions = {
|
||||
offerToReceiveAudio: 1,
|
||||
offerToReceiveVideo: 1
|
||||
}
|
||||
|
||||
function handleLogin (success) {
|
||||
if (success === false) {
|
||||
alert('try a different username')
|
||||
} else {
|
||||
}
|
||||
}
|
||||
|
||||
async function createPeerConnection (target) {
|
||||
console.log('CREATED PEER CONNECTION')
|
||||
var connection = new RTCPeerConnection(configuration)
|
||||
connections[target] = connection
|
||||
|
||||
connection.onicecandidate = function(event) {
|
||||
if (event.candidate) {
|
||||
send({
|
||||
type: 'candidate',
|
||||
name: name,
|
||||
target: target,
|
||||
candidate: event.candidate
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
connection.onnegotiationneeded = function() { handleNegotiationNeededEvent(target); }
|
||||
connection.ontrack = function(event) { handleTrackEvent(event); }
|
||||
connection.onsignalingstatechange = function() { handleSignalingStateChangeEvent(connection); }
|
||||
connection.oniceconnectionstatechange = function() { handleICEConnectionStateChangeEvent(connection); }
|
||||
connection.onicegatheringstatechange = function() { handleICEGatheringStateChangeEvent(connection); }
|
||||
}
|
||||
|
||||
function handleICEConnectionStateChangeEvent (connection) {
|
||||
console.log('ICE CONNECTION CHANGE '+connection.iceConnectionState)
|
||||
}
|
||||
|
||||
function handleICEGatheringStateChangeEvent (connection) {
|
||||
console.log('ICE GATHERING CHANGE '+connection.iceGatheringState)
|
||||
}
|
||||
|
||||
async function makeOffer (target) {
|
||||
createPeerConnection(target)
|
||||
|
||||
var connection = connections[target]
|
||||
|
||||
var offer = await connection.createOffer()
|
||||
send({
|
||||
type: 'offer',
|
||||
name: name,
|
||||
target: target,
|
||||
offer: offer
|
||||
});
|
||||
await connection.setLocalDescription(offer)
|
||||
|
||||
}
|
||||
|
||||
async function handleOffer (offer, target) {
|
||||
console.log('GOT OFFER FROM '+target)
|
||||
await createPeerConnection(target)
|
||||
|
||||
var connection = connections[target]
|
||||
|
||||
await connection.setRemoteDescription(new RTCSessionDescription(offer))
|
||||
|
||||
if (stream) {
|
||||
console.log('STREAM DETECTED')
|
||||
stream.getTracks().forEach((track) => {
|
||||
console.log('ADDED TRACK')
|
||||
connection.addTrack(track, stream)
|
||||
});
|
||||
}
|
||||
|
||||
//create an answer to an offer
|
||||
var answer = await connection.createAnswer()
|
||||
|
||||
await connection.setLocalDescription(answer)
|
||||
|
||||
send({
|
||||
type: 'answer',
|
||||
name: name,
|
||||
target: target,
|
||||
answer: answer
|
||||
});
|
||||
};
|
||||
|
||||
async function handleAnswer (answer, target) {
|
||||
console.log('GOT ANSWER FROM '+target)
|
||||
var connection = connections[target]
|
||||
await connection.setRemoteDescription(new RTCSessionDescription(answer))
|
||||
};
|
||||
|
||||
async function handleCandidate (candidate, target) {
|
||||
console.log('GOT CANDIDATE FROM '+target)
|
||||
var connection = connections[target]
|
||||
await connection.addIceCandidate(new RTCIceCandidate(candidate))
|
||||
};
|
||||
|
||||
async function handleNegotiationNeededEvent (target) {
|
||||
console.log('NEGOTIATION NEEDED FROM '+target)
|
||||
|
||||
var connection = connections[target]
|
||||
var offer = await connection.createOffer(offerOptions)
|
||||
|
||||
await connection.setLocalDescription(offer)
|
||||
|
||||
send({
|
||||
type: 'video-offer',
|
||||
name: name,
|
||||
target: target,
|
||||
sdp: connection.localDescription
|
||||
});
|
||||
}
|
||||
|
||||
function handleLeave () {
|
||||
connections.foreach( (connection) => {
|
||||
connection.close()
|
||||
connection.onicecandidate = null
|
||||
//connection.onaddstream = null
|
||||
connection = null
|
||||
});
|
||||
connections = {};
|
||||
|
||||
remoteVideo.src = null
|
||||
};
|
||||
|
||||
function handleUserlist (list) {
|
||||
console.log('GOT USER LIST')
|
||||
|
||||
}
|
||||
|
||||
async function handleVideoOffer (sdp, target) {
|
||||
console.log('GOT VIDEO OFFER FROM '+target)
|
||||
await createPeerConnection(target)
|
||||
var connection = connections[target]
|
||||
await connection.setRemoteDescription(new RTCSessionDescription(sdp))
|
||||
|
||||
if (stream) {
|
||||
console.log('STREAM DETECTED')
|
||||
stream.getTracks().forEach((track) => {
|
||||
console.log('ADDED TRACK')
|
||||
connection.addTrack(track, stream)
|
||||
});
|
||||
}
|
||||
|
||||
var answer = await connection.createAnswer()
|
||||
await connection.setLocalDescription(answer)
|
||||
|
||||
send({
|
||||
type: 'video-answer',
|
||||
name: name,
|
||||
target: target,
|
||||
sdp: answer
|
||||
});
|
||||
}
|
||||
|
||||
async function handleVideoAnswer (sdp, target) {
|
||||
console.log('GOT VIDEO ANSWER FROM '+target)
|
||||
|
||||
var connection = connections[target]
|
||||
await connection.setRemoteDescription(new RTCSessionDescription(sdp))
|
||||
}
|
||||
|
||||
async function handleSignalingStateChangeEvent (connection) {
|
||||
console.log('STATE CHANGED TO : ' + connection.signalingState)
|
||||
switch(connection.signalingState) {
|
||||
case 'closed':
|
||||
await connection.close()
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
function handleTrackEvent (event) {
|
||||
console.log('GOT TRACK')
|
||||
remoteVideo.srcObject = event.streams[0]
|
||||
}
|
||||
|
||||
//connecting to our signaling server
|
||||
var conn = new WebSocket('ws://localhost:9090')
|
||||
|
||||
conn.onopen = function () {
|
||||
console.log('Connected to the signaling server')
|
||||
};
|
||||
|
||||
//when we got a message from a signaling server
|
||||
conn.onmessage = function (msg) {
|
||||
console.log('Got message', msg.data)
|
||||
|
||||
var data = JSON.parse(msg.data)
|
||||
|
||||
switch(data.type) {
|
||||
case 'login':
|
||||
handleLogin(data.success)
|
||||
break
|
||||
|
||||
case 'offer':
|
||||
handleOffer(data.offer, data.name)
|
||||
break
|
||||
|
||||
case 'answer':
|
||||
handleAnswer(data.answer, data.name)
|
||||
break
|
||||
|
||||
case 'candidate':
|
||||
handleCandidate(data.candidate, data.name)
|
||||
break
|
||||
|
||||
case 'userlist':
|
||||
handleUserlist(data)
|
||||
break
|
||||
|
||||
case 'leave':
|
||||
handleLeave()
|
||||
break
|
||||
|
||||
case 'video-offer':
|
||||
handleVideoOffer(data.sdp, data.name)
|
||||
break
|
||||
|
||||
case 'video-answer':
|
||||
handleVideoAnswer(data.sdp, data.name)
|
||||
break
|
||||
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
conn.onerror = function (err) {
|
||||
console.log('Got error', err)
|
||||
}
|
||||
|
||||
function send (message) {
|
||||
console.log('Sended message', message)
|
||||
conn.send(JSON.stringify(message))
|
||||
}
|
||||
|
||||
export {
|
||||
send,
|
||||
makeOffer
|
||||
}
|
||||
0
client/src/scripts/signal.js
Normal file
1
clientV/bulma.min.css
vendored
Normal file
34
clientV/index.html
Normal file
@@ -0,0 +1,34 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta content="width=device-width, initial-scale=1.0" name="viewport">
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
<link rel="stylesheet" href="bulma.min.css" />
|
||||
<title>Lil'Streamy</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<button id="loginBt" >Login</button>
|
||||
<input id="loginInput" placeholder="..." type="text"/>
|
||||
|
||||
<hr>
|
||||
|
||||
<video autoplay controls id="video"></video>
|
||||
|
||||
<hr>
|
||||
|
||||
<button id="callBt" >Call</button>
|
||||
<input id="callInput" placeholder="..." type="text"/>
|
||||
<button id="disconnectBt" >Disconnect</button>
|
||||
|
||||
<hr>
|
||||
|
||||
<input id="videoInput" type="file" accept="video/*"/>
|
||||
|
||||
<script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
|
||||
<script src = "scripts/script.js"></script>
|
||||
<script src = "scripts/rtc3.js"></script>
|
||||
<script src = "scripts/signal.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
82
clientV/index_old.html
Normal file
@@ -0,0 +1,82 @@
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>WebRTC Video Demo</title>
|
||||
<link rel = "stylesheet" href = "node_modules/bootstrap/dist/css/bootstrap.min.css"/>
|
||||
</head>
|
||||
|
||||
<style>
|
||||
|
||||
body {
|
||||
background: #eee;
|
||||
padding: 5% 0;
|
||||
}
|
||||
|
||||
video {
|
||||
background: black;
|
||||
border: 1px solid gray;
|
||||
}
|
||||
|
||||
.call-page {
|
||||
position: relative;
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
width: 500px;
|
||||
height: 500px;
|
||||
}
|
||||
|
||||
#localVideo {
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
position: absolute;
|
||||
top: 15px;
|
||||
right: 15px;
|
||||
}
|
||||
|
||||
#remoteVideo {
|
||||
width: 500px;
|
||||
height: 500px;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<body>
|
||||
|
||||
<div id = "loginPage" class = "container text-center">
|
||||
|
||||
<div class = "row">
|
||||
<div class = "col-md-4 col-md-offset-4">
|
||||
|
||||
<h2>WebRTC Video Demo. Please sign in</h2>
|
||||
<label for = "usernameInput" class = "sr-only">Login</label>
|
||||
<input type = "email" id = "usernameInput" c
|
||||
lass = "form-control formgroup" placeholder = "Login"
|
||||
required = "" autofocus = "">
|
||||
<button id = "loginBtn" class = "btn btn-lg btn-primary btnblock">
|
||||
Sign in</button>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div id = "callPage" class = "call-page">
|
||||
<video id = "localVideo" autoplay></video>
|
||||
<video id = "remoteVideo" autoplay></video>
|
||||
|
||||
<div class = "row text-center">
|
||||
<div class = "col-md-12">
|
||||
<input id = "callToUsernameInput" type = "text"
|
||||
placeholder = "username to call" />
|
||||
<button id = "callBtn" class = "btn-success btn">Call</button>
|
||||
<button id = "hangUpBtn" class = "btn-danger btn">Hang Up</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<script src = "scripts/rtc_old.js"></script>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
220
clientV/scripts/rtc.js
Normal file
@@ -0,0 +1,220 @@
|
||||
var name;
|
||||
var connectedUser;
|
||||
|
||||
var loginInput = document.querySelector('#loginInput');
|
||||
var loginBt = document.querySelector('#loginBt');
|
||||
|
||||
var callToUsernameInput = document.querySelector('#callInput');
|
||||
var callBtn = document.querySelector('#callBt');
|
||||
|
||||
var hangUpBtn = document.querySelector('#disconnectBt');
|
||||
|
||||
const remoteVideo = document.querySelector('#video');
|
||||
const remoteVideo2 = document.querySelector('#video2');
|
||||
|
||||
var videoInput = document.querySelector('#videoInput');
|
||||
|
||||
var yourConn;
|
||||
var stream;
|
||||
|
||||
var reader;
|
||||
|
||||
const configuration = {
|
||||
iceServers: [{ urls: "stun:stun.l.google.com:19302" }]
|
||||
};
|
||||
|
||||
const offerOptions = {
|
||||
offerToReceiveAudio: 1,
|
||||
offerToReceiveVideo: 1
|
||||
};
|
||||
|
||||
videoInput.addEventListener("change", function (event) {
|
||||
var file = this.files[0]
|
||||
var type = file.type
|
||||
var videoNode = remoteVideo2
|
||||
var canPlay = videoNode.canPlayType(type)
|
||||
if (canPlay === '') canPlay = 'no'
|
||||
var message = 'Can play type "' + type + '": ' + canPlay
|
||||
var isError = canPlay === 'no'
|
||||
//displayMessage(message, isError)
|
||||
|
||||
if (isError) {
|
||||
return
|
||||
}
|
||||
|
||||
var fileURL = URL.createObjectURL(file)
|
||||
videoNode.src = fileURL
|
||||
|
||||
|
||||
});
|
||||
|
||||
remoteVideo.onplay = function() {
|
||||
if(remoteVideo.mozCaptureStream()) stream = remoteVideo.mozCaptureStream();
|
||||
else stream = remoteVideo.captureStream();
|
||||
|
||||
//remoteVideo2.srcObject = stream;
|
||||
|
||||
stream.getTracks().forEach((track) => {
|
||||
console.log("ADDED TRACK");
|
||||
yourConn.addTrack(track, stream);
|
||||
});
|
||||
|
||||
//yourConn.addStream(stream);
|
||||
}
|
||||
|
||||
// Login when the user clicks the button
|
||||
loginBt.addEventListener("click", function (event) {
|
||||
name = loginInput.value;
|
||||
|
||||
if (name.length > 0) {
|
||||
send({
|
||||
type: "login",
|
||||
name: name
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
function handleLogin(success) {
|
||||
if (success === false) {
|
||||
alert("Ooops...try a different username");
|
||||
} else {
|
||||
//**********************
|
||||
//Starting a peer connection
|
||||
//**********************
|
||||
|
||||
yourConn = new RTCPeerConnection(configuration);
|
||||
|
||||
yourConn.ontrack = function (event) {
|
||||
console.log("GOT TRACK");
|
||||
remoteVideo.srcObject = event.streams[0];
|
||||
}
|
||||
|
||||
if (stream) {
|
||||
remoteVideo2.srcObject = stream;
|
||||
|
||||
stream.getTracks().forEach((track) => {
|
||||
yourConn.addTrack(track, stream);
|
||||
});
|
||||
}
|
||||
|
||||
// setup stream listening
|
||||
//yourConn.addStream(stream);
|
||||
|
||||
|
||||
// Setup ice handling
|
||||
yourConn.onicecandidate = function (event) {
|
||||
if (event.candidate) {
|
||||
send({
|
||||
type: "candidate",
|
||||
candidate: event.candidate
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
yourConn.onnegotiationneeded = handleNegotiationNeededEvent;
|
||||
|
||||
window.setInterval(getConnectionStats, 1000);
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
function getConnectionStats() {
|
||||
/*yourConn.getStats().then(stats => {
|
||||
var statsOutput = "";
|
||||
stats.forEach(report => {
|
||||
statsOutput += report;
|
||||
});
|
||||
console.log(statsOutput);
|
||||
});*/
|
||||
console.log(yourConn.connectionState);
|
||||
|
||||
}
|
||||
|
||||
//initiating a call
|
||||
callBtn.addEventListener("click", function () {
|
||||
var callToUsername = callToUsernameInput.value;
|
||||
|
||||
if (callToUsername.length > 0) {
|
||||
|
||||
connectedUser = callToUsername;
|
||||
|
||||
// create an offer
|
||||
yourConn.createOffer(function (offer) {
|
||||
send({
|
||||
type: "offer",
|
||||
offer: offer
|
||||
});
|
||||
|
||||
yourConn.setLocalDescription(offer);
|
||||
}, function (error) {
|
||||
alert("Error when creating an offer");
|
||||
},
|
||||
offerOptions);
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
//when somebody sends us an offer
|
||||
function handleOffer(offer, name) {
|
||||
console.log("GOT OFFER");
|
||||
connectedUser = name;
|
||||
yourConn.setRemoteDescription(new RTCSessionDescription(offer));
|
||||
|
||||
//create an answer to an offer
|
||||
yourConn.createAnswer(function (answer) {
|
||||
yourConn.setLocalDescription(answer);
|
||||
|
||||
send({
|
||||
type: "answer",
|
||||
answer: answer
|
||||
});
|
||||
|
||||
}, function (error) {
|
||||
alert("Error when creating an answer");
|
||||
});
|
||||
};
|
||||
|
||||
//when we got an answer from a remote user
|
||||
function handleAnswer(answer) {
|
||||
console.log("GOT ANSWER");
|
||||
yourConn.setRemoteDescription(new RTCSessionDescription(answer));
|
||||
};
|
||||
|
||||
//when we got an ice candidate from a remote user
|
||||
function handleCandidate(candidate) {
|
||||
console.log("GOT CANDIDATE");
|
||||
yourConn.addIceCandidate(new RTCIceCandidate(candidate));
|
||||
};
|
||||
|
||||
function handleNegotiationNeededEvent() {
|
||||
console.log("NEGOTIATION NEEDED");
|
||||
yourConn.createOffer().then(function(offer) {
|
||||
return yourConn.setLocalDescription(offer);
|
||||
})
|
||||
.then(function() {
|
||||
send({
|
||||
type: "video-offer",
|
||||
sdp: yourConn.localDescription
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
//hang up
|
||||
hangUpBtn.addEventListener("click", function () {
|
||||
|
||||
send({
|
||||
type: "leave"
|
||||
});
|
||||
|
||||
handleLeave();
|
||||
});
|
||||
|
||||
function handleLeave() {
|
||||
connectedUser = null;
|
||||
remoteVideo.src = null;
|
||||
|
||||
yourConn.close();
|
||||
yourConn.onicecandidate = null;
|
||||
//yourConn.onaddstream = null;
|
||||
};
|
||||
658
clientV/scripts/rtc2.js
Normal file
@@ -0,0 +1,658 @@
|
||||
"use strict";
|
||||
|
||||
// Get our hostname
|
||||
|
||||
var myHostname = window.location.hostname;
|
||||
if (!myHostname) {
|
||||
myHostname = "localhost";
|
||||
}
|
||||
log("Hostname: " + myHostname);
|
||||
|
||||
// WebSocket chat/signaling channel variables.
|
||||
|
||||
var connection = null;
|
||||
var clientID = 0;
|
||||
|
||||
// The media constraints object describes what sort of stream we want
|
||||
// to request from the local A/V hardware (typically a webcam and
|
||||
// microphone). Here, we specify only that we want both audio and
|
||||
// video; however, you can be more specific. It's possible to state
|
||||
// that you would prefer (or require) specific resolutions of video,
|
||||
// whether to prefer the user-facing or rear-facing camera (if available),
|
||||
// and so on.
|
||||
//
|
||||
// See also:
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/MediaStreamConstraints
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia
|
||||
//
|
||||
|
||||
var mediaConstraints = {
|
||||
audio: true, // We want an audio track
|
||||
video: {
|
||||
aspectRatio: {
|
||||
ideal: 1.333333 // 3:2 aspect is preferred
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var myUsername = null;
|
||||
var targetUsername = null; // To store username of other peer
|
||||
var myPeerConnection = null; // RTCPeerConnection
|
||||
var transceiver = null; // RTCRtpTransceiver
|
||||
var webcamStream = null; // MediaStream from webcam
|
||||
|
||||
// Output logging information to console.
|
||||
|
||||
function log(text) {
|
||||
var time = new Date();
|
||||
|
||||
console.log("[" + time.toLocaleTimeString() + "] " + text);
|
||||
}
|
||||
|
||||
// Output an error message to console.
|
||||
|
||||
function log_error(text) {
|
||||
var time = new Date();
|
||||
|
||||
console.trace("[" + time.toLocaleTimeString() + "] " + text);
|
||||
}
|
||||
|
||||
// Send a JavaScript object by converting it to JSON and sending
|
||||
// it as a message on the WebSocket connection.
|
||||
|
||||
function sendToServer(msg) {
|
||||
var msgJSON = JSON.stringify(msg);
|
||||
|
||||
log("Sending '" + msg.type + "' message: " + msgJSON);
|
||||
connection.send(msgJSON);
|
||||
}
|
||||
|
||||
// Called when the "id" message is received; this message is sent by the
|
||||
// server to assign this login session a unique ID number; in response,
|
||||
// this function sends a "username" message to set our username for this
|
||||
// session.
|
||||
function setUsername() {
|
||||
myUsername = document.getElementById("name").value;
|
||||
|
||||
sendToServer({
|
||||
name: myUsername,
|
||||
date: Date.now(),
|
||||
id: clientID,
|
||||
type: "username"
|
||||
});
|
||||
}
|
||||
|
||||
// Open and configure the connection to the WebSocket server.
|
||||
|
||||
function connect() {
|
||||
var serverUrl;
|
||||
var scheme = "ws";
|
||||
|
||||
// If this is an HTTPS connection, we have to use a secure WebSocket
|
||||
// connection too, so add another "s" to the scheme.
|
||||
|
||||
if (document.location.protocol === "https:") {
|
||||
scheme += "s";
|
||||
}
|
||||
serverUrl = scheme + "://" + myHostname + ":6503";
|
||||
|
||||
log(`Connecting to server: ${serverUrl}`);
|
||||
connection = new WebSocket(serverUrl, "json");
|
||||
|
||||
connection.onopen = function(evt) {
|
||||
document.getElementById("text").disabled = false;
|
||||
document.getElementById("send").disabled = false;
|
||||
};
|
||||
|
||||
connection.onerror = function(evt) {
|
||||
console.dir(evt);
|
||||
}
|
||||
|
||||
connection.onmessage = function(evt) {
|
||||
var chatBox = document.querySelector(".chatbox");
|
||||
var text = "";
|
||||
var msg = JSON.parse(evt.data);
|
||||
log("Message received: ");
|
||||
console.dir(msg);
|
||||
var time = new Date(msg.date);
|
||||
var timeStr = time.toLocaleTimeString();
|
||||
|
||||
switch(msg.type) {
|
||||
case "id":
|
||||
clientID = msg.id;
|
||||
setUsername();
|
||||
break;
|
||||
|
||||
case "username":
|
||||
text = "<b>User <em>" + msg.name + "</em> signed in at " + timeStr + "</b><br>";
|
||||
break;
|
||||
|
||||
case "message":
|
||||
text = "(" + timeStr + ") <b>" + msg.name + "</b>: " + msg.text + "<br>";
|
||||
break;
|
||||
|
||||
case "rejectusername":
|
||||
myUsername = msg.name;
|
||||
text = "<b>Your username has been set to <em>" + myUsername +
|
||||
"</em> because the name you chose is in use.</b><br>";
|
||||
break;
|
||||
|
||||
case "userlist": // Received an updated user list
|
||||
handleUserlistMsg(msg);
|
||||
break;
|
||||
|
||||
// Signaling messages: these messages are used to trade WebRTC
|
||||
// signaling information during negotiations leading up to a video
|
||||
// call.
|
||||
|
||||
case "video-offer": // Invitation and offer to chat
|
||||
handleVideoOfferMsg(msg);
|
||||
break;
|
||||
|
||||
case "video-answer": // Callee has answered our offer
|
||||
handleVideoAnswerMsg(msg);
|
||||
break;
|
||||
|
||||
case "new-ice-candidate": // A new ICE candidate has been received
|
||||
handleNewICECandidateMsg(msg);
|
||||
break;
|
||||
|
||||
case "hang-up": // The other peer has hung up the call
|
||||
handleHangUpMsg(msg);
|
||||
break;
|
||||
|
||||
// Unknown message; output to console for debugging.
|
||||
|
||||
default:
|
||||
log_error("Unknown message received:");
|
||||
log_error(msg);
|
||||
}
|
||||
|
||||
// If there's text to insert into the chat buffer, do so now, then
|
||||
// scroll the chat panel so that the new text is visible.
|
||||
|
||||
if (text.length) {
|
||||
chatBox.innerHTML += text;
|
||||
chatBox.scrollTop = chatBox.scrollHeight - chatBox.clientHeight;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Handles a click on the Send button (or pressing return/enter) by
|
||||
// building a "message" object and sending it to the server.
|
||||
function handleSendButton() {
|
||||
var msg = {
|
||||
text: document.getElementById("text").value,
|
||||
type: "message",
|
||||
id: clientID,
|
||||
date: Date.now()
|
||||
};
|
||||
sendToServer(msg);
|
||||
document.getElementById("text").value = "";
|
||||
}
|
||||
|
||||
// Handler for keyboard events. This is used to intercept the return and
|
||||
// enter keys so that we can call send() to transmit the entered text
|
||||
// to the server.
|
||||
function handleKey(evt) {
|
||||
if (evt.keyCode === 13 || evt.keyCode === 14) {
|
||||
if (!document.getElementById("send").disabled) {
|
||||
handleSendButton();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create the RTCPeerConnection which knows how to talk to our
|
||||
// selected STUN/TURN server and then uses getUserMedia() to find
|
||||
// our camera and microphone and add that stream to the connection for
|
||||
// use in our video call. Then we configure event handlers to get
|
||||
// needed notifications on the call.
|
||||
|
||||
async function createPeerConnection() {
|
||||
log("Setting up a connection...");
|
||||
|
||||
// Create an RTCPeerConnection which knows to use our chosen
|
||||
// STUN server.
|
||||
|
||||
myPeerConnection = new RTCPeerConnection({
|
||||
iceServers: [ // Information about ICE servers - Use your own!
|
||||
{
|
||||
urls: "turn:" + myHostname, // A TURN server
|
||||
username: "webrtc",
|
||||
credential: "turnserver"
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
// Set up event handlers for the ICE negotiation process.
|
||||
|
||||
myPeerConnection.onicecandidate = handleICECandidateEvent;
|
||||
myPeerConnection.oniceconnectionstatechange = handleICEConnectionStateChangeEvent;
|
||||
myPeerConnection.onicegatheringstatechange = handleICEGatheringStateChangeEvent;
|
||||
myPeerConnection.onsignalingstatechange = handleSignalingStateChangeEvent;
|
||||
myPeerConnection.onnegotiationneeded = handleNegotiationNeededEvent;
|
||||
myPeerConnection.ontrack = handleTrackEvent;
|
||||
}
|
||||
|
||||
// Called by the WebRTC layer to let us know when it's time to
|
||||
// begin, resume, or restart ICE negotiation.
|
||||
|
||||
async function handleNegotiationNeededEvent() {
|
||||
log("*** Negotiation needed");
|
||||
|
||||
try {
|
||||
log("---> Creating offer");
|
||||
const offer = await myPeerConnection.createOffer();
|
||||
|
||||
// If the connection hasn't yet achieved the "stable" state,
|
||||
// return to the caller. Another negotiationneeded event
|
||||
// will be fired when the state stabilizes.
|
||||
|
||||
if (myPeerConnection.signalingState != "stable") {
|
||||
log(" -- The connection isn't stable yet; postponing...")
|
||||
return;
|
||||
}
|
||||
|
||||
// Establish the offer as the local peer's current
|
||||
// description.
|
||||
|
||||
log("---> Setting local description to the offer");
|
||||
await myPeerConnection.setLocalDescription(offer);
|
||||
|
||||
// Send the offer to the remote peer.
|
||||
|
||||
log("---> Sending the offer to the remote peer");
|
||||
sendToServer({
|
||||
name: myUsername,
|
||||
target: targetUsername,
|
||||
type: "video-offer",
|
||||
sdp: myPeerConnection.localDescription
|
||||
});
|
||||
} catch(err) {
|
||||
log("*** The following error occurred while handling the negotiationneeded event:");
|
||||
reportError(err);
|
||||
};
|
||||
}
|
||||
|
||||
// Called by the WebRTC layer when events occur on the media tracks
|
||||
// on our WebRTC call. This includes when streams are added to and
|
||||
// removed from the call.
|
||||
//
|
||||
// track events include the following fields:
|
||||
//
|
||||
// RTCRtpReceiver receiver
|
||||
// MediaStreamTrack track
|
||||
// MediaStream[] streams
|
||||
// RTCRtpTransceiver transceiver
|
||||
//
|
||||
// In our case, we're just taking the first stream found and attaching
|
||||
// it to the <video> element for incoming media.
|
||||
|
||||
function handleTrackEvent(event) {
|
||||
log("*** Track event");
|
||||
document.getElementById("received_video").srcObject = event.streams[0];
|
||||
document.getElementById("hangup-button").disabled = false;
|
||||
}
|
||||
|
||||
// Handles |icecandidate| events by forwarding the specified
|
||||
// ICE candidate (created by our local ICE agent) to the other
|
||||
// peer through the signaling server.
|
||||
|
||||
function handleICECandidateEvent(event) {
|
||||
if (event.candidate) {
|
||||
log("*** Outgoing ICE candidate: " + event.candidate.candidate);
|
||||
|
||||
sendToServer({
|
||||
type: "new-ice-candidate",
|
||||
target: targetUsername,
|
||||
candidate: event.candidate
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Handle |iceconnectionstatechange| events. This will detect
|
||||
// when the ICE connection is closed, failed, or disconnected.
|
||||
//
|
||||
// This is called when the state of the ICE agent changes.
|
||||
|
||||
function handleICEConnectionStateChangeEvent(event) {
|
||||
log("*** ICE connection state changed to " + myPeerConnection.iceConnectionState);
|
||||
|
||||
switch(myPeerConnection.iceConnectionState) {
|
||||
case "closed":
|
||||
case "failed":
|
||||
case "disconnected":
|
||||
closeVideoCall();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Set up a |signalingstatechange| event handler. This will detect when
|
||||
// the signaling connection is closed.
|
||||
//
|
||||
// NOTE: This will actually move to the new RTCPeerConnectionState enum
|
||||
// returned in the property RTCPeerConnection.connectionState when
|
||||
// browsers catch up with the latest version of the specification!
|
||||
|
||||
function handleSignalingStateChangeEvent(event) {
|
||||
log("*** WebRTC signaling state changed to: " + myPeerConnection.signalingState);
|
||||
switch(myPeerConnection.signalingState) {
|
||||
case "closed":
|
||||
closeVideoCall();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle the |icegatheringstatechange| event. This lets us know what the
|
||||
// ICE engine is currently working on: "new" means no networking has happened
|
||||
// yet, "gathering" means the ICE engine is currently gathering candidates,
|
||||
// and "complete" means gathering is complete. Note that the engine can
|
||||
// alternate between "gathering" and "complete" repeatedly as needs and
|
||||
// circumstances change.
|
||||
//
|
||||
// We don't need to do anything when this happens, but we log it to the
|
||||
// console so you can see what's going on when playing with the sample.
|
||||
|
||||
function handleICEGatheringStateChangeEvent(event) {
|
||||
log("*** ICE gathering state changed to: " + myPeerConnection.iceGatheringState);
|
||||
}
|
||||
|
||||
// Given a message containing a list of usernames, this function
|
||||
// populates the user list box with those names, making each item
|
||||
// clickable to allow starting a video call.
|
||||
|
||||
function handleUserlistMsg(msg) {
|
||||
var i;
|
||||
var listElem = document.querySelector(".userlistbox");
|
||||
|
||||
// Remove all current list members. We could do this smarter,
|
||||
// by adding and updating users instead of rebuilding from
|
||||
// scratch but this will do for this sample.
|
||||
|
||||
while (listElem.firstChild) {
|
||||
listElem.removeChild(listElem.firstChild);
|
||||
}
|
||||
|
||||
// Add member names from the received list.
|
||||
|
||||
msg.users.forEach(function(username) {
|
||||
var item = document.createElement("li");
|
||||
item.appendChild(document.createTextNode(username));
|
||||
item.addEventListener("click", invite, false);
|
||||
|
||||
listElem.appendChild(item);
|
||||
});
|
||||
}
|
||||
|
||||
// Close the RTCPeerConnection and reset variables so that the user can
|
||||
// make or receive another call if they wish. This is called both
|
||||
// when the user hangs up, the other user hangs up, or if a connection
|
||||
// failure is detected.
|
||||
|
||||
function closeVideoCall() {
|
||||
var localVideo = document.getElementById("local_video");
|
||||
|
||||
log("Closing the call");
|
||||
|
||||
// Close the RTCPeerConnection
|
||||
|
||||
if (myPeerConnection) {
|
||||
log("--> Closing the peer connection");
|
||||
|
||||
// Disconnect all our event listeners; we don't want stray events
|
||||
// to interfere with the hangup while it's ongoing.
|
||||
|
||||
myPeerConnection.ontrack = null;
|
||||
myPeerConnection.onnicecandidate = null;
|
||||
myPeerConnection.oniceconnectionstatechange = null;
|
||||
myPeerConnection.onsignalingstatechange = null;
|
||||
myPeerConnection.onicegatheringstatechange = null;
|
||||
myPeerConnection.onnotificationneeded = null;
|
||||
|
||||
// Stop all transceivers on the connection
|
||||
|
||||
myPeerConnection.getTransceivers().forEach(transceiver => {
|
||||
transceiver.stop();
|
||||
});
|
||||
|
||||
// Stop the webcam preview as well by pausing the <video>
|
||||
// element, then stopping each of the getUserMedia() tracks
|
||||
// on it.
|
||||
|
||||
if (localVideo.srcObject) {
|
||||
localVideo.pause();
|
||||
localVideo.srcObject.getTracks().forEach(track => {
|
||||
track.stop();
|
||||
});
|
||||
}
|
||||
|
||||
// Close the peer connection
|
||||
|
||||
myPeerConnection.close();
|
||||
myPeerConnection = null;
|
||||
webcamStream = null;
|
||||
}
|
||||
|
||||
// Disable the hangup button
|
||||
|
||||
document.getElementById("hangup-button").disabled = true;
|
||||
targetUsername = null;
|
||||
}
|
||||
|
||||
// Handle the "hang-up" message, which is sent if the other peer
|
||||
// has hung up the call or otherwise disconnected.
|
||||
|
||||
function handleHangUpMsg(msg) {
|
||||
log("*** Received hang up notification from other peer");
|
||||
|
||||
closeVideoCall();
|
||||
}
|
||||
|
||||
// Hang up the call by closing our end of the connection, then
|
||||
// sending a "hang-up" message to the other peer (keep in mind that
|
||||
// the signaling is done on a different connection). This notifies
|
||||
// the other peer that the connection should be terminated and the UI
|
||||
// returned to the "no call in progress" state.
|
||||
|
||||
function hangUpCall() {
|
||||
closeVideoCall();
|
||||
|
||||
sendToServer({
|
||||
name: myUsername,
|
||||
target: targetUsername,
|
||||
type: "hang-up"
|
||||
});
|
||||
}
|
||||
|
||||
// Handle a click on an item in the user list by inviting the clicked
|
||||
// user to video chat. Note that we don't actually send a message to
|
||||
// the callee here -- calling RTCPeerConnection.addTrack() issues
|
||||
// a |notificationneeded| event, so we'll let our handler for that
|
||||
// make the offer.
|
||||
|
||||
async function invite(evt) {
|
||||
log("Starting to prepare an invitation");
|
||||
if (myPeerConnection) {
|
||||
alert("You can't start a call because you already have one open!");
|
||||
} else {
|
||||
var clickedUsername = evt.target.textContent;
|
||||
|
||||
// Don't allow users to call themselves, because weird.
|
||||
|
||||
if (clickedUsername === myUsername) {
|
||||
alert("I'm afraid I can't let you talk to yourself. That would be weird.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Record the username being called for future reference
|
||||
|
||||
targetUsername = clickedUsername;
|
||||
log("Inviting user " + targetUsername);
|
||||
|
||||
// Call createPeerConnection() to create the RTCPeerConnection.
|
||||
// When this returns, myPeerConnection is our RTCPeerConnection
|
||||
// and webcamStream is a stream coming from the camera. They are
|
||||
// not linked together in any way yet.
|
||||
|
||||
log("Setting up connection to invite user: " + targetUsername);
|
||||
createPeerConnection();
|
||||
|
||||
// Get access to the webcam stream and attach it to the
|
||||
// "preview" box (id "local_video").
|
||||
|
||||
try {
|
||||
webcamStream = await navigator.mediaDevices.getUserMedia(mediaConstraints);
|
||||
document.getElementById("local_video").srcObject = webcamStream;
|
||||
} catch(err) {
|
||||
handleGetUserMediaError(err);
|
||||
return;
|
||||
}
|
||||
|
||||
// Add the tracks from the stream to the RTCPeerConnection
|
||||
|
||||
try {
|
||||
webcamStream.getTracks().forEach(
|
||||
transceiver = track => myPeerConnection.addTransceiver(track, {streams: [webcamStream]})
|
||||
);
|
||||
} catch(err) {
|
||||
handleGetUserMediaError(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Accept an offer to video chat. We configure our local settings,
|
||||
// create our RTCPeerConnection, get and attach our local camera
|
||||
// stream, then create and send an answer to the caller.
|
||||
|
||||
async function handleVideoOfferMsg(msg) {
|
||||
targetUsername = msg.name;
|
||||
|
||||
// If we're not already connected, create an RTCPeerConnection
|
||||
// to be linked to the caller.
|
||||
|
||||
log("Received video chat offer from " + targetUsername);
|
||||
if (!myPeerConnection) {
|
||||
createPeerConnection();
|
||||
}
|
||||
|
||||
// We need to set the remote description to the received SDP offer
|
||||
// so that our local WebRTC layer knows how to talk to the caller.
|
||||
|
||||
var desc = new RTCSessionDescription(msg.sdp);
|
||||
|
||||
// If the connection isn't stable yet, wait for it...
|
||||
|
||||
if (myPeerConnection.signalingState != "stable") {
|
||||
log(" - But the signaling state isn't stable, so triggering rollback");
|
||||
|
||||
// Set the local and remove descriptions for rollback; don't proceed
|
||||
// until both return.
|
||||
await Promise.all([
|
||||
myPeerConnection.setLocalDescription({type: "rollback"}),
|
||||
myPeerConnection.setRemoteDescription(desc)
|
||||
]);
|
||||
return;
|
||||
} else {
|
||||
log (" - Setting remote description");
|
||||
await myPeerConnection.setRemoteDescription(desc);
|
||||
}
|
||||
|
||||
// Get the webcam stream if we don't already have it
|
||||
|
||||
if (!webcamStream) {
|
||||
try {
|
||||
webcamStream = await navigator.mediaDevices.getUserMedia(mediaConstraints);
|
||||
} catch(err) {
|
||||
handleGetUserMediaError(err);
|
||||
return;
|
||||
}
|
||||
|
||||
document.getElementById("local_video").srcObject = webcamStream;
|
||||
|
||||
// Add the camera stream to the RTCPeerConnection
|
||||
|
||||
try {
|
||||
webcamStream.getTracks().forEach(
|
||||
transceiver = track => myPeerConnection.addTransceiver(track, {streams: [webcamStream]})
|
||||
);
|
||||
} catch(err) {
|
||||
handleGetUserMediaError(err);
|
||||
}
|
||||
}
|
||||
|
||||
log("---> Creating and sending answer to caller");
|
||||
|
||||
await myPeerConnection.setLocalDescription(await myPeerConnection.createAnswer());
|
||||
|
||||
sendToServer({
|
||||
name: myUsername,
|
||||
target: targetUsername,
|
||||
type: "video-answer",
|
||||
sdp: myPeerConnection.localDescription
|
||||
});
|
||||
}
|
||||
|
||||
// Responds to the "video-answer" message sent to the caller
|
||||
// once the callee has decided to accept our request to talk.
|
||||
|
||||
async function handleVideoAnswerMsg(msg) {
|
||||
log("*** Call recipient has accepted our call");
|
||||
|
||||
// Configure the remote description, which is the SDP payload
|
||||
// in our "video-answer" message.
|
||||
|
||||
var desc = new RTCSessionDescription(msg.sdp);
|
||||
await myPeerConnection.setRemoteDescription(desc).catch(reportError);
|
||||
}
|
||||
|
||||
// A new ICE candidate has been received from the other peer. Call
|
||||
// RTCPeerConnection.addIceCandidate() to send it along to the
|
||||
// local ICE framework.
|
||||
|
||||
async function handleNewICECandidateMsg(msg) {
|
||||
var candidate = new RTCIceCandidate(msg.candidate);
|
||||
|
||||
log("*** Adding received ICE candidate: " + JSON.stringify(candidate));
|
||||
try {
|
||||
await myPeerConnection.addIceCandidate(candidate)
|
||||
} catch(err) {
|
||||
reportError(err);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle errors which occur when trying to access the local media
|
||||
// hardware; that is, exceptions thrown by getUserMedia(). The two most
|
||||
// likely scenarios are that the user has no camera and/or microphone
|
||||
// or that they declined to share their equipment when prompted. If
|
||||
// they simply opted not to share their media, that's not really an
|
||||
// error, so we won't present a message in that situation.
|
||||
|
||||
function handleGetUserMediaError(e) {
|
||||
log_error(e);
|
||||
switch(e.name) {
|
||||
case "NotFoundError":
|
||||
alert("Unable to open your call because no camera and/or microphone" +
|
||||
"were found.");
|
||||
break;
|
||||
case "SecurityError":
|
||||
case "PermissionDeniedError":
|
||||
// Do nothing; this is the same as the user canceling the call.
|
||||
break;
|
||||
default:
|
||||
alert("Error opening your camera and/or microphone: " + e.message);
|
||||
break;
|
||||
}
|
||||
|
||||
// Make sure we shut down our end of the RTCPeerConnection so we're
|
||||
// ready to try again.
|
||||
|
||||
closeVideoCall();
|
||||
}
|
||||
|
||||
// Handles reporting errors. Currently, we just dump stuff to console but
|
||||
// in a real-world application, an appropriate (and user-friendly)
|
||||
// error message should be displayed.
|
||||
|
||||
function reportError(errMessage) {
|
||||
log_error(`Error ${errMessage.name}: ${errMessage.message}`);
|
||||
}
|
||||
195
clientV/scripts/rtc3.js
Normal file
@@ -0,0 +1,195 @@
|
||||
var name;
|
||||
var connections = {};
|
||||
|
||||
const configuration = {
|
||||
iceServers: [{ urls: "stun:stun.l.google.com:19302" }]
|
||||
};
|
||||
|
||||
const offerOptions = {
|
||||
offerToReceiveAudio: 1,
|
||||
offerToReceiveVideo: 1
|
||||
};
|
||||
|
||||
function handleLogin(success) {
|
||||
if (success === false) {
|
||||
alert("try a different username");
|
||||
} else {
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
async function createPeerConnection(target) {
|
||||
console.log("CREATED PEER CONNECTION");
|
||||
var connection = new RTCPeerConnection(configuration);
|
||||
connections[target] = connection;
|
||||
|
||||
connection.onicecandidate = function(event) {
|
||||
if (event.candidate) {
|
||||
send({
|
||||
type: "candidate",
|
||||
name: name,
|
||||
target: target,
|
||||
candidate: event.candidate
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
connection.onnegotiationneeded = function() { handleNegotiationNeededEvent(target); };
|
||||
connection.ontrack = function(event) { handleTrackEvent(event); }
|
||||
connection.onsignalingstatechange = function() { handleSignalingStateChangeEvent(connection); }
|
||||
connection.oniceconnectionstatechange = function() { handleICEConnectionStateChangeEvent(connection); }
|
||||
connection.onicegatheringstatechange = function() { handleICEGatheringStateChangeEvent(connection); }
|
||||
|
||||
//window.setInterval(getConnectionStats, 1000);
|
||||
}
|
||||
|
||||
function handleICEConnectionStateChangeEvent(connection) {
|
||||
console.log("ICE CONNECTION CHANGE "+connection.iceConnectionState);
|
||||
}
|
||||
|
||||
function handleICEGatheringStateChangeEvent(connection) {
|
||||
console.log("ICE GATHERING CHANGE "+connection.iceGatheringState);
|
||||
}
|
||||
|
||||
async function makeOffer(target) {
|
||||
createPeerConnection(target);
|
||||
|
||||
var connection = connections[target];
|
||||
|
||||
var offer = await connection.createOffer();
|
||||
send({
|
||||
type: "offer",
|
||||
name: name,
|
||||
target: target,
|
||||
offer: offer
|
||||
});
|
||||
await connection.setLocalDescription(offer);
|
||||
|
||||
}
|
||||
|
||||
async function handleOffer(offer, target) {
|
||||
console.log("GOT OFFER FROM "+target);
|
||||
await createPeerConnection(target);
|
||||
|
||||
var connection = connections[target];
|
||||
|
||||
await connection.setRemoteDescription(new RTCSessionDescription(offer));
|
||||
|
||||
if (stream) {
|
||||
console.log("STREAM DETECTED");
|
||||
stream.getTracks().forEach((track) => {
|
||||
console.log("ADDED TRACK");
|
||||
connection.addTrack(track, stream);
|
||||
});
|
||||
}
|
||||
|
||||
//create an answer to an offer
|
||||
var answer = await connection.createAnswer();
|
||||
|
||||
await connection.setLocalDescription(answer);
|
||||
|
||||
send({
|
||||
type: "answer",
|
||||
name: name,
|
||||
target: target,
|
||||
answer: answer
|
||||
});
|
||||
};
|
||||
|
||||
async function handleAnswer(answer, target) {
|
||||
console.log("GOT ANSWER FROM "+target);
|
||||
var connection = connections[target];
|
||||
await connection.setRemoteDescription(new RTCSessionDescription(answer));
|
||||
};
|
||||
|
||||
async function handleCandidate(candidate, target) {
|
||||
console.log("GOT CANDIDATE FROM "+target);
|
||||
var connection = connections[target];
|
||||
await connection.addIceCandidate(new RTCIceCandidate(candidate));
|
||||
};
|
||||
|
||||
async function handleNegotiationNeededEvent(target) {
|
||||
console.log("NEGOTIATION NEEDED FROM "+target);
|
||||
|
||||
var connection = connections[target];
|
||||
var offer = await connection.createOffer(offerOptions);
|
||||
|
||||
await connection.setLocalDescription(offer);
|
||||
|
||||
send({
|
||||
type: "video-offer",
|
||||
name: name,
|
||||
target: target,
|
||||
sdp: connection.localDescription
|
||||
});
|
||||
}
|
||||
|
||||
function handleLeave() {
|
||||
connections.foreach( (connection) => {
|
||||
connection.close();
|
||||
connection.onicecandidate = null;
|
||||
//connection.onaddstream = null;
|
||||
connection = null;
|
||||
});
|
||||
connections = {};
|
||||
|
||||
remoteVideo.src = null;
|
||||
};
|
||||
|
||||
function handleUserlist(list) {
|
||||
console.log("GOT USER LIST");
|
||||
|
||||
}
|
||||
|
||||
async function handleVideoOffer(sdp, target) {
|
||||
console.log("GOT VIDEO OFFER FROM "+target);
|
||||
await createPeerConnection(target);
|
||||
var connection = connections[target];
|
||||
await connection.setRemoteDescription(new RTCSessionDescription(sdp))
|
||||
|
||||
if (stream) {
|
||||
console.log("STREAM DETECTED");
|
||||
stream.getTracks().forEach((track) => {
|
||||
console.log("ADDED TRACK");
|
||||
connection.addTrack(track, stream);
|
||||
});
|
||||
}
|
||||
|
||||
var answer = await connection.createAnswer();
|
||||
await connection.setLocalDescription(answer);
|
||||
|
||||
send({
|
||||
type: "video-answer",
|
||||
name: name,
|
||||
target: target,
|
||||
sdp: answer
|
||||
});
|
||||
}
|
||||
|
||||
async function handleVideoAnswer(sdp, target) {
|
||||
console.log("GOT VIDEO ANSWER FROM "+target);
|
||||
|
||||
var connection = connections[target];
|
||||
await connection.setRemoteDescription(new RTCSessionDescription(sdp));
|
||||
}
|
||||
|
||||
async function handleSignalingStateChangeEvent(connection) {
|
||||
console.log("STATE CHANGED TO : " + connection.signalingState);
|
||||
switch(connection.signalingState) {
|
||||
case "closed":
|
||||
await connection.close();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function handleTrackEvent(event) {
|
||||
console.log("GOT TRACK");
|
||||
remoteVideo.srcObject = event.streams[0];
|
||||
//document.getElementById("hangup-button").disabled = false;
|
||||
}
|
||||
|
||||
function getConnectionStats() {
|
||||
for ([connection, target] in connections) {
|
||||
console.log("[" + target + "] " + connection.connectionState);
|
||||
}
|
||||
}
|
||||
203
clientV/scripts/rtc_old.js
Normal file
@@ -0,0 +1,203 @@
|
||||
//our username
|
||||
var name;
|
||||
var connectedUser;
|
||||
|
||||
//connecting to our signaling server
|
||||
var conn = new WebSocket('ws://localhost:9090');
|
||||
|
||||
conn.onopen = function () {
|
||||
console.log("Connected to the signaling server");
|
||||
};
|
||||
|
||||
//when we got a message from a signaling server
|
||||
conn.onmessage = function (msg) {
|
||||
console.log("Got message", msg.data);
|
||||
|
||||
var data = JSON.parse(msg.data);
|
||||
|
||||
switch(data.type) {
|
||||
case "login":
|
||||
handleLogin(data.success);
|
||||
break;
|
||||
//when somebody wants to call us
|
||||
case "offer":
|
||||
handleOffer(data.offer, data.name);
|
||||
break;
|
||||
case "answer":
|
||||
handleAnswer(data.answer);
|
||||
break;
|
||||
//when a remote peer sends an ice candidate to us
|
||||
case "candidate":
|
||||
handleCandidate(data.candidate);
|
||||
break;
|
||||
case "leave":
|
||||
handleLeave();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
conn.onerror = function (err) {
|
||||
console.log("Got error", err);
|
||||
};
|
||||
|
||||
//alias for sending JSON encoded messages
|
||||
function send(message) {
|
||||
//attach the other peer username to our messages
|
||||
if (connectedUser) {
|
||||
message.name = connectedUser;
|
||||
}
|
||||
|
||||
conn.send(JSON.stringify(message));
|
||||
};
|
||||
|
||||
//******
|
||||
//UI selectors block
|
||||
//******
|
||||
|
||||
var loginPage = document.querySelector('#loginPage');
|
||||
var usernameInput = document.querySelector('#usernameInput');
|
||||
var loginBtn = document.querySelector('#loginBtn');
|
||||
|
||||
var callPage = document.querySelector('#callPage');
|
||||
var callToUsernameInput = document.querySelector('#callToUsernameInput');
|
||||
var callBtn = document.querySelector('#callBtn');
|
||||
|
||||
var hangUpBtn = document.querySelector('#hangUpBtn');
|
||||
|
||||
var localVideo = document.querySelector('#localVideo');
|
||||
var remoteVideo = document.querySelector('#remoteVideo');
|
||||
|
||||
var yourConn;
|
||||
var stream;
|
||||
|
||||
callPage.style.display = "none";
|
||||
|
||||
// Login when the user clicks the button
|
||||
loginBtn.addEventListener("click", function (event) {
|
||||
name = usernameInput.value;
|
||||
|
||||
if (name.length > 0) {
|
||||
send({
|
||||
type: "login",
|
||||
name: name
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
function handleLogin(success) {
|
||||
if (success === false) {
|
||||
alert("Ooops...try a different username");
|
||||
} else {
|
||||
loginPage.style.display = "none";
|
||||
callPage.style.display = "block";
|
||||
|
||||
//**********************
|
||||
//Starting a peer connection
|
||||
//**********************
|
||||
|
||||
|
||||
//stream = myStream;
|
||||
|
||||
//displaying local video stream on the page
|
||||
//localVideo.src = window.URL.createObjectURL(stream);
|
||||
|
||||
//using Google public stun server
|
||||
var configuration = {
|
||||
"iceServers": [{ "url": "stun:stun2.1.google.com:19302" }]
|
||||
};
|
||||
|
||||
yourConn = new RTCPeerConnection(configuration);
|
||||
|
||||
// setup stream listening
|
||||
//yourConn.addStream(stream);
|
||||
|
||||
//when a remote user adds stream to the peer connection, we display it
|
||||
yourConn.onaddstream = function (e) {
|
||||
//remoteVideo.src = window.URL.createObjectURL(e.stream);
|
||||
};
|
||||
|
||||
// Setup ice handling
|
||||
yourConn.onicecandidate = function (event) {
|
||||
if (event.candidate) {
|
||||
send({
|
||||
type: "candidate",
|
||||
candidate: event.candidate
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
//initiating a call
|
||||
callBtn.addEventListener("click", function () {
|
||||
var callToUsername = callToUsernameInput.value;
|
||||
|
||||
if (callToUsername.length > 0) {
|
||||
|
||||
connectedUser = callToUsername;
|
||||
|
||||
// create an offer
|
||||
yourConn.createOffer(function (offer) {
|
||||
send({
|
||||
type: "offer",
|
||||
offer: offer
|
||||
});
|
||||
|
||||
yourConn.setLocalDescription(offer);
|
||||
}, function (error) {
|
||||
alert("Error when creating an offer");
|
||||
});
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
//when somebody sends us an offer
|
||||
function handleOffer(offer, name) {
|
||||
connectedUser = name;
|
||||
yourConn.setRemoteDescription(new RTCSessionDescription(offer));
|
||||
|
||||
//create an answer to an offer
|
||||
yourConn.createAnswer(function (answer) {
|
||||
yourConn.setLocalDescription(answer);
|
||||
|
||||
send({
|
||||
type: "answer",
|
||||
answer: answer
|
||||
});
|
||||
|
||||
}, function (error) {
|
||||
alert("Error when creating an answer");
|
||||
});
|
||||
};
|
||||
|
||||
//when we got an answer from a remote user
|
||||
function handleAnswer(answer) {
|
||||
yourConn.setRemoteDescription(new RTCSessionDescription(answer));
|
||||
};
|
||||
|
||||
//when we got an ice candidate from a remote user
|
||||
function handleCandidate(candidate) {
|
||||
yourConn.addIceCandidate(new RTCIceCandidate(candidate));
|
||||
};
|
||||
|
||||
//hang up
|
||||
hangUpBtn.addEventListener("click", function () {
|
||||
|
||||
send({
|
||||
type: "leave"
|
||||
});
|
||||
|
||||
handleLeave();
|
||||
});
|
||||
|
||||
function handleLeave() {
|
||||
connectedUser = null;
|
||||
remoteVideo.src = null;
|
||||
|
||||
yourConn.close();
|
||||
yourConn.onicecandidate = null;
|
||||
yourConn.onaddstream = null;
|
||||
};
|
||||
64
clientV/scripts/script.js
Normal file
@@ -0,0 +1,64 @@
|
||||
var loginInput = document.querySelector('#loginInput');
|
||||
var loginBt = document.querySelector('#loginBt');
|
||||
|
||||
var callInput = document.querySelector('#callInput');
|
||||
var callBt = document.querySelector('#callBt');
|
||||
|
||||
var disconnectBt = document.querySelector('#disconnectBt');
|
||||
|
||||
const remoteVideo = document.querySelector('#video');
|
||||
|
||||
var videoInput = document.querySelector('#videoInput');
|
||||
|
||||
var stream;
|
||||
|
||||
loginBt.addEventListener("click", function (event) {
|
||||
name = loginInput.value;
|
||||
|
||||
if (name.length > 0) {
|
||||
send({
|
||||
type: "login",
|
||||
name: name
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
callBt.addEventListener("click", function () {
|
||||
var callToUsername = callInput.value;
|
||||
|
||||
if (callToUsername.length > 0) {
|
||||
makeOffer(callToUsername);
|
||||
}
|
||||
});
|
||||
|
||||
disconnectBt.addEventListener("click", function () {
|
||||
send({
|
||||
type: "leave",
|
||||
name: name
|
||||
});
|
||||
handleLeave();
|
||||
});
|
||||
|
||||
videoInput.addEventListener("change", function (event) {
|
||||
var file = this.files[0]
|
||||
var type = file.type
|
||||
var videoNode = remoteVideo2
|
||||
var canPlay = videoNode.canPlayType(type)
|
||||
if (canPlay === '') canPlay = 'no'
|
||||
var message = 'Can play type "' + type + '": ' + canPlay
|
||||
var isError = canPlay === 'no'
|
||||
//displayMessage(message, isError)
|
||||
|
||||
if (isError) {
|
||||
return
|
||||
}
|
||||
|
||||
var fileURL = URL.createObjectURL(file)
|
||||
videoNode.src = fileURL
|
||||
});
|
||||
|
||||
remoteVideo.onplay = function() {
|
||||
console.log("ADD STREAM");
|
||||
if(remoteVideo.mozCaptureStream()) stream = remoteVideo.mozCaptureStream();
|
||||
else stream = remoteVideo.captureStream();
|
||||
}
|
||||
60
clientV/scripts/signal.js
Normal file
@@ -0,0 +1,60 @@
|
||||
//connecting to our signaling server
|
||||
var conn = new WebSocket('ws://localhost:9090');
|
||||
|
||||
conn.onopen = function () {
|
||||
console.log("Connected to the signaling server");
|
||||
};
|
||||
|
||||
//when we got a message from a signaling server
|
||||
conn.onmessage = function (msg) {
|
||||
console.log("Got message", msg.data);
|
||||
|
||||
var data = JSON.parse(msg.data);
|
||||
|
||||
switch(data.type) {
|
||||
case "login":
|
||||
handleLogin(data.success);
|
||||
break;
|
||||
|
||||
case "offer":
|
||||
handleOffer(data.offer, data.name);
|
||||
break;
|
||||
|
||||
case "answer":
|
||||
handleAnswer(data.answer, data.name);
|
||||
break;
|
||||
|
||||
case "candidate":
|
||||
handleCandidate(data.candidate, data.name);
|
||||
break;
|
||||
|
||||
case "userlist":
|
||||
handleUserlist(data);
|
||||
break;
|
||||
|
||||
case "leave":
|
||||
handleLeave();
|
||||
break;
|
||||
|
||||
case "video-offer":
|
||||
handleVideoOffer(data.sdp, data.name);
|
||||
break;
|
||||
|
||||
case "video-answer":
|
||||
handleVideoAnswer(data.sdp, data.name);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
conn.onerror = function (err) {
|
||||
console.log("Got error", err);
|
||||
};
|
||||
|
||||
//alias for sending JSON encoded messages
|
||||
function send(message) {
|
||||
console.log("Sended message", message);
|
||||
conn.send(JSON.stringify(message));
|
||||
};
|
||||
0
clientV/style.css
Normal file
21
server/.gitignore
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
/dist
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Log files
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Editor directories and files
|
||||
.idea
|
||||
.vscode
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
3
server/config.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"port" : 8080
|
||||
}
|
||||
602
server/package-lock.json
generated
Normal file
@@ -0,0 +1,602 @@
|
||||
{
|
||||
"name": "lilstreamy-server",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
"accepts": {
|
||||
"version": "1.3.7",
|
||||
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz",
|
||||
"integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==",
|
||||
"requires": {
|
||||
"mime-types": "~2.1.24",
|
||||
"negotiator": "0.6.2"
|
||||
}
|
||||
},
|
||||
"argparse": {
|
||||
"version": "1.0.10",
|
||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
|
||||
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
|
||||
"requires": {
|
||||
"sprintf-js": "~1.0.2"
|
||||
}
|
||||
},
|
||||
"array-flatten": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
|
||||
"integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
|
||||
},
|
||||
"base64-js": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz",
|
||||
"integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g=="
|
||||
},
|
||||
"body-parser": {
|
||||
"version": "1.19.0",
|
||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz",
|
||||
"integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==",
|
||||
"requires": {
|
||||
"bytes": "3.1.0",
|
||||
"content-type": "~1.0.4",
|
||||
"debug": "2.6.9",
|
||||
"depd": "~1.1.2",
|
||||
"http-errors": "1.7.2",
|
||||
"iconv-lite": "0.4.24",
|
||||
"on-finished": "~2.3.0",
|
||||
"qs": "6.7.0",
|
||||
"raw-body": "2.4.0",
|
||||
"type-is": "~1.6.17"
|
||||
}
|
||||
},
|
||||
"buffer": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.5.0.tgz",
|
||||
"integrity": "sha512-9FTEDjLjwoAkEwyMGDjYJQN2gfRgOKBKRfiglhvibGbpeeU/pQn1bJxQqm32OD/AIeEuHxU9roxXxg34Byp/Ww==",
|
||||
"requires": {
|
||||
"base64-js": "^1.0.2",
|
||||
"ieee754": "^1.1.4"
|
||||
}
|
||||
},
|
||||
"bytes": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz",
|
||||
"integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg=="
|
||||
},
|
||||
"circular-json": {
|
||||
"version": "0.5.9",
|
||||
"resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.5.9.tgz",
|
||||
"integrity": "sha512-4ivwqHpIFJZBuhN3g/pEcdbnGUywkBblloGbkglyloVjjR3uT6tieI89MVOfbP2tHX5sgb01FuLgAOzebNlJNQ=="
|
||||
},
|
||||
"content-disposition": {
|
||||
"version": "0.5.3",
|
||||
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz",
|
||||
"integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==",
|
||||
"requires": {
|
||||
"safe-buffer": "5.1.2"
|
||||
}
|
||||
},
|
||||
"content-type": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
|
||||
"integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA=="
|
||||
},
|
||||
"cookie": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz",
|
||||
"integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg=="
|
||||
},
|
||||
"cookie-signature": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
|
||||
"integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw="
|
||||
},
|
||||
"core-util-is": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
|
||||
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
|
||||
},
|
||||
"cors": {
|
||||
"version": "2.8.5",
|
||||
"resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
|
||||
"integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
|
||||
"requires": {
|
||||
"object-assign": "^4",
|
||||
"vary": "^1"
|
||||
}
|
||||
},
|
||||
"crc": {
|
||||
"version": "3.8.0",
|
||||
"resolved": "https://registry.npmjs.org/crc/-/crc-3.8.0.tgz",
|
||||
"integrity": "sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ==",
|
||||
"requires": {
|
||||
"buffer": "^5.1.0"
|
||||
}
|
||||
},
|
||||
"date-format": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/date-format/-/date-format-1.2.0.tgz",
|
||||
"integrity": "sha1-YV6CjiM90aubua4JUODOzPpuytg="
|
||||
},
|
||||
"debug": {
|
||||
"version": "2.6.9",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
||||
"requires": {
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
},
|
||||
"depd": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
|
||||
"integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak="
|
||||
},
|
||||
"destroy": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
|
||||
"integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA="
|
||||
},
|
||||
"ee-first": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
||||
"integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
|
||||
},
|
||||
"encodeurl": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
|
||||
"integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k="
|
||||
},
|
||||
"escape-html": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
|
||||
"integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg="
|
||||
},
|
||||
"esprima": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
|
||||
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="
|
||||
},
|
||||
"etag": {
|
||||
"version": "1.8.1",
|
||||
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
|
||||
"integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc="
|
||||
},
|
||||
"express": {
|
||||
"version": "4.17.1",
|
||||
"resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz",
|
||||
"integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==",
|
||||
"requires": {
|
||||
"accepts": "~1.3.7",
|
||||
"array-flatten": "1.1.1",
|
||||
"body-parser": "1.19.0",
|
||||
"content-disposition": "0.5.3",
|
||||
"content-type": "~1.0.4",
|
||||
"cookie": "0.4.0",
|
||||
"cookie-signature": "1.0.6",
|
||||
"debug": "2.6.9",
|
||||
"depd": "~1.1.2",
|
||||
"encodeurl": "~1.0.2",
|
||||
"escape-html": "~1.0.3",
|
||||
"etag": "~1.8.1",
|
||||
"finalhandler": "~1.1.2",
|
||||
"fresh": "0.5.2",
|
||||
"merge-descriptors": "1.0.1",
|
||||
"methods": "~1.1.2",
|
||||
"on-finished": "~2.3.0",
|
||||
"parseurl": "~1.3.3",
|
||||
"path-to-regexp": "0.1.7",
|
||||
"proxy-addr": "~2.0.5",
|
||||
"qs": "6.7.0",
|
||||
"range-parser": "~1.2.1",
|
||||
"safe-buffer": "5.1.2",
|
||||
"send": "0.17.1",
|
||||
"serve-static": "1.14.1",
|
||||
"setprototypeof": "1.1.1",
|
||||
"statuses": "~1.5.0",
|
||||
"type-is": "~1.6.18",
|
||||
"utils-merge": "1.0.1",
|
||||
"vary": "~1.1.2"
|
||||
}
|
||||
},
|
||||
"finalhandler": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz",
|
||||
"integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==",
|
||||
"requires": {
|
||||
"debug": "2.6.9",
|
||||
"encodeurl": "~1.0.2",
|
||||
"escape-html": "~1.0.3",
|
||||
"on-finished": "~2.3.0",
|
||||
"parseurl": "~1.3.3",
|
||||
"statuses": "~1.5.0",
|
||||
"unpipe": "~1.0.0"
|
||||
}
|
||||
},
|
||||
"forwarded": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz",
|
||||
"integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ="
|
||||
},
|
||||
"fresh": {
|
||||
"version": "0.5.2",
|
||||
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
|
||||
"integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac="
|
||||
},
|
||||
"http-errors": {
|
||||
"version": "1.7.2",
|
||||
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz",
|
||||
"integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==",
|
||||
"requires": {
|
||||
"depd": "~1.1.2",
|
||||
"inherits": "2.0.3",
|
||||
"setprototypeof": "1.1.1",
|
||||
"statuses": ">= 1.5.0 < 2",
|
||||
"toidentifier": "1.0.0"
|
||||
}
|
||||
},
|
||||
"iconv-lite": {
|
||||
"version": "0.4.24",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
||||
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
|
||||
"requires": {
|
||||
"safer-buffer": ">= 2.1.2 < 3"
|
||||
}
|
||||
},
|
||||
"ieee754": {
|
||||
"version": "1.1.13",
|
||||
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz",
|
||||
"integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg=="
|
||||
},
|
||||
"inherits": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
|
||||
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
|
||||
},
|
||||
"ipaddr.js": {
|
||||
"version": "1.9.1",
|
||||
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
|
||||
"integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="
|
||||
},
|
||||
"isarray": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
|
||||
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
|
||||
},
|
||||
"js-yaml": {
|
||||
"version": "3.12.2",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.12.2.tgz",
|
||||
"integrity": "sha512-QHn/Lh/7HhZ/Twc7vJYQTkjuCa0kaCcDcjK5Zlk2rvnUpy7DxMJ23+Jc2dcyvltwQVg1nygAVlB2oRDFHoRS5Q==",
|
||||
"requires": {
|
||||
"argparse": "^1.0.7",
|
||||
"esprima": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"log4js": {
|
||||
"version": "3.0.6",
|
||||
"resolved": "https://registry.npmjs.org/log4js/-/log4js-3.0.6.tgz",
|
||||
"integrity": "sha512-ezXZk6oPJCWL483zj64pNkMuY/NcRX5MPiB0zE6tjZM137aeusrOnW1ecxgF9cmwMWkBMhjteQxBPoZBh9FDxQ==",
|
||||
"requires": {
|
||||
"circular-json": "^0.5.5",
|
||||
"date-format": "^1.2.0",
|
||||
"debug": "^3.1.0",
|
||||
"rfdc": "^1.1.2",
|
||||
"streamroller": "0.7.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": {
|
||||
"version": "3.2.6",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
|
||||
"integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
|
||||
"requires": {
|
||||
"ms": "^2.1.1"
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"media-typer": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
|
||||
"integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g="
|
||||
},
|
||||
"merge-descriptors": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
|
||||
"integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E="
|
||||
},
|
||||
"methods": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
|
||||
"integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4="
|
||||
},
|
||||
"mime": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
|
||||
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="
|
||||
},
|
||||
"mime-db": {
|
||||
"version": "1.43.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.43.0.tgz",
|
||||
"integrity": "sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ=="
|
||||
},
|
||||
"mime-types": {
|
||||
"version": "2.1.26",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.26.tgz",
|
||||
"integrity": "sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ==",
|
||||
"requires": {
|
||||
"mime-db": "1.43.0"
|
||||
}
|
||||
},
|
||||
"minimist": {
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
|
||||
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
|
||||
},
|
||||
"mkdirp": {
|
||||
"version": "0.5.4",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.4.tgz",
|
||||
"integrity": "sha512-iG9AK/dJLtJ0XNgTuDbSyNS3zECqDlAhnQW4CsNxBG3LQJBbHmRX1egw39DmtOdCAqY+dKXV+sgPgilNWUKMVw==",
|
||||
"requires": {
|
||||
"minimist": "^1.2.5"
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
|
||||
},
|
||||
"negotiator": {
|
||||
"version": "0.6.2",
|
||||
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz",
|
||||
"integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw=="
|
||||
},
|
||||
"node-turn": {
|
||||
"version": "0.0.4",
|
||||
"resolved": "https://registry.npmjs.org/node-turn/-/node-turn-0.0.4.tgz",
|
||||
"integrity": "sha512-ZmMbEt9146syRhBqrpgOTTdI5xn5seXpB6+/hkRLQV86ZHWuvF3TNHfN3TTFs3pf/ceY9rbdBs+OnaFQqqGU7g==",
|
||||
"requires": {
|
||||
"crc": "^3.8.0",
|
||||
"js-yaml": "~3.12.0",
|
||||
"log4js": "~3.0.5"
|
||||
}
|
||||
},
|
||||
"object-assign": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
|
||||
},
|
||||
"on-finished": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
|
||||
"integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=",
|
||||
"requires": {
|
||||
"ee-first": "1.1.1"
|
||||
}
|
||||
},
|
||||
"parseurl": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
|
||||
"integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="
|
||||
},
|
||||
"path-to-regexp": {
|
||||
"version": "0.1.7",
|
||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
|
||||
"integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w="
|
||||
},
|
||||
"process-nextick-args": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
|
||||
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
|
||||
},
|
||||
"proxy-addr": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz",
|
||||
"integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==",
|
||||
"requires": {
|
||||
"forwarded": "~0.1.2",
|
||||
"ipaddr.js": "1.9.1"
|
||||
}
|
||||
},
|
||||
"qs": {
|
||||
"version": "6.7.0",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz",
|
||||
"integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ=="
|
||||
},
|
||||
"range-parser": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
|
||||
"integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="
|
||||
},
|
||||
"raw-body": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz",
|
||||
"integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==",
|
||||
"requires": {
|
||||
"bytes": "3.1.0",
|
||||
"http-errors": "1.7.2",
|
||||
"iconv-lite": "0.4.24",
|
||||
"unpipe": "1.0.0"
|
||||
}
|
||||
},
|
||||
"readable-stream": {
|
||||
"version": "2.3.7",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
|
||||
"integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
|
||||
"requires": {
|
||||
"core-util-is": "~1.0.0",
|
||||
"inherits": "~2.0.3",
|
||||
"isarray": "~1.0.0",
|
||||
"process-nextick-args": "~2.0.0",
|
||||
"safe-buffer": "~5.1.1",
|
||||
"string_decoder": "~1.1.1",
|
||||
"util-deprecate": "~1.0.1"
|
||||
}
|
||||
},
|
||||
"rfdc": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.1.4.tgz",
|
||||
"integrity": "sha512-5C9HXdzK8EAqN7JDif30jqsBzavB7wLpaubisuQIGHWf2gUXSpzy6ArX/+Da8RjFpagWsCn+pIgxTMAmKw9Zug=="
|
||||
},
|
||||
"rtcpeerconnection-shim": {
|
||||
"version": "1.2.15",
|
||||
"resolved": "https://registry.npmjs.org/rtcpeerconnection-shim/-/rtcpeerconnection-shim-1.2.15.tgz",
|
||||
"integrity": "sha512-C6DxhXt7bssQ1nHb154lqeL0SXz5Dx4RczXZu2Aa/L1NJFnEVDxFwCBo3fqtuljhHIGceg5JKBV4XJ0gW5JKyw==",
|
||||
"requires": {
|
||||
"sdp": "^2.6.0"
|
||||
}
|
||||
},
|
||||
"safe-buffer": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
|
||||
},
|
||||
"safer-buffer": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
|
||||
},
|
||||
"sdp": {
|
||||
"version": "2.12.0",
|
||||
"resolved": "https://registry.npmjs.org/sdp/-/sdp-2.12.0.tgz",
|
||||
"integrity": "sha512-jhXqQAQVM+8Xj5EjJGVweuEzgtGWb3tmEEpl3CLP3cStInSbVHSg0QWOGQzNq8pSID4JkpeV2mPqlMDLrm0/Vw=="
|
||||
},
|
||||
"send": {
|
||||
"version": "0.17.1",
|
||||
"resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz",
|
||||
"integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==",
|
||||
"requires": {
|
||||
"debug": "2.6.9",
|
||||
"depd": "~1.1.2",
|
||||
"destroy": "~1.0.4",
|
||||
"encodeurl": "~1.0.2",
|
||||
"escape-html": "~1.0.3",
|
||||
"etag": "~1.8.1",
|
||||
"fresh": "0.5.2",
|
||||
"http-errors": "~1.7.2",
|
||||
"mime": "1.6.0",
|
||||
"ms": "2.1.1",
|
||||
"on-finished": "~2.3.0",
|
||||
"range-parser": "~1.2.1",
|
||||
"statuses": "~1.5.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"ms": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
|
||||
"integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"serve-static": {
|
||||
"version": "1.14.1",
|
||||
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz",
|
||||
"integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==",
|
||||
"requires": {
|
||||
"encodeurl": "~1.0.2",
|
||||
"escape-html": "~1.0.3",
|
||||
"parseurl": "~1.3.3",
|
||||
"send": "0.17.1"
|
||||
}
|
||||
},
|
||||
"setprototypeof": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz",
|
||||
"integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw=="
|
||||
},
|
||||
"sprintf-js": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
|
||||
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw="
|
||||
},
|
||||
"statuses": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
|
||||
"integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow="
|
||||
},
|
||||
"streamroller": {
|
||||
"version": "0.7.0",
|
||||
"resolved": "https://registry.npmjs.org/streamroller/-/streamroller-0.7.0.tgz",
|
||||
"integrity": "sha512-WREzfy0r0zUqp3lGO096wRuUp7ho1X6uo/7DJfTlEi0Iv/4gT7YHqXDjKC2ioVGBZtE8QzsQD9nx1nIuoZ57jQ==",
|
||||
"requires": {
|
||||
"date-format": "^1.2.0",
|
||||
"debug": "^3.1.0",
|
||||
"mkdirp": "^0.5.1",
|
||||
"readable-stream": "^2.3.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": {
|
||||
"version": "3.2.6",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
|
||||
"integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
|
||||
"requires": {
|
||||
"ms": "^2.1.1"
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"string_decoder": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
|
||||
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
|
||||
"requires": {
|
||||
"safe-buffer": "~5.1.0"
|
||||
}
|
||||
},
|
||||
"toidentifier": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz",
|
||||
"integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw=="
|
||||
},
|
||||
"type-is": {
|
||||
"version": "1.6.18",
|
||||
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
|
||||
"integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
|
||||
"requires": {
|
||||
"media-typer": "0.3.0",
|
||||
"mime-types": "~2.1.24"
|
||||
}
|
||||
},
|
||||
"unpipe": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
|
||||
"integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw="
|
||||
},
|
||||
"util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
|
||||
},
|
||||
"utils-merge": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
|
||||
"integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM="
|
||||
},
|
||||
"vary": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
||||
"integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw="
|
||||
},
|
||||
"webrtc-adapter": {
|
||||
"version": "7.5.1",
|
||||
"resolved": "https://registry.npmjs.org/webrtc-adapter/-/webrtc-adapter-7.5.1.tgz",
|
||||
"integrity": "sha512-R5LkIR/APjODkstSXFOztOmINXQ0nqIGfUoKTtCzjyiDXHNgwhkqZ9vi8UzGyjfUBibuZ0ZzVyV10qtuLGW3CQ==",
|
||||
"requires": {
|
||||
"rtcpeerconnection-shim": "^1.2.15",
|
||||
"sdp": "^2.12.0"
|
||||
}
|
||||
},
|
||||
"ws": {
|
||||
"version": "7.2.3",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-7.2.3.tgz",
|
||||
"integrity": "sha512-HTDl9G9hbkNDk98naoR/cHDws7+EyYMOdL1BmjsZXRUjf7d+MficC4B7HLUPlSiho0vg+CWKrGIt/VJBd1xunQ=="
|
||||
}
|
||||
}
|
||||
}
|
||||
19
server/package.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "lilstreamy-server",
|
||||
"version": "1.0.0",
|
||||
"description": "A simple webRTC server",
|
||||
"main": "src/server.js",
|
||||
"scripts": {
|
||||
"start": "node src/server.js",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"cors": "^2.8.5",
|
||||
"express": "^4.17.1",
|
||||
"node-turn": "0.0.4",
|
||||
"webrtc-adapter": "^7.5.1",
|
||||
"ws": "^7.2.3"
|
||||
}
|
||||
}
|
||||
18
server/src/server.js
Normal file
@@ -0,0 +1,18 @@
|
||||
const express = require('express');
|
||||
const bodyParser = require("body-parser");
|
||||
const signal = require("./signal3");
|
||||
const cors = require("cors");
|
||||
const config = require("../config.json");
|
||||
|
||||
const app = express()
|
||||
const {port} = config
|
||||
|
||||
app.use(cors())
|
||||
app.use(bodyParser.urlencoded({ extended: false }))
|
||||
app.use(bodyParser.json())
|
||||
|
||||
app.use(express.static("../clientV"))
|
||||
|
||||
app.listen(port, () => {
|
||||
console.log(`Launching Lil'Streamy on ${port}`)
|
||||
})
|
||||
154
server/src/signal.js
Normal file
@@ -0,0 +1,154 @@
|
||||
const WebSocketServer = require('ws').Server;
|
||||
|
||||
var wss = new WebSocketServer({port: 9090});
|
||||
|
||||
var users = {};
|
||||
|
||||
wss.on('connection', function(connection) {
|
||||
|
||||
console.log("User connected");
|
||||
|
||||
//when server gets a message from a connected user
|
||||
connection.on('message', (message) => onMessage(connection, message));
|
||||
|
||||
//when user exits, for example closes a browser window
|
||||
//this may help if we are still in "offer","answer" or "candidate" state
|
||||
connection.on("close", () => onClose(connection));
|
||||
});
|
||||
|
||||
function sendTo(connection, message) {
|
||||
connection.send(JSON.stringify(message));
|
||||
}
|
||||
|
||||
function onClose(connection) {
|
||||
if(connection.name) {
|
||||
delete users[connection.name];
|
||||
|
||||
if(connection.otherName) {
|
||||
console.log("Disconnecting from ", connection.otherName);
|
||||
var conn = users[connection.otherName];
|
||||
conn.otherName = null;
|
||||
|
||||
if(conn != null) {
|
||||
sendTo(conn, {
|
||||
type: "leave"
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function onMessage(connection, message) {
|
||||
var data;
|
||||
|
||||
//accepting only JSON messages
|
||||
try {
|
||||
data = JSON.parse(message);
|
||||
} catch (e) {
|
||||
console.log("Invalid JSON");
|
||||
data = {};
|
||||
}
|
||||
|
||||
//switching type of the user message
|
||||
switch (data.type) {
|
||||
//when a user tries to login
|
||||
case "login":
|
||||
console.log("User logged", data.name);
|
||||
|
||||
//if anyone is logged in with this username then refuse
|
||||
if(users[data.name]) {
|
||||
sendTo(connection, {
|
||||
type: "login",
|
||||
success: false
|
||||
});
|
||||
} else {
|
||||
//save user connection on the server
|
||||
users[data.target] = connection;
|
||||
connection.name = data.target;
|
||||
|
||||
sendTo(connection, {
|
||||
type: "login",
|
||||
success: true
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
||||
case "offer":
|
||||
//for ex. UserA wants to call UserB
|
||||
console.log("Sending offer to: ", data.target);
|
||||
|
||||
//if UserB exists then send him offer details
|
||||
var conn = users[data.target];
|
||||
|
||||
if(conn != null) {
|
||||
//setting that UserA connected with UserB
|
||||
connection.otherName = data.target;
|
||||
|
||||
sendTo(conn, {
|
||||
type: "offer",
|
||||
offer: data.offer,
|
||||
name: connection.target
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
||||
case "answer":
|
||||
console.log("Sending answer to: ", data.target);
|
||||
//for ex. UserB answers UserA
|
||||
var conn = users[data.target];
|
||||
|
||||
if(conn != null) {
|
||||
connection.otherName = data.target;
|
||||
sendTo(conn, {
|
||||
type: "answer",
|
||||
answer: data.answer
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
||||
case "candidate":
|
||||
console.log("Sending candidate to:",data.target);
|
||||
var conn = users[data.name];
|
||||
|
||||
if(conn != null) {
|
||||
sendTo(conn, {
|
||||
type: "candidate",
|
||||
candidate: data.candidate
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
||||
case "leave":
|
||||
console.log("Disconnecting from", data.target);
|
||||
var conn = users[data.target];
|
||||
conn.otherName = null;
|
||||
|
||||
//notify the other user so he can disconnect his peer connection
|
||||
if(conn != null) {
|
||||
sendTo(conn, {
|
||||
type: "leave"
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
||||
case "video-offer":
|
||||
console.log("Send video offer to", data.target);
|
||||
var conn = users[data.name];
|
||||
|
||||
if(conn != null) {
|
||||
connection.otherName = data.target;
|
||||
sendTo(conn, {
|
||||
type: "video-offer",
|
||||
answer: data.answer
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
sendTo(connection, {
|
||||
type: "error",
|
||||
message: "Command not found: " + data.type
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
330
server/src/signal2.js
Normal file
@@ -0,0 +1,330 @@
|
||||
"use strict";
|
||||
|
||||
var http = require('http');
|
||||
var https = require('https');
|
||||
var fs = require('fs');
|
||||
var WebSocketServer = require('ws').Server;
|
||||
|
||||
// Pathnames of the SSL key and certificate files to use for
|
||||
// HTTPS connections.
|
||||
|
||||
const keyFilePath = "/etc/pki/tls/private/mdn-samples.mozilla.org.key";
|
||||
const certFilePath = "/etc/pki/tls/certs/mdn-samples.mozilla.org.crt";
|
||||
|
||||
// Used for managing the text chat user list.
|
||||
|
||||
var connectionArray = [];
|
||||
var nextID = Date.now();
|
||||
var appendToMakeUnique = 1;
|
||||
|
||||
// Output logging information to console
|
||||
|
||||
function log(text) {
|
||||
var time = new Date();
|
||||
|
||||
console.log("[" + time.toLocaleTimeString() + "] " + text);
|
||||
}
|
||||
|
||||
// If you want to implement support for blocking specific origins, this is
|
||||
// where you do it. Just return false to refuse WebSocket connections given
|
||||
// the specified origin.
|
||||
function originIsAllowed(origin) {
|
||||
return true; // We will accept all connections
|
||||
}
|
||||
|
||||
// Scans the list of users and see if the specified name is unique. If it is,
|
||||
// return true. Otherwise, returns false. We want all users to have unique
|
||||
// names.
|
||||
function isUsernameUnique(name) {
|
||||
var isUnique = true;
|
||||
var i;
|
||||
|
||||
for (i=0; i<connectionArray.length; i++) {
|
||||
if (connectionArray[i].username === name) {
|
||||
isUnique = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return isUnique;
|
||||
}
|
||||
|
||||
// Sends a message (which is already stringified JSON) to a single
|
||||
// user, given their username. We use this for the WebRTC signaling,
|
||||
// and we could use it for private text messaging.
|
||||
function sendToOneUser(target, msgString) {
|
||||
var isUnique = true;
|
||||
var i;
|
||||
|
||||
for (i=0; i<connectionArray.length; i++) {
|
||||
if (connectionArray[i].username === target) {
|
||||
connectionArray[i].sendUTF(msgString);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Scan the list of connections and return the one for the specified
|
||||
// clientID. Each login gets an ID that doesn't change during the session,
|
||||
// so it can be tracked across username changes.
|
||||
function getConnectionForID(id) {
|
||||
var connect = null;
|
||||
var i;
|
||||
|
||||
for (i=0; i<connectionArray.length; i++) {
|
||||
if (connectionArray[i].clientID === id) {
|
||||
connect = connectionArray[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return connect;
|
||||
}
|
||||
|
||||
// Builds a message object of type "userlist" which contains the names of
|
||||
// all connected users. Used to ramp up newly logged-in users and,
|
||||
// inefficiently, to handle name change notifications.
|
||||
function makeUserListMessage() {
|
||||
var userListMsg = {
|
||||
type: "userlist",
|
||||
users: []
|
||||
};
|
||||
var i;
|
||||
|
||||
// Add the users to the list
|
||||
|
||||
for (i=0; i<connectionArray.length; i++) {
|
||||
userListMsg.users.push(connectionArray[i].username);
|
||||
}
|
||||
|
||||
return userListMsg;
|
||||
}
|
||||
|
||||
// Sends a "userlist" message to all chat members. This is a cheesy way
|
||||
// to ensure that every join/drop is reflected everywhere. It would be more
|
||||
// efficient to send simple join/drop messages to each user, but this is
|
||||
// good enough for this simple example.
|
||||
function sendUserListToAll() {
|
||||
var userListMsg = makeUserListMessage();
|
||||
var userListMsgStr = JSON.stringify(userListMsg);
|
||||
var i;
|
||||
|
||||
for (i=0; i<connectionArray.length; i++) {
|
||||
connectionArray[i].sendUTF(userListMsgStr);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Try to load the key and certificate files for SSL so we can
|
||||
// do HTTPS (required for non-local WebRTC).
|
||||
|
||||
var httpsOptions = {
|
||||
key: null,
|
||||
cert: null
|
||||
};
|
||||
|
||||
try {
|
||||
httpsOptions.key = fs.readFileSync(keyFilePath);
|
||||
try {
|
||||
httpsOptions.cert = fs.readFileSync(certFilePath);
|
||||
} catch(err) {
|
||||
httpsOptions.key = null;
|
||||
httpsOptions.cert = null;
|
||||
}
|
||||
} catch(err) {
|
||||
httpsOptions.key = null;
|
||||
httpsOptions.cert = null;
|
||||
}
|
||||
|
||||
// If we were able to get the key and certificate files, try to
|
||||
// start up an HTTPS server.
|
||||
|
||||
var webServer = null;
|
||||
|
||||
try {
|
||||
if (httpsOptions.key && httpsOptions.cert) {
|
||||
webServer = https.createServer(httpsOptions, handleWebRequest);
|
||||
}
|
||||
} catch(err) {
|
||||
webServer = null;
|
||||
}
|
||||
|
||||
if (!webServer) {
|
||||
try {
|
||||
webServer = http.createServer({}, handleWebRequest);
|
||||
} catch(err) {
|
||||
webServer = null;
|
||||
log(`Error attempting to create HTTP(s) server: ${err.toString()}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Our HTTPS server does nothing but service WebSocket
|
||||
// connections, so every request just returns 404. Real Web
|
||||
// requests are handled by the main server on the box. If you
|
||||
// want to, you can return real HTML here and serve Web content.
|
||||
|
||||
function handleWebRequest(request, response) {
|
||||
log ("Received request for " + request.url);
|
||||
response.writeHead(404);
|
||||
response.end();
|
||||
}
|
||||
|
||||
// Spin up the HTTPS server on the port assigned to this sample.
|
||||
// This will be turned into a WebSocket port very shortly.
|
||||
|
||||
webServer.listen(6503, function() {
|
||||
log("Server is listening on port 6503");
|
||||
});
|
||||
|
||||
// Create the WebSocket server by converting the HTTPS server into one.
|
||||
|
||||
var wsServer = new WebSocketServer({
|
||||
httpServer: webServer,
|
||||
autoAcceptConnections: false
|
||||
});
|
||||
|
||||
if (!wsServer) {
|
||||
log("ERROR: Unable to create WbeSocket server!");
|
||||
}
|
||||
|
||||
// Set up a "connect" message handler on our WebSocket server. This is
|
||||
// called whenever a user connects to the server's port using the
|
||||
// WebSocket protocol.
|
||||
|
||||
wsServer.on('request', function(request) {
|
||||
if (!originIsAllowed(request.origin)) {
|
||||
request.reject();
|
||||
log("Connection from " + request.origin + " rejected.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Accept the request and get a connection.
|
||||
|
||||
var connection = request.accept("json", request.origin);
|
||||
|
||||
// Add the new connection to our list of connections.
|
||||
|
||||
log("Connection accepted from " + connection.remoteAddress + ".");
|
||||
connectionArray.push(connection);
|
||||
|
||||
connection.clientID = nextID;
|
||||
nextID++;
|
||||
|
||||
// Send the new client its token; it send back a "username" message to
|
||||
// tell us what username they want to use.
|
||||
|
||||
var msg = {
|
||||
type: "id",
|
||||
id: connection.clientID
|
||||
};
|
||||
connection.sendUTF(JSON.stringify(msg));
|
||||
|
||||
// Set up a handler for the "message" event received over WebSocket. This
|
||||
// is a message sent by a client, and may be text to share with other
|
||||
// users, a private message (text or signaling) for one user, or a command
|
||||
// to the server.
|
||||
|
||||
connection.on('message', function(message) {
|
||||
if (message.type === 'utf8') {
|
||||
log("Received Message: " + message.utf8Data);
|
||||
|
||||
// Process incoming data.
|
||||
|
||||
var sendToClients = true;
|
||||
msg = JSON.parse(message.utf8Data);
|
||||
var connect = getConnectionForID(msg.id);
|
||||
|
||||
// Take a look at the incoming object and act on it based
|
||||
// on its type. Unknown message types are passed through,
|
||||
// since they may be used to implement client-side features.
|
||||
// Messages with a "target" property are sent only to a user
|
||||
// by that name.
|
||||
|
||||
switch(msg.type) {
|
||||
// Public, textual message
|
||||
case "message":
|
||||
msg.name = connect.username;
|
||||
msg.text = msg.text.replace(/(<([^>]+)>)/ig, "");
|
||||
break;
|
||||
|
||||
// Username change
|
||||
case "username":
|
||||
var nameChanged = false;
|
||||
var origName = msg.name;
|
||||
|
||||
// Ensure the name is unique by appending a number to it
|
||||
// if it's not; keep trying that until it works.
|
||||
while (!isUsernameUnique(msg.name)) {
|
||||
msg.name = origName + appendToMakeUnique;
|
||||
appendToMakeUnique++;
|
||||
nameChanged = true;
|
||||
}
|
||||
|
||||
// If the name had to be changed, we send a "rejectusername"
|
||||
// message back to the user so they know their name has been
|
||||
// altered by the server.
|
||||
if (nameChanged) {
|
||||
var changeMsg = {
|
||||
id: msg.id,
|
||||
type: "rejectusername",
|
||||
name: msg.name
|
||||
};
|
||||
connect.sendUTF(JSON.stringify(changeMsg));
|
||||
}
|
||||
|
||||
// Set this connection's final username and send out the
|
||||
// updated user list to all users. Yeah, we're sending a full
|
||||
// list instead of just updating. It's horribly inefficient
|
||||
// but this is a demo. Don't do this in a real app.
|
||||
connect.username = msg.name;
|
||||
sendUserListToAll();
|
||||
sendToClients = false; // We already sent the proper responses
|
||||
break;
|
||||
}
|
||||
|
||||
// Convert the revised message back to JSON and send it out
|
||||
// to the specified client or all clients, as appropriate. We
|
||||
// pass through any messages not specifically handled
|
||||
// in the select block above. This allows the clients to
|
||||
// exchange signaling and other control objects unimpeded.
|
||||
|
||||
if (sendToClients) {
|
||||
var msgString = JSON.stringify(msg);
|
||||
var i;
|
||||
|
||||
// If the message specifies a target username, only send the
|
||||
// message to them. Otherwise, send it to every user.
|
||||
if (msg.target && msg.target !== undefined && msg.target.length !== 0) {
|
||||
sendToOneUser(msg.target, msgString);
|
||||
} else {
|
||||
for (i=0; i<connectionArray.length; i++) {
|
||||
connectionArray[i].sendUTF(msgString);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Handle the WebSocket "close" event; this means a user has logged off
|
||||
// or has been disconnected.
|
||||
connection.on('close', function(reason, description) {
|
||||
// First, remove the connection from the list of connections.
|
||||
connectionArray = connectionArray.filter(function(el, idx, ar) {
|
||||
return el.connected;
|
||||
});
|
||||
|
||||
// Now send the updated user list. Again, please don't do this in a
|
||||
// real application. Your users won't like you very much.
|
||||
sendUserListToAll();
|
||||
|
||||
// Build and output log output for close information.
|
||||
|
||||
var logMessage = "Connection closed: " + connection.remoteAddress + " (" +
|
||||
reason;
|
||||
if (description !== null && description.length !== 0) {
|
||||
logMessage += ": " + description;
|
||||
}
|
||||
logMessage += ")";
|
||||
log(logMessage);
|
||||
});
|
||||
});
|
||||
100
server/src/signal3.js
Normal file
@@ -0,0 +1,100 @@
|
||||
const WebSocketServer = require('ws').Server;
|
||||
|
||||
var wss = new WebSocketServer({port: 9090});
|
||||
|
||||
var users = {};
|
||||
|
||||
wss.on('connection', function(connection) {
|
||||
console.log("User connected");
|
||||
|
||||
connection.on('message', (message) => onMessage(connection, message));
|
||||
|
||||
connection.on("close", () => onClose(connection));
|
||||
});
|
||||
|
||||
function sendTo(connection, message) {
|
||||
connection.send(JSON.stringify(message));
|
||||
}
|
||||
|
||||
function onClose(connection) {
|
||||
if(connection.name) {
|
||||
delete users[connection.name];
|
||||
|
||||
if(connection.otherName) {
|
||||
console.log("Disconnecting from ", connection.otherName);
|
||||
var conn = users[connection.otherName];
|
||||
conn.otherName = null;
|
||||
|
||||
if(conn != null) {
|
||||
sendTo(conn, {
|
||||
type: "leave"
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function onMessage(connection, message) {
|
||||
var data;
|
||||
|
||||
try {
|
||||
data = JSON.parse(message);
|
||||
} catch (e) {
|
||||
console.log("Invalid JSON");
|
||||
data = {};
|
||||
}
|
||||
|
||||
switch (data.type) {
|
||||
case "login" :
|
||||
console.log("User logged", data.name);
|
||||
if(users[data.name]) {
|
||||
sendTo(connection, {
|
||||
type: "login",
|
||||
success: false
|
||||
});
|
||||
} else {
|
||||
users[data.name] = connection;
|
||||
connection.name = data.name;
|
||||
|
||||
sendTo(connection, {
|
||||
type: "login",
|
||||
success: true
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
||||
case "leave" :
|
||||
console.log("Disconnecting from", data.name);
|
||||
var conn = users[data.name];
|
||||
conn.otherName = null;
|
||||
|
||||
//notify the other user so he can disconnect his peer connection
|
||||
/*
|
||||
if(conn != null) {
|
||||
sendTo(conn, {
|
||||
type: "leave"
|
||||
});
|
||||
}*/
|
||||
break;
|
||||
|
||||
case "userlist" :
|
||||
console.log("Send list to", data.name);
|
||||
sendTo(connection, {
|
||||
type: "userlist",
|
||||
list: users
|
||||
});
|
||||
break;
|
||||
|
||||
default:
|
||||
if (data.target){
|
||||
var targetConnection = users[data.target];
|
||||
console.log("Forward message from " + data.name + " to " + data.target + " (" + data.type + ")");
|
||||
sendTo(targetConnection, data);
|
||||
} else {
|
||||
sendTo(connection, {
|
||||
type: "error",
|
||||
message: "Command not found: " + data.type
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||