├── .gitignore
├── LICENSE
├── README.md
├── meetjs-react
├── .Dockerignore
├── .env
├── .env.development
├── .netlify
│ └── state.json
├── .vscode
│ └── settings.json
├── Dockerfile
├── README.md
├── build
│ ├── _redirects
│ ├── asset-manifest.json
│ ├── favicon.ico
│ ├── index.html
│ ├── logo192.png
│ ├── logo512.png
│ ├── manifest.json
│ ├── precache-manifest.3738c4cb77ba36efcb7b64e390cf5b2a.js
│ ├── robots.txt
│ ├── service-worker.js
│ └── static
│ │ ├── css
│ │ ├── main.724d8bfe.chunk.css
│ │ └── main.724d8bfe.chunk.css.map
│ │ ├── js
│ │ ├── 2.d8ce8405.chunk.js
│ │ ├── 2.d8ce8405.chunk.js.LICENSE.txt
│ │ ├── 2.d8ce8405.chunk.js.map
│ │ ├── main.3d8c5fc6.chunk.js
│ │ ├── main.3d8c5fc6.chunk.js.map
│ │ ├── runtime-main.d3070e05.js
│ │ └── runtime-main.d3070e05.js.map
│ │ └── media
│ │ ├── Quicksand_Bold.c3bf00e5.otf
│ │ ├── Quicksand_Book.dd2da1d8.otf
│ │ ├── cam-off.60765bda.svg
│ │ ├── cam-on.a9c7e91b.svg
│ │ ├── logo.acb38cf1.svg
│ │ ├── mic-off.99246803.svg
│ │ ├── mic-on.5ce2865f.svg
│ │ └── view.d319b0ef.svg
├── docker-compose.yml
├── nginx.conf
├── package-lock.json
├── package.json
├── public
│ ├── _redirects
│ ├── favicon.ico
│ ├── index.html
│ ├── logo192.png
│ ├── logo512.png
│ ├── manifest.json
│ └── robots.txt
├── src
│ ├── App.scss
│ ├── App.tsx
│ ├── assets
│ │ ├── fonts
│ │ │ ├── Quicksand_Bold.otf
│ │ │ └── Quicksand_Book.otf
│ │ └── images
│ │ │ ├── cam-off.svg
│ │ │ ├── cam-on.svg
│ │ │ ├── close.svg
│ │ │ ├── logo.png
│ │ │ ├── logo.svg
│ │ │ ├── man.svg
│ │ │ ├── mic-off.svg
│ │ │ ├── mic-on.svg
│ │ │ ├── video-off.svg
│ │ │ ├── video-on.svg
│ │ │ └── view.svg
│ ├── components
│ │ ├── home
│ │ │ ├── home.scss
│ │ │ └── home.tsx
│ │ ├── meeting
│ │ │ ├── meeting.scss
│ │ │ └── meeting.tsx
│ │ ├── session-modal
│ │ │ ├── modal.scss
│ │ │ └── modal.tsx
│ │ └── state-machine
│ │ │ └── state-machine.tsx
│ ├── constants
│ │ └── stun-servers.ts
│ ├── fonts.scss
│ ├── index.tsx
│ ├── interfaces
│ │ ├── response-data.tsx
│ │ ├── socket-data.tsx
│ │ └── stun-servers.tsx
│ ├── logo.svg
│ ├── react-app-env.d.ts
│ ├── serviceWorker.ts
│ ├── services
│ │ ├── api.services.tsx
│ │ └── web-socket.services.tsx
│ ├── setupTests.ts
│ └── utils
│ │ ├── easy-rtc.ts
│ │ └── identifier.utils.ts
├── tsconfig.json
└── yarn.lock
├── meetjs-server
├── .vscode
│ └── settings.json
├── Dockerfile
├── docker-compose.yml
├── go.mod
├── go.sum
└── src
│ ├── controllers
│ ├── session.controllers.go
│ └── socket.controllers.go
│ ├── interfaces
│ ├── connection.interfaces.go
│ ├── mongodb.interfaces.go
│ ├── session.interfaces.go
│ └── socket.interfaces.go
│ ├── server.go
│ └── utils
│ └── password.utils.go
└── resource
├── architecture.png
├── demo1.png
├── demo2.png
└── demo3.png
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | lerna-debug.log*
8 |
9 | # Diagnostic reports (https://nodejs.org/api/report.html)
10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
11 |
12 | # Runtime data
13 | pids
14 | *.pid
15 | *.seed
16 | *.pid.lock
17 |
18 | # Directory for instrumented libs generated by jscoverage/JSCover
19 | lib-cov
20 |
21 | # Coverage directory used by tools like istanbul
22 | coverage
23 | *.lcov
24 |
25 | # nyc test coverage
26 | .nyc_output
27 |
28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
29 | .grunt
30 |
31 | # Bower dependency directory (https://bower.io/)
32 | bower_components
33 |
34 | # node-waf configuration
35 | .lock-wscript
36 |
37 | # Compiled binary addons (https://nodejs.org/api/addons.html)
38 | build/Release
39 |
40 | # Dependency directories
41 | node_modules/
42 | jspm_packages/
43 |
44 | # TypeScript v1 declaration files
45 | typings/
46 |
47 | # TypeScript cache
48 | *.tsbuildinfo
49 |
50 | # Optional npm cache directory
51 | .npm
52 |
53 | # Optional eslint cache
54 | .eslintcache
55 |
56 | # Microbundle cache
57 | .rpt2_cache/
58 | .rts2_cache_cjs/
59 | .rts2_cache_es/
60 | .rts2_cache_umd/
61 |
62 | # Optional REPL history
63 | .node_repl_history
64 |
65 | # Output of 'npm pack'
66 | *.tgz
67 |
68 | # Yarn Integrity file
69 | .yarn-integrity
70 |
71 | # dotenv environment variables file
72 | .env.development
73 | .env.production
74 |
75 | # parcel-bundler cache (https://parceljs.org/)
76 | .cache
77 |
78 | # Next.js build output
79 | .next
80 |
81 | # Nuxt.js build / generate output
82 | .nuxt
83 | dist
84 |
85 | # Gatsby files
86 | .cache/
87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js
88 | # https://nextjs.org/blog/next-9-1#public-directory-support
89 | # public
90 |
91 | # vuepress build output
92 | .vuepress/dist
93 |
94 | # Serverless directories
95 | .serverless/
96 |
97 | # FuseBox cache
98 | .fusebox/
99 |
100 | # DynamoDB Local files
101 | .dynamodb/
102 |
103 | # TernJS port file
104 | .tern-port
105 |
106 | .DS_STORE
107 |
108 | terraform.tfvars
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 André Marques
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://app.netlify.com/sites/meetjs/deploys)
2 |
3 | Live Demo - [Open](https://meetjs.netlify.app/)
4 | # MeetJS - A free video chat platform
5 |
6 | Application for non-commercial use.
7 | Project created be part of my open-source series. Hope it can help other developers out there trying to develop similar applications.
8 |
9 | ## WebRTC Architecture
10 |
11 | Published an article on Medium's largest active publication (The Startup) - [Read more](https://medium.com/swlh/manage-dynamic-multi-peer-connections-in-webrtc-3ff4e10f75b7).
12 |
13 | ## Technologies
14 |
15 | * React Hooks
16 | * React Router
17 | * Sass (BEM methodology)
18 | * Golang + Gin
19 | * Gorilla Websockets
20 | * MongoDB
21 |
22 | ## Backend Architecture
23 |
24 |
25 |
26 | When a user creates a session he'll receive a unique hashed URL whereas he can share with others to join the meeting (upon credentials are validated).
27 |
28 | ## Examples
29 |
30 |
31 |
32 |
33 |
34 | ## Contributing
35 | Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.
36 |
37 | ## License
38 | [MIT](https://choosealicense.com/licenses/mit/)
39 |
--------------------------------------------------------------------------------
/meetjs-react/.Dockerignore:
--------------------------------------------------------------------------------
1 | node_modules
--------------------------------------------------------------------------------
/meetjs-react/.env:
--------------------------------------------------------------------------------
1 | REACT_APP_SERVER=${SERVER_URL}
2 | REACT_APP_WS_URL=${WS_URL}
--------------------------------------------------------------------------------
/meetjs-react/.env.development:
--------------------------------------------------------------------------------
1 | REACT_APP_WS_URL = 'ws://localhost:9000/ws'
2 | REACT_APP_SERVER = 'http://localhost:9000'
--------------------------------------------------------------------------------
/meetjs-react/.netlify/state.json:
--------------------------------------------------------------------------------
1 | {
2 | "siteId": "3fab6827-60bb-4548-b465-9e86b62150cf"
3 | }
--------------------------------------------------------------------------------
/meetjs-react/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "compile-hero.disable-compile-files-on-did-save-code": false
3 | }
--------------------------------------------------------------------------------
/meetjs-react/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:14.1-alpine AS builder
2 |
3 | WORKDIR /opt/web
4 | COPY package.json package-lock.json ./
5 | RUN npm install
6 |
7 | ENV PATH="./node_modules/.bin:$PATH"
8 |
9 | COPY . ./
10 |
11 | RUN npm run build
12 |
13 | FROM nginx:1.17-alpine
14 |
15 | RUN apk --no-cache add curl
16 |
17 | RUN curl -L https://github.com/a8m/envsubst/releases/download/v1.1.0/envsubst-`uname -s`-`uname -m` -o envsubst && \
18 | chmod +x envsubst && \
19 | mv envsubst /usr/local/bin
20 |
21 | COPY nginx.conf /etc/nginx/nginx.tmpl
22 |
23 | CMD ["/bin/sh", "-c", "envsubst < /etc/nginx/nginx.tmpl > /etc/nginx/nginx.conf && nginx -g 'daemon off;'"]
24 |
25 | COPY --from=builder /opt/web/build /usr/share/nginx/html
--------------------------------------------------------------------------------
/meetjs-react/README.md:
--------------------------------------------------------------------------------
1 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
2 |
3 | ## Available Scripts
4 |
5 | In the project directory, you can run:
6 |
7 | ### `yarn start`
8 |
9 | Runs the app in the development mode.
10 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
11 |
12 | The page will reload if you make edits.
13 | You will also see any lint errors in the console.
14 |
15 | ### `yarn test`
16 |
17 | Launches the test runner in the interactive watch mode.
18 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
19 |
20 | ### `yarn build`
21 |
22 | Builds the app for production to the `build` folder.
23 | It correctly bundles React in production mode and optimizes the build for the best performance.
24 |
25 | The build is minified and the filenames include the hashes.
26 | Your app is ready to be deployed!
27 |
28 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
29 |
30 | ### `yarn eject`
31 |
32 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!**
33 |
34 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
35 |
36 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
37 |
38 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
39 |
40 | ## Learn More
41 |
42 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
43 |
44 | To learn React, check out the [React documentation](https://reactjs.org/).
45 |
--------------------------------------------------------------------------------
/meetjs-react/build/_redirects:
--------------------------------------------------------------------------------
1 | /* /index.html 200
--------------------------------------------------------------------------------
/meetjs-react/build/asset-manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "files": {
3 | "main.css": "/static/css/main.724d8bfe.chunk.css",
4 | "main.js": "/static/js/main.3d8c5fc6.chunk.js",
5 | "main.js.map": "/static/js/main.3d8c5fc6.chunk.js.map",
6 | "runtime-main.js": "/static/js/runtime-main.d3070e05.js",
7 | "runtime-main.js.map": "/static/js/runtime-main.d3070e05.js.map",
8 | "static/js/2.d8ce8405.chunk.js": "/static/js/2.d8ce8405.chunk.js",
9 | "static/js/2.d8ce8405.chunk.js.map": "/static/js/2.d8ce8405.chunk.js.map",
10 | "index.html": "/index.html",
11 | "precache-manifest.3738c4cb77ba36efcb7b64e390cf5b2a.js": "/precache-manifest.3738c4cb77ba36efcb7b64e390cf5b2a.js",
12 | "service-worker.js": "/service-worker.js",
13 | "static/css/main.724d8bfe.chunk.css.map": "/static/css/main.724d8bfe.chunk.css.map",
14 | "static/js/2.d8ce8405.chunk.js.LICENSE.txt": "/static/js/2.d8ce8405.chunk.js.LICENSE.txt",
15 | "static/media/App.scss": "/static/media/Quicksand_Book.dd2da1d8.otf",
16 | "static/media/cam-off.svg": "/static/media/cam-off.60765bda.svg",
17 | "static/media/cam-on.svg": "/static/media/cam-on.a9c7e91b.svg",
18 | "static/media/logo.svg": "/static/media/logo.acb38cf1.svg",
19 | "static/media/mic-off.svg": "/static/media/mic-off.99246803.svg",
20 | "static/media/mic-on.svg": "/static/media/mic-on.5ce2865f.svg",
21 | "static/media/view.svg": "/static/media/view.d319b0ef.svg"
22 | },
23 | "entrypoints": [
24 | "static/js/runtime-main.d3070e05.js",
25 | "static/js/2.d8ce8405.chunk.js",
26 | "static/css/main.724d8bfe.chunk.css",
27 | "static/js/main.3d8c5fc6.chunk.js"
28 | ]
29 | }
--------------------------------------------------------------------------------
/meetjs-react/build/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thealmarques/meetjs-webrtc-golang/79205d205bdb4624ef664ba6630f53ff19999b77/meetjs-react/build/favicon.ico
--------------------------------------------------------------------------------
/meetjs-react/build/index.html:
--------------------------------------------------------------------------------
1 |
MeetJS
--------------------------------------------------------------------------------
/meetjs-react/build/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thealmarques/meetjs-webrtc-golang/79205d205bdb4624ef664ba6630f53ff19999b77/meetjs-react/build/logo192.png
--------------------------------------------------------------------------------
/meetjs-react/build/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thealmarques/meetjs-webrtc-golang/79205d205bdb4624ef664ba6630f53ff19999b77/meetjs-react/build/logo512.png
--------------------------------------------------------------------------------
/meetjs-react/build/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/meetjs-react/build/precache-manifest.3738c4cb77ba36efcb7b64e390cf5b2a.js:
--------------------------------------------------------------------------------
1 | self.__precacheManifest = (self.__precacheManifest || []).concat([
2 | {
3 | "revision": "6043490a4c87966f7b5ccf65de38c258",
4 | "url": "/index.html"
5 | },
6 | {
7 | "revision": "a2dd6f72d18416f2d7b2",
8 | "url": "/static/css/main.724d8bfe.chunk.css"
9 | },
10 | {
11 | "revision": "29a5482d20774e5ea822",
12 | "url": "/static/js/2.d8ce8405.chunk.js"
13 | },
14 | {
15 | "revision": "c64c486544348f10a6d6c716950bc223",
16 | "url": "/static/js/2.d8ce8405.chunk.js.LICENSE.txt"
17 | },
18 | {
19 | "revision": "a2dd6f72d18416f2d7b2",
20 | "url": "/static/js/main.3d8c5fc6.chunk.js"
21 | },
22 | {
23 | "revision": "a4a6c323de87f60984d2",
24 | "url": "/static/js/runtime-main.d3070e05.js"
25 | },
26 | {
27 | "revision": "c3bf00e585782373e1b601c07b513d85",
28 | "url": "/static/media/Quicksand_Bold.c3bf00e5.otf"
29 | },
30 | {
31 | "revision": "dd2da1d8f9d3944efe2797e1fa02e096",
32 | "url": "/static/media/Quicksand_Book.dd2da1d8.otf"
33 | },
34 | {
35 | "revision": "60765bda15abc59faa89876cf94b2693",
36 | "url": "/static/media/cam-off.60765bda.svg"
37 | },
38 | {
39 | "revision": "a9c7e91bff3a2eb6d7a47be95a65dd02",
40 | "url": "/static/media/cam-on.a9c7e91b.svg"
41 | },
42 | {
43 | "revision": "acb38cf18cf170453b8ef4f882b8605b",
44 | "url": "/static/media/logo.acb38cf1.svg"
45 | },
46 | {
47 | "revision": "99246803db7fb1b40a9c2d9d11c52bd4",
48 | "url": "/static/media/mic-off.99246803.svg"
49 | },
50 | {
51 | "revision": "5ce2865fa8ecbdcea464b41dd99d584a",
52 | "url": "/static/media/mic-on.5ce2865f.svg"
53 | },
54 | {
55 | "revision": "d319b0ef0ceb711d0673b1f2ecfb8561",
56 | "url": "/static/media/view.d319b0ef.svg"
57 | }
58 | ]);
--------------------------------------------------------------------------------
/meetjs-react/build/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/meetjs-react/build/service-worker.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Welcome to your Workbox-powered service worker!
3 | *
4 | * You'll need to register this file in your web app and you should
5 | * disable HTTP caching for this file too.
6 | * See https://goo.gl/nhQhGp
7 | *
8 | * The rest of the code is auto-generated. Please don't update this file
9 | * directly; instead, make changes to your Workbox build configuration
10 | * and re-run your build process.
11 | * See https://goo.gl/2aRDsh
12 | */
13 |
14 | importScripts("https://storage.googleapis.com/workbox-cdn/releases/4.3.1/workbox-sw.js");
15 |
16 | importScripts(
17 | "/precache-manifest.3738c4cb77ba36efcb7b64e390cf5b2a.js"
18 | );
19 |
20 | self.addEventListener('message', (event) => {
21 | if (event.data && event.data.type === 'SKIP_WAITING') {
22 | self.skipWaiting();
23 | }
24 | });
25 |
26 | workbox.core.clientsClaim();
27 |
28 | /**
29 | * The workboxSW.precacheAndRoute() method efficiently caches and responds to
30 | * requests for URLs in the manifest.
31 | * See https://goo.gl/S9QRab
32 | */
33 | self.__precacheManifest = [].concat(self.__precacheManifest || []);
34 | workbox.precaching.precacheAndRoute(self.__precacheManifest, {});
35 |
36 | workbox.routing.registerNavigationRoute(workbox.precaching.getCacheKeyForURL("/index.html"), {
37 |
38 | blacklist: [/^\/_/,/\/[^/?]+\.[^/]+$/],
39 | });
40 |
--------------------------------------------------------------------------------
/meetjs-react/build/static/css/main.724d8bfe.chunk.css:
--------------------------------------------------------------------------------
1 | @font-face{font-family:"Quicksand-Bold";src:local("Lato"),url(/static/media/Quicksand_Bold.c3bf00e5.otf) format("opentype")}@font-face{font-family:"Quicksand-Regular";src:local("Lato"),url(/static/media/Quicksand_Book.dd2da1d8.otf) format("opentype")}body{margin:0}.app{width:100%;height:100vh}*{box-sizing:border-box}::-webkit-input-placeholder{color:#707070;opacity:1}:-ms-input-placeholder{opacity:1}::-ms-input-placeholder{opacity:1}::placeholder{color:#707070;opacity:1}:-ms-input-placeholder{color:#707070}::-ms-input-placeholder{color:#707070}.container{height:100%;width:100%;background-color:#8c55a3;overflow:auto}.container .header{padding:20px;width:100%;justify-content:space-between}.container .header,.container .header__left{display:flex;align-items:center}.container .header__left__logo{width:60px;height:60px;color:#fff}.container .header__left__text{margin-left:10px;font-size:20px;font-family:"Quicksand-Bold";color:#fff}.container .header__left__text:hover{color:#fff}.container .header__right{cursor:pointer;display:flex;color:#fff;font-family:"Quicksand-Bold"}.container .header__right a{color:#fff}.container .header__right__margin{margin-right:7px}.container .body{margin-top:50px;display:flex;flex-direction:column;align-items:center}.container .body__title{font-size:24px}.container .body__sub-title,.container .body__title{font-family:"Quicksand-Bold";color:#fff;text-align:center}.container .body__sub-title{font-size:14px;opacity:.7;margin-top:10px}.container .body__form{display:flex;flex-direction:row;margin-top:40px;width:480px;align-items:center;justify-content:space-between}.container .body__form__inputs{display:flex;flex-direction:column;width:60%}.container .body__form__inputs__input{outline:none;border:0;padding-left:10px;border-radius:2px;width:100%;height:35px;margin-top:10px;box-shadow:0 0 2px 1px #ededed}.container .body__form__create{width:30%;display:flex;flex-direction:column;align-items:center;justify-content:center;flex-wrap:wrap}.container .body__form__create__btn{font-family:"Quicksand-Bold";margin-top:10px;border:0;outline:0;cursor:pointer;width:100%;height:35px;background-color:#ed7655;border-radius:5px;color:#fff}.container .body__form__create__small-text{margin-top:5px;opacity:.5;font-family:"Quicksand-Bold";color:#fff;font-size:12px}@media (max-width:480px){.container .body__form{width:90%}}.modal{width:100%;position:relative;display:flex;align-items:center;justify-content:center}.modal__wrapper{border-radius:5px;z-index:1;width:400px;height:250px;background-color:#f7f7f7}.modal__wrapper__body{padding-left:20px;width:100%;height:100%;position:relative;display:flex;flex-direction:row}.modal__wrapper__body__form{width:92%;display:flex;flex-direction:column;margin-top:50px}.modal__wrapper__body__form__small-text{font-family:"Quicksand-Regular";font-size:14px}.modal__wrapper__body__form__big-text{font-family:"Quicksand-Bold";font-size:17px}.modal__wrapper__body__form__input{outline:none;border:0;padding-left:10px;border-radius:2px;width:100%;height:35px;margin-top:10px;box-shadow:0 0 5px 1px #ededed}.modal__wrapper__body__form__connect{font-family:"Quicksand-Bold";margin-top:10px;border:0;outline:0;cursor:pointer;width:100%;height:35px;background-color:#ed7655;border-radius:5px;color:#fff}.modal__wrapper__body__form-error{margin-top:5px;color:#ce0b29;font-size:10px;font-family:"Quicksand-Bold"}.meeting{height:100%;width:100%;background-color:#8c55a3;overflow:auto}.meeting__header{padding:20px;width:100%;justify-content:space-between}.meeting__header,.meeting__header__left{display:flex;align-items:center}.meeting__header__left__logo{width:60px;height:60px;color:#fff}.meeting__header__left__text{margin-left:10px;font-size:20px;font-family:"Quicksand-Bold";color:#fff}.meeting__header__left__text:hover{color:#fff}.meeting__header__right{cursor:pointer;display:flex;color:#fff;font-family:"Quicksand-Bold"}.meeting__header__right a{color:#fff}.meeting__header__right__margin{margin-right:7px}.meeting__body{width:100%;margin-top:20px;display:flex;align-items:center;justify-content:center}.meeting__body__users{display:flex;align-items:center;flex-wrap:wrap}.meeting__body__users-row{flex-direction:row}.meeting__body__users-column{flex-direction:column}.meeting__body__users__remote-video{margin-bottom:20px;margin-right:20px;width:40vw}.meeting__body__users__remote-video-split{width:35vw;justify-content:center}.meeting__body__info{margin-top:10%;color:#fff;font-family:"Quicksand-Bold";text-align:center}.meeting__body__spinner{margin-top:10%}.meeting__body__bar{position:absolute;bottom:20px;width:60vw;height:45px;background-color:#ed7655;border-radius:20px;display:flex;justify-content:space-between;align-items:center}.meeting__body__bar__video-container{position:absolute;right:-2vw;top:-11vw;transition:transform 1s ease-in-out}.meeting__body__bar__video-container__local-video{border-radius:5px;height:10vw;width:20vw;transition:transform 1s ease-in-out}.meeting__body__bar__video-container__local-video:hover{transform:scale(2)}.meeting__body__bar__video-container:hover{transform:translateY(-5vw)}.meeting__body__bar__left-bar,.meeting__body__bar__left-bar__status{height:100%;display:flex;align-items:center}.meeting__body__bar__left-bar__status{width:8vw;border-radius:20px;justify-content:center;color:#fff;font-size:1.2vw;font-family:"Quicksand-Bold";cursor:pointer;-webkit-user-select:none;-ms-user-select:none;user-select:none}.meeting__body__bar__left-bar__status-inactive{background-color:#ce0b29}.meeting__body__bar__left-bar__status-active{background-color:#299b43}.meeting__body__bar__left-bar__title{font-size:1.2vw;color:#fff;margin-left:10px;text-align:center;font-family:"Quicksand-Bold"}.meeting__body__bar__right-bar{margin-right:10px;display:flex;align-items:center}.meeting__body__bar__right-bar__count{font-size:10px;font-family:"Quicksand-Bold";color:#fff;margin-right:10px}.meeting__body__bar__right-bar__icon{margin-right:15px;width:2vw;height:2vw;cursor:pointer}
2 | /*# sourceMappingURL=main.724d8bfe.chunk.css.map */
--------------------------------------------------------------------------------
/meetjs-react/build/static/css/main.724d8bfe.chunk.css.map:
--------------------------------------------------------------------------------
1 | {"version":3,"sources":["fonts.scss","App.scss","home.scss","modal.scss","meeting.scss"],"names":[],"mappings":"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","file":"main.724d8bfe.chunk.css","sourcesContent":["@font-face {\n font-family: 'Quicksand-Bold';\n src: local('Lato'), url(./assets/fonts/Quicksand_Bold.otf) format('opentype');\n}\n\n@font-face {\n font-family: 'Quicksand-Regular';\n src: local('Lato'), url(./assets/fonts/Quicksand_Book.otf) format('opentype');\n}","@import \"./fonts.scss\";\n\nbody {\n margin: 0;\n}\n\n.app {\n width: 100%;\n height: 100vh;\n}\n\n* {\n box-sizing: border-box;\n}\n\n::placeholder { /* Chrome, Firefox, Opera, Safari 10.1+ */\n color: #707070;\n opacity: 1; /* Firefox */\n}\n\n:-ms-input-placeholder { /* Internet Explorer 10-11 */\n color: #707070;\n}\n\n::-ms-input-placeholder { /* Microsoft Edge */\n color: #707070;\n}",".container {\n height: 100%;\n width: 100%;\n background-color: #8C55A3;\n overflow: auto;\n\n .header {\n padding: 20px;\n width: 100%;\n display: flex;\n align-items: center;\n justify-content: space-between;\n\n &__left {\n display: flex;\n align-items: center;\n\n &__logo {\n width: 60px;\n height: 60px;\n color: white;\n }\n \n &__text {\n margin-left: 10px;\n font-size: 20px;\n font-family: 'Quicksand-Bold';\n color: white;\n \n &:hover {\n color: white;\n }\n }\n }\n\n &__right {\n cursor: pointer;\n display: flex;\n color: white;\n font-family: 'Quicksand-Bold';\n\n a {\n color: white;\n }\n\n &__margin {\n margin-right: 7px;\n }\n }\n }\n\n .body {\n margin-top: 50px;\n display: flex;\n flex-direction: column;\n align-items: center;\n\n &__title {\n font-family: 'Quicksand-Bold';\n font-size: 24px;\n color: white;\n text-align: center;\n }\n\n &__sub-title {\n font-family: 'Quicksand-Bold';\n font-size: 14px;\n opacity: .7;\n color: white;\n margin-top: 10px;\n text-align: center;\n }\n\n &__form {\n display: flex;\n flex-direction: row;\n margin-top: 40px;\n width: 480px;\n align-items: center;\n justify-content: space-between;\n\n &__inputs {\n display: flex;\n flex-direction: column;\n width: 60%;\n\n &__input {\n outline: none;\n border: 0;\n padding-left: 10px;\n border-radius: 2px;\n width: 100%;\n height: 35px;\n margin-top: 10px;\n box-shadow: 0px 0px 2px 1px rgba(237,237,237,1);\n }\n }\n\n &__create {\n width: 30%;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n flex-wrap: wrap;\n\n &__btn {\n font-family: 'Quicksand-Bold';\n margin-top: 10px;\n border: 0;\n outline: 0;\n cursor: pointer;\n width: 100%;\n height: 35px;\n background-color: #ED7655;\n border-radius: 5px;\n color: white;\n }\n\n &__small-text {\n margin-top: 5px;\n opacity: .5;\n font-family: 'Quicksand-Bold';\n color: white;\n font-size: 12px;\n }\n }\n\n @media (max-width: 480px) {\n width: 90%;\n }\n }\n }\n}",".modal {\n width: 100%;\n position: relative;\n display: flex;\n align-items: center;\n justify-content: center;\n\n &__wrapper {\n border-radius: 5px;\n z-index: 1;\n width: 400px;\n height: 250px;\n background-color: #F7F7F7;\n\n &__body {\n padding-left: 20px;\n width: 100%;\n height: 100%;\n position: relative;\n display: flex;\n flex-direction: row;\n\n &__form {\n width: 92%;\n display: flex;\n flex-direction: column;\n margin-top: 50px;\n\n &__small-text {\n font-family: 'Quicksand-Regular';\n font-size: 14px;\n }\n\n &__big-text {\n font-family: 'Quicksand-Bold';\n font-size: 17px;\n }\n\n &__input {\n outline: none;\n border: 0;\n padding-left: 10px;\n border-radius: 2px;\n width: 100%;\n height: 35px;\n margin-top: 10px;\n box-shadow: 0px 0px 5px 1px rgba(237,237,237,1);\n }\n\n &__connect {\n font-family: 'Quicksand-Bold';\n margin-top: 10px;\n border: 0;\n outline: 0;\n cursor: pointer;\n width: 100%;\n height: 35px;\n background-color: #ED7655;\n border-radius: 5px;\n color: white;\n }\n\n &-error {\n margin-top: 5px;\n color: #CE0B29;\n font-size: 10px;\n font-family: 'Quicksand-Bold';\n }\n }\n }\n }\n}",".meeting {\n height: 100%;\n width: 100%;\n background-color: #8C55A3;\n overflow: auto;\n\n &__header {\n padding: 20px;\n width: 100%;\n display: flex;\n align-items: center;\n justify-content: space-between;\n\n &__left {\n display: flex;\n align-items: center;\n\n &__logo {\n width: 60px;\n height: 60px;\n color: white;\n }\n\n &__text {\n margin-left: 10px;\n font-size: 20px;\n font-family: 'Quicksand-Bold';\n color: white;\n\n &:hover {\n color: white;\n }\n }\n }\n\n &__right {\n cursor: pointer;\n display: flex;\n color: white;\n font-family: 'Quicksand-Bold';\n\n a {\n color: white;\n }\n\n &__margin {\n margin-right: 7px;\n }\n }\n }\n\n &__body {\n width: 100%;\n margin-top: 20px;\n display: flex;\n align-items: center;\n justify-content: center;\n\n &__users {\n display: flex;\n align-items: center;\n flex-wrap: wrap;\n\n &-row {\n flex-direction: row;\n }\n\n &-column {\n flex-direction: column;\n }\n\n &__remote-video {\n margin-bottom: 20px;\n margin-right: 20px;\n width: 40vw;\n\n &-split {\n width: 35vw;\n justify-content: center;\n }\n }\n }\n\n &__info {\n margin-top: 10%;\n color: white;\n font-family: 'Quicksand-Bold';\n text-align: center;\n }\n\n &__spinner {\n margin-top: 10%;\n }\n\n &__bar {\n position: absolute;\n bottom: 20px;\n width: 60vw;\n height: 45px;\n background-color: #ED7655;\n border-radius: 20px;\n display: flex;\n justify-content: space-between;\n align-items: center;\n\n &__video-container {\n position: absolute;\n right: -2vw;\n top: -11vw;\n transition: transform 1s ease-in-out;\n\n &__local-video {\n border-radius: 5px;\n height: 10vw;\n width: 20vw;\n transition: transform 1s ease-in-out;\n\n &:hover {\n transform: scale(2);\n }\n }\n\n &:hover {\n transform: translateY(-5vw);\n }\n }\n\n &__left-bar {\n height: 100%;\n display: flex;\n align-items: center;\n\n &__status {\n width: 8vw;\n height: 100%;\n border-radius: 20px;\n display: flex;\n align-items: center;\n justify-content: center;\n color: white;\n font-size: 1.2vw;\n font-family: 'Quicksand-Bold';\n cursor: pointer;\n user-select: none;\n\n &-inactive {\n background-color: #CE0B29;\n }\n\n &-active {\n background-color: #299B43;\n }\n }\n\n &__title {\n font-size: 1.2vw;\n color: white;\n margin-left: 10px;\n text-align: center;\n font-family: 'Quicksand-Bold';\n }\n }\n\n &__right-bar {\n margin-right: 10px;\n display: flex;\n align-items: center;\n\n &__count {\n font-size: 10px;\n font-family: 'Quicksand-Bold';\n color: white;\n margin-right: 10px;\n }\n\n &__icon {\n margin-right: 15px;\n width: 2vw;\n height: 2vw;\n cursor: pointer;\n }\n }\n }\n }\n}"]}
--------------------------------------------------------------------------------
/meetjs-react/build/static/js/2.d8ce8405.chunk.js.LICENSE.txt:
--------------------------------------------------------------------------------
1 | /*
2 | object-assign
3 | (c) Sindre Sorhus
4 | @license MIT
5 | */
6 |
7 | /** @license React v0.19.1
8 | * scheduler.production.min.js
9 | *
10 | * Copyright (c) Facebook, Inc. and its affiliates.
11 | *
12 | * This source code is licensed under the MIT license found in the
13 | * LICENSE file in the root directory of this source tree.
14 | */
15 |
16 | /** @license React v16.13.1
17 | * react-dom.production.min.js
18 | *
19 | * Copyright (c) Facebook, Inc. and its affiliates.
20 | *
21 | * This source code is licensed under the MIT license found in the
22 | * LICENSE file in the root directory of this source tree.
23 | */
24 |
25 | /** @license React v16.13.1
26 | * react-is.production.min.js
27 | *
28 | * Copyright (c) Facebook, Inc. and its affiliates.
29 | *
30 | * This source code is licensed under the MIT license found in the
31 | * LICENSE file in the root directory of this source tree.
32 | */
33 |
34 | /** @license React v16.13.1
35 | * react.production.min.js
36 | *
37 | * Copyright (c) Facebook, Inc. and its affiliates.
38 | *
39 | * This source code is licensed under the MIT license found in the
40 | * LICENSE file in the root directory of this source tree.
41 | */
42 |
--------------------------------------------------------------------------------
/meetjs-react/build/static/js/main.3d8c5fc6.chunk.js:
--------------------------------------------------------------------------------
1 | (this["webpackJsonpmeetjs-react"]=this["webpackJsonpmeetjs-react"]||[]).push([[0],{13:function(e,t,n){e.exports=n.p+"static/media/logo.acb38cf1.svg"},32:function(e,t,n){e.exports=n.p+"static/media/mic-on.5ce2865f.svg"},33:function(e,t,n){e.exports=n.p+"static/media/mic-off.99246803.svg"},34:function(e,t,n){e.exports=n.p+"static/media/cam-on.a9c7e91b.svg"},35:function(e,t,n){e.exports=n.p+"static/media/cam-off.60765bda.svg"},36:function(e,t,n){e.exports=n.p+"static/media/view.d319b0ef.svg"},39:function(e,t,n){e.exports=n(71)},44:function(e,t,n){},45:function(e,t,n){},69:function(e,t,n){},70:function(e,t,n){},71:function(e,t,n){"use strict";n.r(t);var a=n(0),r=n.n(a),c=n(30),o=n.n(c),s=(n(44),n(9)),i=n(1),u=(n(45),n(13)),l=n.n(u),_=n(16),m=n.n(_);function d(e){return m.a.get("".concat("https://meetjs.tk","/connect"),{params:{url:e}})}var f,p=function(){var e=Object(a.useState)(""),t=Object(i.a)(e,2),n=t[0],c=t[1],o=Object(a.useState)(""),u=Object(i.a)(o,2),_=u[0],d=u[1],f=Object(a.useState)(""),p=Object(i.a)(f,2),b=p[0],h=p[1],v=Object(s.e)();return r.a.createElement("div",{className:"container"},r.a.createElement("div",{className:"header"},r.a.createElement("div",{className:"header__left"},r.a.createElement("img",{src:l.a,alt:"Logo",className:"header__left__logo"}),r.a.createElement("span",{className:"header__left__text"},"MeetJS")),r.a.createElement("div",{className:"header__right"},r.a.createElement("a",{href:"https://github.com/thealmarques/meetjs-webrtc-golang",className:"header__right__margin"},"Github"))),r.a.createElement("div",{className:"body"},r.a.createElement("span",{className:"body__title"},"Do you want a free video chat platform?"),r.a.createElement("span",{className:"body__sub-title"},"Get you own URL and share it with others to join you."),r.a.createElement("form",{onSubmit:function(e){e.preventDefault(),function(e,t,n){return m.a.post("".concat("https://meetjs.tk","/session"),JSON.stringify({title:t,host:e,password:n}))}(n,_,b).then((function(e){v.push("/meeting/".concat(e.data.socket))})).catch((function(e){console.error(e.message)}))},className:"body__form"},r.a.createElement("div",{className:"body__form__inputs"},r.a.createElement("input",{placeholder:"Host",className:"body__form__inputs__input",type:"text",value:n,onChange:function(e){return c(e.target.value)},required:!0,autoComplete:"on"}),r.a.createElement("input",{placeholder:"Title",className:"body__form__inputs__input",type:"text",value:_,onChange:function(e){return d(e.target.value)},required:!0,autoComplete:"on"}),r.a.createElement("input",{placeholder:"Password",className:"body__form__inputs__input",type:"password",value:b,onChange:function(e){return h(e.target.value)},required:!0,autoComplete:"on"})),r.a.createElement("div",{className:"body__form__create"},r.a.createElement("button",{className:"body__form__create__btn",type:"submit"},"Create"),r.a.createElement("span",{className:"body__form__create__small-text"},"Secure and simple")))))},b=n(37),h=n(2),v=n.n(h),g=n(8),y=n(32),O=n.n(y),E=n(33),N=n.n(E),j=n(34),w=n.n(j),D=n(35),I=n.n(D),k=n(36),C=n.n(k),S=(n(69),function(e){var t=Object(a.useState)(""),n=Object(i.a)(t,2),c=n[0],o=n[1],s=Object(a.useState)(""),u=Object(i.a)(s,2),l=u[0],_=u[1],m=Object(a.useState)(!1),d=Object(i.a)(m,2),f=d[0],p=d[1];return r.a.createElement("div",{className:"modal"},r.a.createElement("div",{className:"modal__wrapper"},r.a.createElement("div",{className:"modal__wrapper__body"},r.a.createElement("form",{onSubmit:function(t){t.preventDefault(),e.connect(c,l),p(!0)},className:"modal__wrapper__body__form",action:"submit"},r.a.createElement("span",{className:"modal__wrapper__body__form__small-text"},"Connect with your friends"),r.a.createElement("span",{className:"modal__wrapper__body__form__big-text"},"Enter your session credentials"),r.a.createElement("input",{placeholder:"Host",className:"modal__wrapper__body__form__input",type:"text",value:c,onChange:function(e){return o(e.target.value)},required:!0,autoComplete:"on"}),r.a.createElement("input",{placeholder:"Password",className:"modal__wrapper__body__form__input",value:l,onChange:function(e){return _(e.target.value)},type:"password",required:!0,autoComplete:"on"}),r.a.createElement("button",{className:"modal__wrapper__body__form__connect",type:"submit"},"Connect"),e.error&&f&&r.a.createElement("span",{className:"modal__wrapper__body__form-error"},"Invalid credentials")))))}),x=(n(70),n(14)),J=n(15),L=function(){function e(t){Object(x.a)(this,e),this.connection=void 0,this.connection=new WebSocket(t)}return Object(J.a)(e,[{key:"disconnect",value:function(){this.connection.close()}},{key:"onOpen",value:function(e){this.connection.onopen=e}},{key:"send",value:function(e,t){this.connection.send(JSON.stringify({type:e,userID:t}))}},{key:"sendDescription",value:function(e,t,n,a){this.connection.send(JSON.stringify({type:e,userID:n,description:JSON.stringify(t),to:a}))}},{key:"candidate",value:function(e,t){this.connection.send(JSON.stringify({candidate:JSON.stringify(e),userID:t,type:"ice"}))}},{key:"onMessage",value:function(e){this.connection.onmessage=e}},{key:"isReady",value:function(){return this.connection.readyState===this.connection.OPEN}}]),e}();function R(e){return("0"+e.toString(16)).slice(-2)}!function(e){e[e.VALID_URL=1]="VALID_URL",e[e.INVALID=2]="INVALID",e[e.LOGGED=3]="LOGGED",e[e.JOINED=4]="JOINED"}(f||(f={}));var A={iceServers:[{urls:"stun:stun3.l.google.com:19302"},{urls:"stun:stun4.l.google.com:19302"}]},G=function(){function e(t,n){var a=this;Object(x.a)(this,e),this.servers=void 0,this.stream=void 0,this.peerConnection=void 0,this.servers=t,this.stream=n,this.peerConnection=new RTCPeerConnection(t),this.stream.getTracks().forEach((function(e){a.peerConnection.addTrack(e)}))}return Object(J.a)(e,[{key:"createOffer",value:function(){var e=Object(g.a)(v.a.mark((function e(){var t;return v.a.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.next=2,this.peerConnection.createOffer();case 2:return t=e.sent,this.peerConnection.setLocalDescription(t),e.abrupt("return",t);case 5:case"end":return e.stop()}}),e,this)})));return function(){return e.apply(this,arguments)}}()},{key:"createAnswer",value:function(){var e=Object(g.a)(v.a.mark((function e(){var t;return v.a.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.next=2,this.peerConnection.createAnswer();case 2:return t=e.sent,this.peerConnection.setLocalDescription(t),e.abrupt("return",t);case 5:case"end":return e.stop()}}),e,this)})));return function(){return e.apply(this,arguments)}}()},{key:"receiveAnswer",value:function(){var e=Object(g.a)(v.a.mark((function e(t){return v.a.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:if(!t){e.next=3;break}return e.next=3,this.peerConnection.setRemoteDescription(new RTCSessionDescription(t));case 3:case"end":return e.stop()}}),e,this)})));return function(t){return e.apply(this,arguments)}}()},{key:"addCandidate",value:function(){var e=Object(g.a)(v.a.mark((function e(t){return v.a.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.next=2,this.peerConnection.addIceCandidate(t);case 2:case"end":return e.stop()}}),e,this)})));return function(t){return e.apply(this,arguments)}}()},{key:"onTrack",value:function(e){this.peerConnection.ontrack=e}},{key:"onIceCandidate",value:function(e){this.peerConnection.onicecandidate=e}},{key:"setRemoteDescription",value:function(){var e=Object(g.a)(v.a.mark((function e(t){return v.a.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.next=2,this.peerConnection.setRemoteDescription(t);case 2:case"end":return e.stop()}}),e,this)})));return function(t){return e.apply(this,arguments)}}()},{key:"addIceCandidate",value:function(e){this.peerConnection.addIceCandidate(e)}},{key:"disconnect",value:function(){this.peerConnection.close()}}]),e}(),T=n(38),V=function(){var e=Object(s.f)(),t=Object(a.useState)(f.INVALID),n=Object(i.a)(t,2),c=n[0],o=n[1],u=Object(a.useState)(""),_=Object(i.a)(u,2),p=_[0],h=_[1],y=Object(a.useState)(),E=Object(i.a)(y,2),j=E[0],D=E[1],k=Object(a.useState)(),x=Object(i.a)(k,2),J=x[0],V=x[1],M=Object(a.useState)(!1),q=Object(i.a)(M,2),U=q[0],P=q[1],W=Object(a.useState)(),B=Object(i.a)(W,2),H=B[0],$=B[1],z=Object(a.useState)([]),F=Object(i.a)(z,2),K=F[0],Q=F[1],X=Object(a.useState)(""),Y=Object(i.a)(X,2),Z=Y[0],ee=Y[1],te=Object(a.useState)(e.pathname.split("/meeting/")[1]),ne=Object(i.a)(te,1)[0],ae=Object(a.useState)(),re=Object(i.a)(ae,2),ce=re[0],oe=re[1],se=Object(a.useRef)(null),ie=Object(a.useRef)(function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:40,t=new Uint8Array(e/2);return window.crypto.getRandomValues(t),Array.from(t,R).join("")}());Object(T.a)((function(e){e.preventDefault(),H&&H.send("disconnect",ie.current)})),Object(a.useEffect)((function(){var e=!1;return function(){var t=Object(g.a)(v.a.mark((function t(){return v.a.wrap((function(t){for(;;)switch(t.prev=t.next){case 0:return t.prev=0,t.next=3,d(ne);case 3:e||o(f.VALID_URL),t.next=11;break;case 6:t.prev=6,t.t0=t.catch(0),console.log(t.t0.message),console.error("Invalid URL"),window.location.href="/";case 11:case"end":return t.stop()}}),t,null,[[0,6]])})));return function(){return t.apply(this,arguments)}}()(),function(){e=!0}}),[ne]);return Object(a.useEffect)((function(){c>=3&&se.current&&!se.current.srcObject&&navigator.mediaDevices.getUserMedia({audio:!0,video:!0}).then((function(e){se.current&&(se.current.srcObject=e),oe(e),D(!0),V(!0)})).catch((function(e){console.log(e)}))}),[c]),Object(a.useEffect)((function(){ce&&ce.getTracks().forEach((function(e){"audio"===e.kind&&(e.enabled=!e.enabled)}))}),[ce,j]),Object(a.useEffect)((function(){ce&&ce.getTracks().forEach((function(e){"video"===e.kind&&(e.enabled=!e.enabled)}))}),[ce,J]),Object(a.useEffect)((function(){3===c&&H&&(H.send("disconnect",ie.current),Q([]))}),[c,H]),Object(a.useEffect)((function(){if(c>3){var e=new L("".concat("wss://meetjs.tk/ws","/").concat(Z)),t=[],n=[];e.onOpen((function(){e.send("connect",ie.current)})),e.onMessage((function(a){var r=JSON.parse(a.data);switch(n[r.userID]||ie.current===r.userID||(t[r.userID]||(t[r.userID]=new MediaStream),se.current&&se.current.srcObject&&(n[r.userID]=new G(A,se.current.srcObject)),n[r.userID].onIceCandidate((function(t){t.candidate&&e.candidate(t.candidate,ie.current)})),n[r.userID].onTrack((function(e){var n=t[r.userID];n.addTrack(e.track),document.getElementById("".concat(r.userID,"-video")).srcObject=n}))),r.type){case"session_joined":e.send("start_call",ie.current);break;case"start_call":r.userID!==ie.current&&(Q((function(e){return e.indexOf(r.userID)<0?e.concat(r.userID):e})),n[r.userID].createOffer().then((function(t){e.sendDescription("offer",t,ie.current,r.userID)})));break;case"offer":r.to===ie.current&&(Q((function(e){return e.indexOf(r.userID)<0?e.concat(r.userID):e})),n[r.userID].setRemoteDescription(new RTCSessionDescription(JSON.parse(r.description))).then(Object(g.a)(v.a.mark((function t(){var a;return v.a.wrap((function(t){for(;;)switch(t.prev=t.next){case 0:return t.next=2,n[r.userID].createAnswer();case 2:a=t.sent,e.sendDescription("answer",a,ie.current,r.userID);case 4:case"end":return t.stop()}}),t)})))));break;case"answer":r.to===ie.current&&n[r.userID].receiveAnswer(JSON.parse(r.description));break;case"ice":ie.current!==r.userID&&r.candidate&&n[r.userID].addIceCandidate(JSON.parse(r.candidate));break;case"disconnect":r.userID!==ie.current&&(Q((function(e){var t=Object(b.a)(e);return t.splice(t.indexOf(r.userID),1),t})),t[r.userID]=void 0,n[r.userID]=void 0)}})),$(e)}}),[c,Z]),r.a.createElement("div",{className:"meeting"},r.a.createElement("div",{className:"meeting__header"},r.a.createElement("div",{className:"meeting__header__left"},r.a.createElement("img",{src:l.a,alt:"Logo",className:"meeting__header__left__logo"}),r.a.createElement("span",{className:"meeting__header__left__text"},"MeetJS")),r.a.createElement("div",{className:"meeting__header__right"},r.a.createElement("a",{href:"https://github.com/thealmarques/meetjs-webrtc-golang",className:"meeting__header__right__margin"},"Github"))),r.a.createElement("div",{className:"meeting__body"},c<=2&&r.a.createElement(S,{error:c===f.INVALID,connect:function(e,t){(function(e,t,n){return m.a.post("".concat("https://meetjs.tk","/connect/").concat(n),JSON.stringify({host:e,password:t}))})(e,t,ne).then((function(e){e.data.title&&h(e.data.title),e.data.socket&&ee(e.data.socket),o(f.LOGGED)})).catch((function(e){o(f.INVALID)}))}}),c===f.LOGGED&&r.a.createElement("div",{className:"meeting__body__info"},"Join the meeting to communicate with others."),c===f.JOINED&&r.a.createElement("div",{className:"meeting__body__users ".concat(U?"meeting__body__users-row":"meeting__body__users-column")},K.map((function(e,t){return r.a.createElement("video",{key:t,id:"".concat(e,"-video"),autoPlay:!0,className:"meeting__body__users__remote-video ".concat(U?"meeting__body__users__remote-video-split":"")})}))),c>2&&r.a.createElement("div",{className:"meeting__body__bar"},r.a.createElement("div",{className:"meeting__body__bar__video-container"},r.a.createElement("video",{hidden:!J,ref:se,autoPlay:!0,muted:!0,className:"meeting__body__bar__video-container__local-video",id:"local-video"})),r.a.createElement("div",{className:"meeting__body__bar__left-bar"},r.a.createElement("div",{onClick:function(){switch(c){case f.LOGGED:o(f.JOINED);break;case f.JOINED:o(f.LOGGED)}},className:"meeting__body__bar__left-bar__status ".concat(c===f.JOINED?"meeting__body__bar__left-bar__status-inactive":"meeting__body__bar__left-bar__status-active")},c===f.JOINED?"Leave":"Join"),r.a.createElement("span",{className:"meeting__body__bar__left-bar__title"},p)),r.a.createElement("div",{className:"meeting__body__bar__right-bar"},c===f.JOINED&&r.a.createElement("span",{className:"meeting__body__bar__right-bar__count"},K.length," online users"),r.a.createElement("img",{className:"meeting__body__bar__right-bar__icon",src:C.a,alt:"Change view",onClick:function(){return P(!U)}}),r.a.createElement("img",{className:"meeting__body__bar__right-bar__icon",src:j?O.a:N.a,alt:"Mic",onClick:function(){return D(!j)}}),r.a.createElement("img",{className:"meeting__body__bar__right-bar__icon",src:J?w.a:I.a,alt:"Webcam",onClick:function(){return V(!J)}})))))},M=n(7);var q=function(){var e=Object(M.a)();return r.a.createElement("div",{className:"app"},r.a.createElement(s.c,{history:e},r.a.createElement(s.d,null,r.a.createElement(s.b,{exact:!0,path:"/meeting/*"},r.a.createElement(V,null)),r.a.createElement(s.b,{exact:!0,path:"/"},r.a.createElement(p,null)),r.a.createElement(s.b,{path:"*"},r.a.createElement(s.a,{push:!0,to:"/"})))))};Boolean("localhost"===window.location.hostname||"[::1]"===window.location.hostname||window.location.hostname.match(/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/));o.a.render(r.a.createElement(r.a.StrictMode,null,r.a.createElement(q,null)),document.getElementById("root")),"serviceWorker"in navigator&&navigator.serviceWorker.ready.then((function(e){e.unregister()})).catch((function(e){console.error(e.message)}))}},[[39,1,2]]]);
2 | //# sourceMappingURL=main.3d8c5fc6.chunk.js.map
--------------------------------------------------------------------------------
/meetjs-react/build/static/js/main.3d8c5fc6.chunk.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"sources":["assets/images/logo.svg","assets/images/mic-on.svg","assets/images/mic-off.svg","assets/images/cam-on.svg","assets/images/cam-off.svg","assets/images/view.svg","services/api.services.tsx","components/home/home.tsx","components/state-machine/state-machine.tsx","components/session-modal/modal.tsx","services/web-socket.services.tsx","utils/identifier.utils.ts","constants/stun-servers.ts","utils/easy-rtc.ts","components/meeting/meeting.tsx","App.tsx","serviceWorker.ts","index.tsx"],"names":["module","exports","verifySocket","url","Axios","get","process","params","State","Home","useState","host","setHost","title","setTitle","password","setPassword","history","useHistory","className","src","logo","alt","href","onSubmit","event","preventDefault","post","JSON","stringify","createSession","then","response","push","data","socket","catch","error","console","message","placeholder","type","value","onChange","target","required","autoComplete","SessionCredentials","props","submitted","setSubmitted","connect","action","ConnectionSocket","connection","this","WebSocket","close","callback","onopen","userID","send","description","to","candidate","onmessage","readyState","OPEN","byteToHex","byte","toString","slice","STUN_SERVERS","iceServers","urls","EasyRTC","servers","stream","peerConnection","RTCPeerConnection","getTracks","forEach","track","addTrack","createOffer","sessionDescription","setLocalDescription","createAnswer","setRemoteDescription","RTCSessionDescription","addIceCandidate","ontrack","onicecandidate","Meeting","location","useLocation","INVALID","state","setState","audio","setAudio","video","setVideo","viewMode","setViewMode","setConnection","users","setUsers","setSocket","pathname","split","localStream","setLocalStream","localVideo","useRef","userId","len","arr","Uint8Array","window","crypto","getRandomValues","Array","from","join","generateId","useBeforeunload","current","useEffect","unmounted","a","VALID_URL","log","getValidity","srcObject","navigator","mediaDevices","getUserMedia","kind","enabled","socketConnection","remoteStreams","rtcPeerConnection","onOpen","onMessage","parse","MediaStream","onIceCandidate","onTrack","mediaStream","document","getElementById","prevState","indexOf","concat","sendDescription","answer","receiveAnswer","prevItems","splice","undefined","connectSession","LOGGED","JOINED","map","user","index","key","id","autoPlay","hidden","ref","muted","onClick","length","view","micOn","micOff","camOn","camOff","App","createBrowserHistory","exact","path","Boolean","hostname","match","ReactDOM","render","StrictMode","serviceWorker","ready","registration","unregister"],"mappings":"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","file":"static/js/main.3d8c5fc6.chunk.js","sourcesContent":["module.exports = __webpack_public_path__ + \"static/media/logo.acb38cf1.svg\";","module.exports = __webpack_public_path__ + \"static/media/mic-on.5ce2865f.svg\";","module.exports = __webpack_public_path__ + \"static/media/mic-off.99246803.svg\";","module.exports = __webpack_public_path__ + \"static/media/cam-on.a9c7e91b.svg\";","module.exports = __webpack_public_path__ + \"static/media/cam-off.60765bda.svg\";","module.exports = __webpack_public_path__ + \"static/media/view.d319b0ef.svg\";","import Axios from \"axios\";\nimport { ResponseData } from \"../interfaces/response-data\";\n\nexport function createSession(host: string, title: string, password: string): Promise {\n return Axios.post(`${process.env.REACT_APP_SERVER}/session`, JSON.stringify({\n title,\n host,\n password\n })\n );\n}\n\nexport function connectSession(host: string, password: string, socket: string): Promise {\n return Axios.post(`${process.env.REACT_APP_SERVER}/connect/${socket}`, JSON.stringify({\n host,\n password\n })\n );\n}\n\nexport function verifySocket(url: string): Promise {\n return Axios.get(`${process.env.REACT_APP_SERVER}/connect`, {\n params: {\n url\n }\n });\n}","import React, { useState } from 'react';\nimport { useHistory } from \"react-router\";\nimport './home.scss';\nimport logo from '../../assets/images/logo.svg';\nimport { createSession } from '../../services/api.services';\nimport { ResponseData } from '../../interfaces/response-data';\n\nexport const Home = () => {\n const [host, setHost] = useState('');\n const [title, setTitle] = useState('');\n const [password, setPassword] = useState('');\n const history = useHistory();\n\n const onSubmit = (event: React.FormEvent) => {\n event.preventDefault();\n createSession(host, title, password).then((response: Response & ResponseData) => {\n history.push(`/meeting/${response.data.socket}`);\n }).catch((error: Error) => {\n console.error(error.message)\n });\n }\n\n return (\n \n
\n
\n

\n
MeetJS\n
\n
\n
\n
\n
Do you want a free video chat platform?\n
Get you own URL and share it with others to join you.\n
\n
\n
\n )\n}\n","export enum State {\n VALID_URL = 1,\n INVALID,\n LOGGED,\n JOINED\n}","import React, { useState } from 'react'\nimport './modal.scss';\n\ninterface Props {\n connect: (host: string, password: string) => void;\n error: boolean;\n}\n\nexport const SessionCredentials = (props: Props) => {\n const [host, setHost] = useState('');\n const [password, setPassword] = useState('');\n const [submitted, setSubmitted] = useState(false);\n\n const onSubmit = (event: React.FormEvent) => {\n event.preventDefault();\n props.connect(host, password);\n setSubmitted(true);\n }\n\n return (\n \n )\n}\n","import { SocketEvent } from \"../interfaces/socket-data\";\n\nexport class ConnectionSocket {\n private connection: WebSocket;\n\n constructor(url: string) {\n this.connection = new WebSocket(url);\n }\n\n /**\n * Disconnect\n */\n disconnect() {\n this.connection.close();\n }\n \n /**\n * On open connection event.\n * @param callback handles on connection ready.\n */\n onOpen(callback: () => void) {\n this.connection.onopen = callback;\n }\n\n /**\n * Sends message through web sockets.\n * @param data data to send\n * @param type event type\n * @param userID user id\n * @param offer RTC offer\n */\n send(type: string, userID: string) {\n this.connection.send(JSON.stringify({\n type,\n userID\n }));\n }\n\n sendDescription(type: string, description: RTCSessionDescriptionInit, userID: string, to: string) {\n this.connection.send(JSON.stringify({\n type,\n userID,\n description: JSON.stringify(description),\n to\n }));\n }\n\n candidate(candidate: RTCIceCandidate, userID: string) {\n this.connection.send(JSON.stringify({\n candidate: JSON.stringify(candidate),\n userID,\n type: 'ice'\n }));\n }\n\n /**\n * On message received.\n * @param callback method to handle on message function.\n */\n onMessage(callback: (event: Event & SocketEvent) => void) {\n this.connection.onmessage = callback;\n }\n\n /**\n * Checks if connection is ready.\n */\n isReady(): boolean {\n return this.connection.readyState === this.connection.OPEN;\n }\n}","export function byteToHex(byte) {\n return ('0' + byte.toString(16)).slice(-2);\n}\n\nexport function generateId(len = 40) {\n var arr = new Uint8Array(len / 2);\n window.crypto.getRandomValues(arr);\n return Array.from(arr, byteToHex).join(\"\");\n}","export const STUN_SERVERS = {\n iceServers: [\n { urls: 'stun:stun3.l.google.com:19302' },\n { urls: 'stun:stun4.l.google.com:19302' },\n ],\n}","import { StunServers } from \"../interfaces/stun-servers\";\n\nexport class EasyRTC {\n servers: StunServers;\n stream: MediaStream;\n peerConnection: RTCPeerConnection;\n\n constructor(servers: StunServers, stream: MediaStream) {\n this.servers = servers;\n this.stream = stream;\n\n this.peerConnection = new RTCPeerConnection(servers);\n\n this.stream.getTracks().forEach((track: MediaStreamTrack) => {\n this.peerConnection.addTrack(track);\n });\n }\n\n /**\n * Creates offer to send to remote peers.\n */\n async createOffer() {\n let sessionDescription: RTCSessionDescriptionInit;\n sessionDescription = await this.peerConnection.createOffer();\n this.peerConnection.setLocalDescription(sessionDescription);\n \n return sessionDescription;\n }\n\n /**\n * Creates answer when offer is received.\n */\n async createAnswer() {\n let sessionDescription: RTCSessionDescriptionInit;\n sessionDescription = await this.peerConnection.createAnswer();\n this.peerConnection.setLocalDescription(sessionDescription)\n\n return sessionDescription;\n }\n\n /**\n * Receives answer from remote peers.\n */\n async receiveAnswer(event: RTCSessionDescriptionInit | undefined) {\n if (event) {\n await this.peerConnection.setRemoteDescription(new RTCSessionDescription(event))\n }\n }\n\n /**\n * \n * @param candidate \n */\n async addCandidate(candidate: RTCIceCandidate) {\n await this.peerConnection.addIceCandidate(candidate);\n }\n\n onTrack(callback: (event: RTCTrackEvent) => void) {\n this.peerConnection.ontrack = callback ;\n }\n\n onIceCandidate(callback: (event: RTCPeerConnectionIceEvent) => void) {\n this.peerConnection.onicecandidate = callback ;\n }\n\n async setRemoteDescription(description: RTCSessionDescriptionInit) {\n await this.peerConnection.setRemoteDescription(description);\n }\n\n addIceCandidate(candidate: RTCIceCandidate) {\n this.peerConnection.addIceCandidate(candidate);\n }\n\n disconnect() {\n this.peerConnection.close();\n }\n}","import React, { useEffect, useState, useRef } from 'react'\nimport { useLocation } from \"react-router-dom\";\nimport { verifySocket, connectSession } from '../../services/api.services';\nimport logo from '../../assets/images/logo.svg';\nimport micOn from '../../assets/images/mic-on.svg';\nimport micOff from '../../assets/images/mic-off.svg';\nimport camOn from '../../assets/images/cam-on.svg';\nimport camOff from '../../assets/images/cam-off.svg';\nimport view from '../../assets/images/view.svg';\nimport { SessionCredentials } from '../session-modal/modal';\nimport './meeting.scss';\nimport { ResponseData } from '../../interfaces/response-data';\nimport { ConnectionSocket } from '../../services/web-socket.services';\nimport { generateId } from '../../utils/identifier.utils';\nimport { SocketEvent, SocketResponse } from '../../interfaces/socket-data';\nimport { State } from '../state-machine/state-machine';\nimport { STUN_SERVERS } from '../../constants/stun-servers';\nimport { EasyRTC } from '../../utils/easy-rtc';\nimport { useBeforeunload } from 'react-beforeunload';\n\nexport const Meeting = () => {\n const location = useLocation();\n const [state, setState] = useState(State.INVALID);\n const [title, setTitle] = useState('');\n const [audio, setAudio] = useState();\n const [video, setVideo] = useState();\n const [viewMode, setViewMode] = useState(false);\n const [connection, setConnection] = useState();\n const [users, setUsers] = useState([]);\n const [socket, setSocket] = useState('');\n const [url] = useState(location.pathname.split('/meeting/')[1]);\n const [localStream, setLocalStream] = useState();\n const localVideo = useRef(null);\n const userId = useRef(generateId());\n\n useBeforeunload((event: Event) => {\n event.preventDefault();\n if (connection) {\n connection.send('disconnect', userId.current);\n }\n });\n\n useEffect(() => {\n let unmounted = false;\n const getValidity = async () => {\n try {\n await verifySocket(url);\n if (!unmounted) {\n setState(State.VALID_URL);\n }\n } catch (error) {\n console.log(error.message);\n console.error('Invalid URL');\n window.location.href = '/';\n }\n }\n getValidity();\n return () => {\n unmounted = true;\n };\n }, [url])\n\n const connect = (host: string, password: string) => {\n connectSession(host, password, url).then((response: Response & ResponseData) => {\n if (response.data.title) {\n setTitle(response.data.title);\n }\n\n if (response.data.socket) {\n setSocket(response.data.socket);\n }\n\n setState(State.LOGGED);\n }).catch((error) => {\n setState(State.INVALID);\n });\n }\n\n const joinMeeting = () => {\n switch (state) {\n case State.LOGGED:\n setState(State.JOINED);\n break;\n case State.JOINED:\n setState(State.LOGGED);\n break;\n }\n }\n\n useEffect(() => {\n if (state >= 3) {\n if (localVideo.current && !localVideo.current.srcObject) {\n navigator.mediaDevices.getUserMedia(\n { audio: true, video: true }\n ).then(stream => {\n if (localVideo.current) {\n localVideo.current.srcObject = stream;\n }\n setLocalStream(stream);\n\n setAudio(true);\n setVideo(true);\n }).catch(error => {\n console.log(error);\n });\n }\n }\n }, [state]);\n\n useEffect(() => {\n if (localStream) {\n const media: MediaStream = localStream;\n media.getTracks().forEach((track: MediaStreamTrack) => {\n if (track.kind === 'audio') {\n track.enabled = !track.enabled;\n }\n });\n }\n }, [localStream, audio]);\n\n useEffect(() => {\n if (localStream) {\n const media: MediaStream = localStream;\n media.getTracks().forEach((track: MediaStreamTrack) => {\n if (track.kind === 'video') {\n track.enabled = !track.enabled;\n }\n });\n }\n }, [localStream, video]);\n\n useEffect(() => {\n if (state === 3 && connection) {\n connection.send('disconnect', userId.current);\n setUsers([]);\n }\n }, [state, connection]);\n\n useEffect(() => {\n if (state > 3) {\n const socketConnection = new ConnectionSocket(`${process.env.REACT_APP_WS_URL}/${socket}`);\n const remoteStreams: MediaStream[] = [];\n const rtcPeerConnection: EasyRTC[] = [];\n\n const onConnectionReady = () => {\n socketConnection.send('connect', userId.current);\n }\n\n const onMessage = (event: Event & SocketEvent) => {\n const data: SocketResponse = JSON.parse(event.data);\n\n if (!rtcPeerConnection[data.userID] && userId.current !== data.userID) {\n if (!remoteStreams[data.userID]) {\n remoteStreams[data.userID] = new MediaStream();\n }\n\n if (localVideo.current && localVideo.current.srcObject) {\n rtcPeerConnection[data.userID] = new EasyRTC(STUN_SERVERS, localVideo.current.srcObject as MediaStream);\n }\n\n rtcPeerConnection[data.userID].onIceCandidate((event: RTCPeerConnectionIceEvent) => {\n if (event.candidate) {\n socketConnection.candidate(event.candidate, userId.current);\n }\n });\n\n rtcPeerConnection[data.userID].onTrack((event: RTCTrackEvent) => {\n const mediaStream: MediaStream = remoteStreams[data.userID];\n mediaStream.addTrack(event.track);\n\n const element = document.getElementById(`${data.userID}-video`) as HTMLMediaElement;\n element.srcObject = mediaStream;\n });\n }\n\n switch (data.type) {\n case 'session_joined':\n socketConnection.send('start_call', userId.current);\n break;\n case 'start_call':\n if (data.userID !== userId.current) {\n setUsers((prevState: string[]) => prevState.indexOf(data.userID) < 0 ? prevState.concat(data.userID) : prevState);\n rtcPeerConnection[data.userID].createOffer().then((description: RTCSessionDescriptionInit) => {\n socketConnection.sendDescription('offer', description, userId.current, data.userID);\n });\n }\n break;\n case 'offer':\n if (data.to === userId.current) {\n setUsers((prevState: string[]) => prevState.indexOf(data.userID) < 0 ? prevState.concat(data.userID) : prevState);\n rtcPeerConnection[data.userID].setRemoteDescription(new RTCSessionDescription(JSON.parse(data.description))).then(async () => {\n const answer = await rtcPeerConnection[data.userID].createAnswer();\n socketConnection.sendDescription('answer', answer, userId.current, data.userID);\n });\n }\n break;\n case 'answer':\n if (data.to === userId.current) {\n rtcPeerConnection[data.userID].receiveAnswer(JSON.parse(data.description));\n }\n break;\n case 'ice':\n if (userId.current !== data.userID && data.candidate) {\n rtcPeerConnection[data.userID].addIceCandidate(JSON.parse(data.candidate));\n }\n break\n case 'disconnect':\n if (data.userID !== userId.current) {\n setUsers(prevItems => {\n const users = [...prevItems];\n users.splice(users.indexOf(data.userID), 1);\n return users;\n });\n remoteStreams[data.userID] = undefined;\n rtcPeerConnection[data.userID] = undefined;\n }\n break;\n }\n }\n\n socketConnection.onOpen(onConnectionReady);\n socketConnection.onMessage(onMessage);\n setConnection(socketConnection);\n }\n }, [state, socket])\n\n return (\n \n
\n
\n

\n
MeetJS\n
\n
\n
\n
\n {state <= 2 &&
}\n {state === State.LOGGED &&\n (\n
Join the meeting to communicate with others.
\n )\n }\n {state === State.JOINED && (\n
\n {\n users.map((user, index) => {\n return (\n \n )\n })\n }\n
\n )}\n {state > 2 &&\n (\n
\n
\n \n
\n
\n
\n {state === State.JOINED ? 'Leave' : 'Join'}\n
\n
{title}\n
\n
\n {\n state === State.JOINED &&\n
{users.length} online users\n }\n

setViewMode(!viewMode)} />\n

setAudio(!audio)} />\n

setVideo(!video)} />\n
\n
\n )\n }\n
\n
\n )\n}","import React from 'react';\nimport './App.scss';\nimport { Router, Redirect, Route, Switch } from \"react-router\";\nimport { Home } from './components/home/home';\nimport { Meeting } from './components/meeting/meeting';\nimport { createBrowserHistory } from \"history\";\n\nfunction App() {\n const history = createBrowserHistory();\n\n return (\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
\n );\n}\n\nexport default App;\n","// This optional code is used to register a service worker.\n// register() is not called by default.\n\n// This lets the app load faster on subsequent visits in production, and gives\n// it offline capabilities. However, it also means that developers (and users)\n// will only see deployed updates on subsequent visits to a page, after all the\n// existing tabs open on the page have been closed, since previously cached\n// resources are updated in the background.\n\n// To learn more about the benefits of this model and instructions on how to\n// opt-in, read https://bit.ly/CRA-PWA\n\nconst isLocalhost = Boolean(\n window.location.hostname === 'localhost' ||\n // [::1] is the IPv6 localhost address.\n window.location.hostname === '[::1]' ||\n // 127.0.0.0/8 are considered localhost for IPv4.\n window.location.hostname.match(\n /^127(?:\\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/\n )\n);\n\ntype Config = {\n onSuccess?: (registration: ServiceWorkerRegistration) => void;\n onUpdate?: (registration: ServiceWorkerRegistration) => void;\n};\n\nexport function register(config?: Config) {\n if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {\n // The URL constructor is available in all browsers that support SW.\n const publicUrl = new URL(\n process.env.PUBLIC_URL,\n window.location.href\n );\n if (publicUrl.origin !== window.location.origin) {\n // Our service worker won't work if PUBLIC_URL is on a different origin\n // from what our page is served on. This might happen if a CDN is used to\n // serve assets; see https://github.com/facebook/create-react-app/issues/2374\n return;\n }\n\n window.addEventListener('load', () => {\n const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;\n\n if (isLocalhost) {\n // This is running on localhost. Let's check if a service worker still exists or not.\n checkValidServiceWorker(swUrl, config);\n\n // Add some additional logging to localhost, pointing developers to the\n // service worker/PWA documentation.\n navigator.serviceWorker.ready.then(() => {\n console.log(\n 'This web app is being served cache-first by a service ' +\n 'worker. To learn more, visit https://bit.ly/CRA-PWA'\n );\n });\n } else {\n // Is not localhost. Just register service worker\n registerValidSW(swUrl, config);\n }\n });\n }\n}\n\nfunction registerValidSW(swUrl: string, config?: Config) {\n navigator.serviceWorker\n .register(swUrl)\n .then(registration => {\n registration.onupdatefound = () => {\n const installingWorker = registration.installing;\n if (installingWorker == null) {\n return;\n }\n installingWorker.onstatechange = () => {\n if (installingWorker.state === 'installed') {\n if (navigator.serviceWorker.controller) {\n // At this point, the updated precached content has been fetched,\n // but the previous service worker will still serve the older\n // content until all client tabs are closed.\n console.log(\n 'New content is available and will be used when all ' +\n 'tabs for this page are closed. See https://bit.ly/CRA-PWA.'\n );\n\n // Execute callback\n if (config && config.onUpdate) {\n config.onUpdate(registration);\n }\n } else {\n // At this point, everything has been precached.\n // It's the perfect time to display a\n // \"Content is cached for offline use.\" message.\n console.log('Content is cached for offline use.');\n\n // Execute callback\n if (config && config.onSuccess) {\n config.onSuccess(registration);\n }\n }\n }\n };\n };\n })\n .catch(error => {\n console.error('Error during service worker registration:', error);\n });\n}\n\nfunction checkValidServiceWorker(swUrl: string, config?: Config) {\n // Check if the service worker can be found. If it can't reload the page.\n fetch(swUrl, {\n headers: { 'Service-Worker': 'script' }\n })\n .then(response => {\n // Ensure service worker exists, and that we really are getting a JS file.\n const contentType = response.headers.get('content-type');\n if (\n response.status === 404 ||\n (contentType != null && contentType.indexOf('javascript') === -1)\n ) {\n // No service worker found. Probably a different app. Reload the page.\n navigator.serviceWorker.ready.then(registration => {\n registration.unregister().then(() => {\n window.location.reload();\n });\n });\n } else {\n // Service worker found. Proceed as normal.\n registerValidSW(swUrl, config);\n }\n })\n .catch(() => {\n console.log(\n 'No internet connection found. App is running in offline mode.'\n );\n });\n}\n\nexport function unregister() {\n if ('serviceWorker' in navigator) {\n navigator.serviceWorker.ready\n .then(registration => {\n registration.unregister();\n })\n .catch(error => {\n console.error(error.message);\n });\n }\n}\n","import React from 'react';\nimport ReactDOM from 'react-dom';\nimport App from './App';\nimport * as serviceWorker from './serviceWorker';\n\nReactDOM.render(\n \n \n ,\n document.getElementById('root')\n);\n\n// If you want your app to work offline and load faster, you can change\n// unregister() to register() below. Note this comes with some pitfalls.\n// Learn more about service workers: https://bit.ly/CRA-PWA\nserviceWorker.unregister();\n"],"sourceRoot":""}
--------------------------------------------------------------------------------
/meetjs-react/build/static/js/runtime-main.d3070e05.js:
--------------------------------------------------------------------------------
1 | !function(e){function t(t){for(var n,l,a=t[0],f=t[1],i=t[2],p=0,s=[];p
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/meetjs-react/build/static/media/cam-on.a9c7e91b.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/meetjs-react/build/static/media/logo.acb38cf1.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/meetjs-react/build/static/media/mic-off.99246803.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/meetjs-react/build/static/media/mic-on.5ce2865f.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/meetjs-react/build/static/media/view.d319b0ef.svg:
--------------------------------------------------------------------------------
1 |
20 |
--------------------------------------------------------------------------------
/meetjs-react/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3.7'
2 |
3 | services:
4 | meetjs-client:
5 | container_name: meetjs-client
6 | build:
7 | context: .
8 | dockerfile: Dockerfile
9 | volumes:
10 | - '.:/app'
11 | - '/app/node_modules'
12 | environment:
13 | SERVER_URL: http://3.138.188.169:9000
14 | WS_URL: ws://3.138.188.169:9000/ws
15 | ports:
16 | - 3000:80
--------------------------------------------------------------------------------
/meetjs-react/nginx.conf:
--------------------------------------------------------------------------------
1 | worker_processes 1;
2 |
3 | events {
4 | worker_connections 1024;
5 | }
6 |
7 | http {
8 | include /etc/nginx/mime.types;
9 |
10 | server {
11 | listen ${PORT:-80};
12 | server_name _;
13 |
14 | root /usr/share/nginx/html;
15 | index index.html;
16 |
17 | location / {
18 | try_files $$uri /index.html;
19 | }
20 | }
21 | }
--------------------------------------------------------------------------------
/meetjs-react/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "meetjs-react",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@testing-library/jest-dom": "^4.2.4",
7 | "@testing-library/react": "^9.3.2",
8 | "@testing-library/user-event": "^7.1.2",
9 | "@types/jest": "^24.0.0",
10 | "@types/node": "^12.0.0",
11 | "@types/node-sass": "^4.11.1",
12 | "@types/react": "^16.9.0",
13 | "@types/react-dom": "^16.9.0",
14 | "@types/react-router-dom": "^5.1.5",
15 | "axios": "^0.20.0",
16 | "node-sass": "^4.14.1",
17 | "react": "^16.13.1",
18 | "react-beforeunload": "^2.2.4",
19 | "react-dom": "^16.13.1",
20 | "react-loader-spinner": "^3.1.14",
21 | "react-router-dom": "^5.2.0",
22 | "react-scripts": "3.4.3",
23 | "typescript": "~3.7.2"
24 | },
25 | "scripts": {
26 | "start": "react-scripts start",
27 | "build": "GENERATE_SOURCEMAP=true react-scripts build",
28 | "test": "react-scripts test",
29 | "eject": "react-scripts eject"
30 | },
31 | "eslintConfig": {
32 | "extends": "react-app"
33 | },
34 | "browserslist": {
35 | "production": [
36 | ">0.2%",
37 | "not dead",
38 | "not op_mini all"
39 | ],
40 | "development": [
41 | "last 1 chrome version",
42 | "last 1 firefox version",
43 | "last 1 safari version"
44 | ]
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/meetjs-react/public/_redirects:
--------------------------------------------------------------------------------
1 | /* /index.html 200
--------------------------------------------------------------------------------
/meetjs-react/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thealmarques/meetjs-webrtc-golang/79205d205bdb4624ef664ba6630f53ff19999b77/meetjs-react/public/favicon.ico
--------------------------------------------------------------------------------
/meetjs-react/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | MeetJS
28 |
29 |
30 |
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/meetjs-react/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thealmarques/meetjs-webrtc-golang/79205d205bdb4624ef664ba6630f53ff19999b77/meetjs-react/public/logo192.png
--------------------------------------------------------------------------------
/meetjs-react/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thealmarques/meetjs-webrtc-golang/79205d205bdb4624ef664ba6630f53ff19999b77/meetjs-react/public/logo512.png
--------------------------------------------------------------------------------
/meetjs-react/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/meetjs-react/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/meetjs-react/src/App.scss:
--------------------------------------------------------------------------------
1 | @import "./fonts.scss";
2 |
3 | body {
4 | margin: 0;
5 | }
6 |
7 | .app {
8 | width: 100%;
9 | height: 100vh;
10 | }
11 |
12 | * {
13 | box-sizing: border-box;
14 | }
15 |
16 | ::placeholder { /* Chrome, Firefox, Opera, Safari 10.1+ */
17 | color: #707070;
18 | opacity: 1; /* Firefox */
19 | }
20 |
21 | :-ms-input-placeholder { /* Internet Explorer 10-11 */
22 | color: #707070;
23 | }
24 |
25 | ::-ms-input-placeholder { /* Microsoft Edge */
26 | color: #707070;
27 | }
--------------------------------------------------------------------------------
/meetjs-react/src/App.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import './App.scss';
3 | import { Router, Redirect, Route, Switch } from "react-router";
4 | import { Home } from './components/home/home';
5 | import { Meeting } from './components/meeting/meeting';
6 | import { createBrowserHistory } from "history";
7 |
8 | function App() {
9 | const history = createBrowserHistory();
10 |
11 | return (
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | );
28 | }
29 |
30 | export default App;
31 |
--------------------------------------------------------------------------------
/meetjs-react/src/assets/fonts/Quicksand_Bold.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thealmarques/meetjs-webrtc-golang/79205d205bdb4624ef664ba6630f53ff19999b77/meetjs-react/src/assets/fonts/Quicksand_Bold.otf
--------------------------------------------------------------------------------
/meetjs-react/src/assets/fonts/Quicksand_Book.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thealmarques/meetjs-webrtc-golang/79205d205bdb4624ef664ba6630f53ff19999b77/meetjs-react/src/assets/fonts/Quicksand_Book.otf
--------------------------------------------------------------------------------
/meetjs-react/src/assets/images/cam-off.svg:
--------------------------------------------------------------------------------
1 |
12 |
--------------------------------------------------------------------------------
/meetjs-react/src/assets/images/cam-on.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/meetjs-react/src/assets/images/close.svg:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/meetjs-react/src/assets/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thealmarques/meetjs-webrtc-golang/79205d205bdb4624ef664ba6630f53ff19999b77/meetjs-react/src/assets/images/logo.png
--------------------------------------------------------------------------------
/meetjs-react/src/assets/images/logo.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/meetjs-react/src/assets/images/mic-off.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/meetjs-react/src/assets/images/mic-on.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/meetjs-react/src/assets/images/video-off.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
48 |
--------------------------------------------------------------------------------
/meetjs-react/src/assets/images/video-on.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
45 |
--------------------------------------------------------------------------------
/meetjs-react/src/assets/images/view.svg:
--------------------------------------------------------------------------------
1 |
20 |
--------------------------------------------------------------------------------
/meetjs-react/src/components/home/home.scss:
--------------------------------------------------------------------------------
1 | .container {
2 | height: 100%;
3 | width: 100%;
4 | background-color: #8C55A3;
5 | overflow: auto;
6 |
7 | .header {
8 | padding: 20px;
9 | width: 100%;
10 | display: flex;
11 | align-items: center;
12 | justify-content: space-between;
13 |
14 | &__left {
15 | display: flex;
16 | align-items: center;
17 |
18 | &__logo {
19 | width: 60px;
20 | height: 60px;
21 | color: white;
22 | }
23 |
24 | &__text {
25 | margin-left: 10px;
26 | font-size: 20px;
27 | font-family: 'Quicksand-Bold';
28 | color: white;
29 |
30 | &:hover {
31 | color: white;
32 | }
33 | }
34 | }
35 |
36 | &__right {
37 | cursor: pointer;
38 | display: flex;
39 | color: white;
40 | font-family: 'Quicksand-Bold';
41 |
42 | a {
43 | color: white;
44 | }
45 |
46 | &__margin {
47 | margin-right: 7px;
48 | }
49 | }
50 | }
51 |
52 | .body {
53 | margin-top: 50px;
54 | display: flex;
55 | flex-direction: column;
56 | align-items: center;
57 |
58 | &__title {
59 | font-family: 'Quicksand-Bold';
60 | font-size: 24px;
61 | color: white;
62 | text-align: center;
63 | }
64 |
65 | &__sub-title {
66 | font-family: 'Quicksand-Bold';
67 | font-size: 14px;
68 | opacity: .7;
69 | color: white;
70 | margin-top: 10px;
71 | text-align: center;
72 | }
73 |
74 | &__form {
75 | display: flex;
76 | flex-direction: row;
77 | margin-top: 40px;
78 | width: 480px;
79 | align-items: center;
80 | justify-content: space-between;
81 |
82 | &__inputs {
83 | display: flex;
84 | flex-direction: column;
85 | width: 60%;
86 |
87 | &__input {
88 | outline: none;
89 | border: 0;
90 | padding-left: 10px;
91 | border-radius: 2px;
92 | width: 100%;
93 | height: 35px;
94 | margin-top: 10px;
95 | box-shadow: 0px 0px 2px 1px rgba(237,237,237,1);
96 | }
97 | }
98 |
99 | &__create {
100 | width: 30%;
101 | display: flex;
102 | flex-direction: column;
103 | align-items: center;
104 | justify-content: center;
105 | flex-wrap: wrap;
106 |
107 | &__btn {
108 | font-family: 'Quicksand-Bold';
109 | margin-top: 10px;
110 | border: 0;
111 | outline: 0;
112 | cursor: pointer;
113 | width: 100%;
114 | height: 35px;
115 | background-color: #ED7655;
116 | border-radius: 5px;
117 | color: white;
118 | }
119 |
120 | &__small-text {
121 | margin-top: 5px;
122 | opacity: .5;
123 | font-family: 'Quicksand-Bold';
124 | color: white;
125 | font-size: 12px;
126 | }
127 | }
128 |
129 | @media (max-width: 480px) {
130 | width: 90%;
131 | }
132 | }
133 | }
134 | }
--------------------------------------------------------------------------------
/meetjs-react/src/components/home/home.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { useHistory } from "react-router";
3 | import './home.scss';
4 | import logo from '../../assets/images/logo.svg';
5 | import { createSession } from '../../services/api.services';
6 | import { ResponseData } from '../../interfaces/response-data';
7 |
8 | export const Home = () => {
9 | const [host, setHost] = useState('');
10 | const [title, setTitle] = useState('');
11 | const [password, setPassword] = useState('');
12 | const history = useHistory();
13 |
14 | const onSubmit = (event: React.FormEvent) => {
15 | event.preventDefault();
16 | createSession(host, title, password).then((response: Response & ResponseData) => {
17 | history.push(`/meeting/${response.data.socket}`);
18 | }).catch((error: Error) => {
19 | console.error(error.message)
20 | });
21 | }
22 |
23 | return (
24 |
25 |
26 |
27 |

28 |
MeetJS
29 |
30 |
33 |
34 |
35 |
Do you want a free video chat platform?
36 |
Get you own URL and share it with others to join you.
37 |
48 |
49 |
50 | )
51 | }
52 |
--------------------------------------------------------------------------------
/meetjs-react/src/components/meeting/meeting.scss:
--------------------------------------------------------------------------------
1 | .meeting {
2 | height: 100%;
3 | width: 100%;
4 | background-color: #8C55A3;
5 | overflow: auto;
6 |
7 | &__header {
8 | padding: 20px;
9 | width: 100%;
10 | display: flex;
11 | align-items: center;
12 | justify-content: space-between;
13 |
14 | &__left {
15 | display: flex;
16 | align-items: center;
17 |
18 | &__logo {
19 | width: 60px;
20 | height: 60px;
21 | color: white;
22 | }
23 |
24 | &__text {
25 | margin-left: 10px;
26 | font-size: 20px;
27 | font-family: 'Quicksand-Bold';
28 | color: white;
29 |
30 | &:hover {
31 | color: white;
32 | }
33 | }
34 | }
35 |
36 | &__right {
37 | cursor: pointer;
38 | display: flex;
39 | color: white;
40 | font-family: 'Quicksand-Bold';
41 |
42 | a {
43 | color: white;
44 | }
45 |
46 | &__margin {
47 | margin-right: 7px;
48 | }
49 | }
50 | }
51 |
52 | &__body {
53 | width: 100%;
54 | margin-top: 20px;
55 | display: flex;
56 | align-items: center;
57 | justify-content: center;
58 |
59 | &__users {
60 | display: flex;
61 | align-items: center;
62 | flex-wrap: wrap;
63 |
64 | &-row {
65 | flex-direction: row;
66 | }
67 |
68 | &-column {
69 | flex-direction: column;
70 | }
71 |
72 | &__remote-video {
73 | margin-bottom: 20px;
74 | margin-right: 20px;
75 | width: 40vw;
76 |
77 | &-split {
78 | width: 35vw;
79 | justify-content: center;
80 | }
81 | }
82 | }
83 |
84 | &__info {
85 | margin-top: 10%;
86 | color: white;
87 | font-family: 'Quicksand-Bold';
88 | text-align: center;
89 | }
90 |
91 | &__spinner {
92 | margin-top: 10%;
93 | }
94 |
95 | &__bar {
96 | position: absolute;
97 | bottom: 20px;
98 | width: 60vw;
99 | height: 45px;
100 | background-color: #ED7655;
101 | border-radius: 20px;
102 | display: flex;
103 | justify-content: space-between;
104 | align-items: center;
105 |
106 | &__video-container {
107 | position: absolute;
108 | right: -2vw;
109 | top: -11vw;
110 | transition: transform 1s ease-in-out;
111 |
112 | &__local-video {
113 | border-radius: 5px;
114 | height: 10vw;
115 | width: 20vw;
116 | transition: transform 1s ease-in-out;
117 |
118 | &:hover {
119 | transform: scale(2);
120 | }
121 | }
122 |
123 | &:hover {
124 | transform: translateY(-5vw);
125 | }
126 | }
127 |
128 | &__left-bar {
129 | height: 100%;
130 | display: flex;
131 | align-items: center;
132 |
133 | &__status {
134 | width: 8vw;
135 | height: 100%;
136 | border-radius: 20px;
137 | display: flex;
138 | align-items: center;
139 | justify-content: center;
140 | color: white;
141 | font-size: 1.2vw;
142 | font-family: 'Quicksand-Bold';
143 | cursor: pointer;
144 | user-select: none;
145 |
146 | &-inactive {
147 | background-color: #CE0B29;
148 | }
149 |
150 | &-active {
151 | background-color: #299B43;
152 | }
153 | }
154 |
155 | &__title {
156 | font-size: 1.2vw;
157 | color: white;
158 | margin-left: 10px;
159 | text-align: center;
160 | font-family: 'Quicksand-Bold';
161 | }
162 | }
163 |
164 | &__right-bar {
165 | margin-right: 10px;
166 | display: flex;
167 | align-items: center;
168 |
169 | &__count {
170 | font-size: 10px;
171 | font-family: 'Quicksand-Bold';
172 | color: white;
173 | margin-right: 10px;
174 | }
175 |
176 | &__icon {
177 | margin-right: 15px;
178 | width: 2vw;
179 | height: 2vw;
180 | cursor: pointer;
181 | }
182 | }
183 | }
184 | }
185 | }
--------------------------------------------------------------------------------
/meetjs-react/src/components/meeting/meeting.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState, useRef } from 'react'
2 | import { useLocation } from "react-router-dom";
3 | import { verifySocket, connectSession } from '../../services/api.services';
4 | import logo from '../../assets/images/logo.svg';
5 | import micOn from '../../assets/images/mic-on.svg';
6 | import micOff from '../../assets/images/mic-off.svg';
7 | import camOn from '../../assets/images/cam-on.svg';
8 | import camOff from '../../assets/images/cam-off.svg';
9 | import view from '../../assets/images/view.svg';
10 | import { SessionCredentials } from '../session-modal/modal';
11 | import './meeting.scss';
12 | import { ResponseData } from '../../interfaces/response-data';
13 | import { ConnectionSocket } from '../../services/web-socket.services';
14 | import { generateId } from '../../utils/identifier.utils';
15 | import { SocketEvent, SocketResponse } from '../../interfaces/socket-data';
16 | import { State } from '../state-machine/state-machine';
17 | import { STUN_SERVERS } from '../../constants/stun-servers';
18 | import { EasyRTC } from '../../utils/easy-rtc';
19 | import { useBeforeunload } from 'react-beforeunload';
20 |
21 | export const Meeting = () => {
22 | const location = useLocation();
23 | const [state, setState] = useState(State.INVALID);
24 | const [title, setTitle] = useState('');
25 | const [audio, setAudio] = useState();
26 | const [video, setVideo] = useState();
27 | const [viewMode, setViewMode] = useState(false);
28 | const [connection, setConnection] = useState();
29 | const [users, setUsers] = useState([]);
30 | const [socket, setSocket] = useState('');
31 | const [url] = useState(location.pathname.split('/meeting/')[1]);
32 | const [localStream, setLocalStream] = useState();
33 | const localVideo = useRef(null);
34 | const userId = useRef(generateId());
35 |
36 | useBeforeunload((event: Event) => {
37 | event.preventDefault();
38 | if (connection) {
39 | connection.send('disconnect', userId.current);
40 | }
41 | });
42 |
43 | useEffect(() => {
44 | let unmounted = false;
45 | const getValidity = async () => {
46 | try {
47 | await verifySocket(url);
48 | if (!unmounted) {
49 | setState(State.VALID_URL);
50 | }
51 | } catch (error) {
52 | console.log(error.message);
53 | console.error('Invalid URL');
54 | window.location.href = '/';
55 | }
56 | }
57 | getValidity();
58 | return () => {
59 | unmounted = true;
60 | };
61 | }, [url])
62 |
63 | const connect = (host: string, password: string) => {
64 | connectSession(host, password, url).then((response: Response & ResponseData) => {
65 | if (response.data.title) {
66 | setTitle(response.data.title);
67 | }
68 |
69 | if (response.data.socket) {
70 | setSocket(response.data.socket);
71 | }
72 |
73 | setState(State.LOGGED);
74 | }).catch((error) => {
75 | setState(State.INVALID);
76 | });
77 | }
78 |
79 | const joinMeeting = () => {
80 | switch (state) {
81 | case State.LOGGED:
82 | setState(State.JOINED);
83 | break;
84 | case State.JOINED:
85 | setState(State.LOGGED);
86 | break;
87 | }
88 | }
89 |
90 | useEffect(() => {
91 | if (state >= 3) {
92 | if (localVideo.current && !localVideo.current.srcObject) {
93 | navigator.mediaDevices.getUserMedia(
94 | { audio: true, video: true }
95 | ).then(stream => {
96 | if (localVideo.current) {
97 | localVideo.current.srcObject = stream;
98 | }
99 | setLocalStream(stream);
100 |
101 | setAudio(true);
102 | setVideo(true);
103 | }).catch(error => {
104 | console.log(error);
105 | });
106 | }
107 | }
108 | }, [state]);
109 |
110 | useEffect(() => {
111 | if (localStream) {
112 | const media: MediaStream = localStream;
113 | media.getTracks().forEach((track: MediaStreamTrack) => {
114 | if (track.kind === 'audio') {
115 | track.enabled = !track.enabled;
116 | }
117 | });
118 | }
119 | }, [localStream, audio]);
120 |
121 | useEffect(() => {
122 | if (localStream) {
123 | const media: MediaStream = localStream;
124 | media.getTracks().forEach((track: MediaStreamTrack) => {
125 | if (track.kind === 'video') {
126 | track.enabled = !track.enabled;
127 | }
128 | });
129 | }
130 | }, [localStream, video]);
131 |
132 | useEffect(() => {
133 | if (state === 3 && connection) {
134 | connection.send('disconnect', userId.current);
135 | setUsers([]);
136 | }
137 | }, [state, connection]);
138 |
139 | useEffect(() => {
140 | if (state > 3) {
141 | const socketConnection = new ConnectionSocket(`${process.env.REACT_APP_WS_URL}/${socket}`);
142 | const remoteStreams: MediaStream[] = [];
143 | const rtcPeerConnection: EasyRTC[] = [];
144 |
145 | const onConnectionReady = () => {
146 | socketConnection.send('connect', userId.current);
147 | }
148 |
149 | const onMessage = (event: Event & SocketEvent) => {
150 | const data: SocketResponse = JSON.parse(event.data);
151 |
152 | if (!rtcPeerConnection[data.userID] && userId.current !== data.userID) {
153 | if (!remoteStreams[data.userID]) {
154 | remoteStreams[data.userID] = new MediaStream();
155 | }
156 |
157 | if (localVideo.current && localVideo.current.srcObject) {
158 | rtcPeerConnection[data.userID] = new EasyRTC(STUN_SERVERS, localVideo.current.srcObject as MediaStream);
159 | }
160 |
161 | rtcPeerConnection[data.userID].onIceCandidate((event: RTCPeerConnectionIceEvent) => {
162 | if (event.candidate) {
163 | socketConnection.candidate(event.candidate, userId.current);
164 | }
165 | });
166 |
167 | rtcPeerConnection[data.userID].onTrack((event: RTCTrackEvent) => {
168 | const mediaStream: MediaStream = remoteStreams[data.userID];
169 | mediaStream.addTrack(event.track);
170 |
171 | const element = document.getElementById(`${data.userID}-video`) as HTMLMediaElement;
172 | element.srcObject = mediaStream;
173 | });
174 | }
175 |
176 | switch (data.type) {
177 | case 'session_joined':
178 | socketConnection.send('start_call', userId.current);
179 | break;
180 | case 'start_call':
181 | if (data.userID !== userId.current) {
182 | setUsers((prevState: string[]) => prevState.indexOf(data.userID) < 0 ? prevState.concat(data.userID) : prevState);
183 | rtcPeerConnection[data.userID].createOffer().then((description: RTCSessionDescriptionInit) => {
184 | socketConnection.sendDescription('offer', description, userId.current, data.userID);
185 | });
186 | }
187 | break;
188 | case 'offer':
189 | if (data.to === userId.current) {
190 | setUsers((prevState: string[]) => prevState.indexOf(data.userID) < 0 ? prevState.concat(data.userID) : prevState);
191 | rtcPeerConnection[data.userID].setRemoteDescription(new RTCSessionDescription(JSON.parse(data.description))).then(async () => {
192 | const answer = await rtcPeerConnection[data.userID].createAnswer();
193 | socketConnection.sendDescription('answer', answer, userId.current, data.userID);
194 | });
195 | }
196 | break;
197 | case 'answer':
198 | if (data.to === userId.current) {
199 | rtcPeerConnection[data.userID].receiveAnswer(JSON.parse(data.description));
200 | }
201 | break;
202 | case 'ice':
203 | if (userId.current !== data.userID && data.candidate) {
204 | rtcPeerConnection[data.userID].addIceCandidate(JSON.parse(data.candidate));
205 | }
206 | break
207 | case 'disconnect':
208 | if (data.userID !== userId.current) {
209 | setUsers(prevItems => {
210 | const users = [...prevItems];
211 | users.splice(users.indexOf(data.userID), 1);
212 | return users;
213 | });
214 | remoteStreams[data.userID] = undefined;
215 | rtcPeerConnection[data.userID] = undefined;
216 | }
217 | break;
218 | }
219 | }
220 |
221 | socketConnection.onOpen(onConnectionReady);
222 | socketConnection.onMessage(onMessage);
223 | setConnection(socketConnection);
224 | }
225 | }, [state, socket])
226 |
227 | return (
228 |
229 |
230 |
231 |

232 |
MeetJS
233 |
234 |
237 |
238 |
239 | {state <= 2 &&
}
240 | {state === State.LOGGED &&
241 | (
242 |
Join the meeting to communicate with others.
243 | )
244 | }
245 | {state === State.JOINED && (
246 |
247 | {
248 | users.map((user, index) => {
249 | return (
250 |
255 | )
256 | })
257 | }
258 |
259 | )}
260 | {state > 2 &&
261 | (
262 |
263 |
264 |
266 |
267 |
268 |
269 | {state === State.JOINED ? 'Leave' : 'Join'}
270 |
271 |
{title}
272 |
273 |
274 | {
275 | state === State.JOINED &&
276 |
{users.length} online users
277 | }
278 |

setViewMode(!viewMode)} />
279 |

setAudio(!audio)} />
280 |

setVideo(!video)} />
281 |
282 |
283 | )
284 | }
285 |
286 |
287 | )
288 | }
--------------------------------------------------------------------------------
/meetjs-react/src/components/session-modal/modal.scss:
--------------------------------------------------------------------------------
1 | .modal {
2 | width: 100%;
3 | position: relative;
4 | display: flex;
5 | align-items: center;
6 | justify-content: center;
7 |
8 | &__wrapper {
9 | border-radius: 5px;
10 | z-index: 1;
11 | width: 400px;
12 | height: 250px;
13 | background-color: #F7F7F7;
14 |
15 | &__body {
16 | padding-left: 20px;
17 | width: 100%;
18 | height: 100%;
19 | position: relative;
20 | display: flex;
21 | flex-direction: row;
22 |
23 | &__form {
24 | width: 92%;
25 | display: flex;
26 | flex-direction: column;
27 | margin-top: 50px;
28 |
29 | &__small-text {
30 | font-family: 'Quicksand-Regular';
31 | font-size: 14px;
32 | }
33 |
34 | &__big-text {
35 | font-family: 'Quicksand-Bold';
36 | font-size: 17px;
37 | }
38 |
39 | &__input {
40 | outline: none;
41 | border: 0;
42 | padding-left: 10px;
43 | border-radius: 2px;
44 | width: 100%;
45 | height: 35px;
46 | margin-top: 10px;
47 | box-shadow: 0px 0px 5px 1px rgba(237,237,237,1);
48 | }
49 |
50 | &__connect {
51 | font-family: 'Quicksand-Bold';
52 | margin-top: 10px;
53 | border: 0;
54 | outline: 0;
55 | cursor: pointer;
56 | width: 100%;
57 | height: 35px;
58 | background-color: #ED7655;
59 | border-radius: 5px;
60 | color: white;
61 | }
62 |
63 | &-error {
64 | margin-top: 5px;
65 | color: #CE0B29;
66 | font-size: 10px;
67 | font-family: 'Quicksand-Bold';
68 | }
69 | }
70 | }
71 | }
72 | }
--------------------------------------------------------------------------------
/meetjs-react/src/components/session-modal/modal.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react'
2 | import './modal.scss';
3 |
4 | interface Props {
5 | connect: (host: string, password: string) => void;
6 | error: boolean;
7 | }
8 |
9 | export const SessionCredentials = (props: Props) => {
10 | const [host, setHost] = useState('');
11 | const [password, setPassword] = useState('');
12 | const [submitted, setSubmitted] = useState(false);
13 |
14 | const onSubmit = (event: React.FormEvent) => {
15 | event.preventDefault();
16 | props.connect(host, password);
17 | setSubmitted(true);
18 | }
19 |
20 | return (
21 |
35 | )
36 | }
37 |
--------------------------------------------------------------------------------
/meetjs-react/src/components/state-machine/state-machine.tsx:
--------------------------------------------------------------------------------
1 | export enum State {
2 | VALID_URL = 1,
3 | INVALID,
4 | LOGGED,
5 | JOINED
6 | }
--------------------------------------------------------------------------------
/meetjs-react/src/constants/stun-servers.ts:
--------------------------------------------------------------------------------
1 | export const STUN_SERVERS = {
2 | iceServers: [
3 | { urls: 'stun:stun3.l.google.com:19302' },
4 | { urls: 'stun:stun4.l.google.com:19302' },
5 | ],
6 | }
--------------------------------------------------------------------------------
/meetjs-react/src/fonts.scss:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: 'Quicksand-Bold';
3 | src: local('Lato'), url(./assets/fonts/Quicksand_Bold.otf) format('opentype');
4 | }
5 |
6 | @font-face {
7 | font-family: 'Quicksand-Regular';
8 | src: local('Lato'), url(./assets/fonts/Quicksand_Book.otf) format('opentype');
9 | }
--------------------------------------------------------------------------------
/meetjs-react/src/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 | import * as serviceWorker from './serviceWorker';
5 |
6 | ReactDOM.render(
7 |
8 |
9 | ,
10 | document.getElementById('root')
11 | );
12 |
13 | // If you want your app to work offline and load faster, you can change
14 | // unregister() to register() below. Note this comes with some pitfalls.
15 | // Learn more about service workers: https://bit.ly/CRA-PWA
16 | serviceWorker.unregister();
17 |
--------------------------------------------------------------------------------
/meetjs-react/src/interfaces/response-data.tsx:
--------------------------------------------------------------------------------
1 | export interface ResponseData {
2 | data: {
3 | socket?: string;
4 | title?: string;
5 | }
6 | }
--------------------------------------------------------------------------------
/meetjs-react/src/interfaces/socket-data.tsx:
--------------------------------------------------------------------------------
1 | export interface SocketResponse {
2 | type: string;
3 | userID: string;
4 | description: string;
5 | candidate: string;
6 | label: string;
7 | to: string;
8 | }
9 |
10 | export interface SocketEvent {
11 | data: string;
12 | }
--------------------------------------------------------------------------------
/meetjs-react/src/interfaces/stun-servers.tsx:
--------------------------------------------------------------------------------
1 | export interface StunServers {
2 | iceServers: {
3 | urls: string;
4 | }[]
5 | }
--------------------------------------------------------------------------------
/meetjs-react/src/logo.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/meetjs-react/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/meetjs-react/src/serviceWorker.ts:
--------------------------------------------------------------------------------
1 | // This optional code is used to register a service worker.
2 | // register() is not called by default.
3 |
4 | // This lets the app load faster on subsequent visits in production, and gives
5 | // it offline capabilities. However, it also means that developers (and users)
6 | // will only see deployed updates on subsequent visits to a page, after all the
7 | // existing tabs open on the page have been closed, since previously cached
8 | // resources are updated in the background.
9 |
10 | // To learn more about the benefits of this model and instructions on how to
11 | // opt-in, read https://bit.ly/CRA-PWA
12 |
13 | const isLocalhost = Boolean(
14 | window.location.hostname === 'localhost' ||
15 | // [::1] is the IPv6 localhost address.
16 | window.location.hostname === '[::1]' ||
17 | // 127.0.0.0/8 are considered localhost for IPv4.
18 | window.location.hostname.match(
19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
20 | )
21 | );
22 |
23 | type Config = {
24 | onSuccess?: (registration: ServiceWorkerRegistration) => void;
25 | onUpdate?: (registration: ServiceWorkerRegistration) => void;
26 | };
27 |
28 | export function register(config?: Config) {
29 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
30 | // The URL constructor is available in all browsers that support SW.
31 | const publicUrl = new URL(
32 | process.env.PUBLIC_URL,
33 | window.location.href
34 | );
35 | if (publicUrl.origin !== window.location.origin) {
36 | // Our service worker won't work if PUBLIC_URL is on a different origin
37 | // from what our page is served on. This might happen if a CDN is used to
38 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374
39 | return;
40 | }
41 |
42 | window.addEventListener('load', () => {
43 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
44 |
45 | if (isLocalhost) {
46 | // This is running on localhost. Let's check if a service worker still exists or not.
47 | checkValidServiceWorker(swUrl, config);
48 |
49 | // Add some additional logging to localhost, pointing developers to the
50 | // service worker/PWA documentation.
51 | navigator.serviceWorker.ready.then(() => {
52 | console.log(
53 | 'This web app is being served cache-first by a service ' +
54 | 'worker. To learn more, visit https://bit.ly/CRA-PWA'
55 | );
56 | });
57 | } else {
58 | // Is not localhost. Just register service worker
59 | registerValidSW(swUrl, config);
60 | }
61 | });
62 | }
63 | }
64 |
65 | function registerValidSW(swUrl: string, config?: Config) {
66 | navigator.serviceWorker
67 | .register(swUrl)
68 | .then(registration => {
69 | registration.onupdatefound = () => {
70 | const installingWorker = registration.installing;
71 | if (installingWorker == null) {
72 | return;
73 | }
74 | installingWorker.onstatechange = () => {
75 | if (installingWorker.state === 'installed') {
76 | if (navigator.serviceWorker.controller) {
77 | // At this point, the updated precached content has been fetched,
78 | // but the previous service worker will still serve the older
79 | // content until all client tabs are closed.
80 | console.log(
81 | 'New content is available and will be used when all ' +
82 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
83 | );
84 |
85 | // Execute callback
86 | if (config && config.onUpdate) {
87 | config.onUpdate(registration);
88 | }
89 | } else {
90 | // At this point, everything has been precached.
91 | // It's the perfect time to display a
92 | // "Content is cached for offline use." message.
93 | console.log('Content is cached for offline use.');
94 |
95 | // Execute callback
96 | if (config && config.onSuccess) {
97 | config.onSuccess(registration);
98 | }
99 | }
100 | }
101 | };
102 | };
103 | })
104 | .catch(error => {
105 | console.error('Error during service worker registration:', error);
106 | });
107 | }
108 |
109 | function checkValidServiceWorker(swUrl: string, config?: Config) {
110 | // Check if the service worker can be found. If it can't reload the page.
111 | fetch(swUrl, {
112 | headers: { 'Service-Worker': 'script' }
113 | })
114 | .then(response => {
115 | // Ensure service worker exists, and that we really are getting a JS file.
116 | const contentType = response.headers.get('content-type');
117 | if (
118 | response.status === 404 ||
119 | (contentType != null && contentType.indexOf('javascript') === -1)
120 | ) {
121 | // No service worker found. Probably a different app. Reload the page.
122 | navigator.serviceWorker.ready.then(registration => {
123 | registration.unregister().then(() => {
124 | window.location.reload();
125 | });
126 | });
127 | } else {
128 | // Service worker found. Proceed as normal.
129 | registerValidSW(swUrl, config);
130 | }
131 | })
132 | .catch(() => {
133 | console.log(
134 | 'No internet connection found. App is running in offline mode.'
135 | );
136 | });
137 | }
138 |
139 | export function unregister() {
140 | if ('serviceWorker' in navigator) {
141 | navigator.serviceWorker.ready
142 | .then(registration => {
143 | registration.unregister();
144 | })
145 | .catch(error => {
146 | console.error(error.message);
147 | });
148 | }
149 | }
150 |
--------------------------------------------------------------------------------
/meetjs-react/src/services/api.services.tsx:
--------------------------------------------------------------------------------
1 | import Axios from "axios";
2 | import { ResponseData } from "../interfaces/response-data";
3 |
4 | export function createSession(host: string, title: string, password: string): Promise {
5 | return Axios.post(`${process.env.REACT_APP_SERVER}/session`, JSON.stringify({
6 | title,
7 | host,
8 | password
9 | })
10 | );
11 | }
12 |
13 | export function connectSession(host: string, password: string, socket: string): Promise {
14 | return Axios.post(`${process.env.REACT_APP_SERVER}/connect/${socket}`, JSON.stringify({
15 | host,
16 | password
17 | })
18 | );
19 | }
20 |
21 | export function verifySocket(url: string): Promise {
22 | return Axios.get(`${process.env.REACT_APP_SERVER}/connect`, {
23 | params: {
24 | url
25 | }
26 | });
27 | }
--------------------------------------------------------------------------------
/meetjs-react/src/services/web-socket.services.tsx:
--------------------------------------------------------------------------------
1 | import { SocketEvent } from "../interfaces/socket-data";
2 |
3 | export class ConnectionSocket {
4 | private connection: WebSocket;
5 |
6 | constructor(url: string) {
7 | this.connection = new WebSocket(url);
8 | }
9 |
10 | /**
11 | * Disconnect
12 | */
13 | disconnect() {
14 | this.connection.close();
15 | }
16 |
17 | /**
18 | * On open connection event.
19 | * @param callback handles on connection ready.
20 | */
21 | onOpen(callback: () => void) {
22 | this.connection.onopen = callback;
23 | }
24 |
25 | /**
26 | * Sends message through web sockets.
27 | * @param data data to send
28 | * @param type event type
29 | * @param userID user id
30 | * @param offer RTC offer
31 | */
32 | send(type: string, userID: string) {
33 | this.connection.send(JSON.stringify({
34 | type,
35 | userID
36 | }));
37 | }
38 |
39 | sendDescription(type: string, description: RTCSessionDescriptionInit, userID: string, to: string) {
40 | this.connection.send(JSON.stringify({
41 | type,
42 | userID,
43 | description: JSON.stringify(description),
44 | to
45 | }));
46 | }
47 |
48 | candidate(candidate: RTCIceCandidate, userID: string) {
49 | this.connection.send(JSON.stringify({
50 | candidate: JSON.stringify(candidate),
51 | userID,
52 | type: 'ice'
53 | }));
54 | }
55 |
56 | /**
57 | * On message received.
58 | * @param callback method to handle on message function.
59 | */
60 | onMessage(callback: (event: Event & SocketEvent) => void) {
61 | this.connection.onmessage = callback;
62 | }
63 |
64 | /**
65 | * Checks if connection is ready.
66 | */
67 | isReady(): boolean {
68 | return this.connection.readyState === this.connection.OPEN;
69 | }
70 | }
--------------------------------------------------------------------------------
/meetjs-react/src/setupTests.ts:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom/extend-expect';
6 |
--------------------------------------------------------------------------------
/meetjs-react/src/utils/easy-rtc.ts:
--------------------------------------------------------------------------------
1 | import { StunServers } from "../interfaces/stun-servers";
2 |
3 | export class EasyRTC {
4 | servers: StunServers;
5 | stream: MediaStream;
6 | peerConnection: RTCPeerConnection;
7 |
8 | constructor(servers: StunServers, stream: MediaStream) {
9 | this.servers = servers;
10 | this.stream = stream;
11 |
12 | this.peerConnection = new RTCPeerConnection(servers);
13 |
14 | this.stream.getTracks().forEach((track: MediaStreamTrack) => {
15 | this.peerConnection.addTrack(track);
16 | });
17 | }
18 |
19 | /**
20 | * Creates offer to send to remote peers.
21 | */
22 | async createOffer() {
23 | let sessionDescription: RTCSessionDescriptionInit;
24 | sessionDescription = await this.peerConnection.createOffer();
25 | this.peerConnection.setLocalDescription(sessionDescription);
26 |
27 | return sessionDescription;
28 | }
29 |
30 | /**
31 | * Creates answer when offer is received.
32 | */
33 | async createAnswer() {
34 | let sessionDescription: RTCSessionDescriptionInit;
35 | sessionDescription = await this.peerConnection.createAnswer();
36 | this.peerConnection.setLocalDescription(sessionDescription)
37 |
38 | return sessionDescription;
39 | }
40 |
41 | /**
42 | * Receives answer from remote peers.
43 | */
44 | async receiveAnswer(event: RTCSessionDescriptionInit | undefined) {
45 | if (event) {
46 | await this.peerConnection.setRemoteDescription(new RTCSessionDescription(event))
47 | }
48 | }
49 |
50 | /**
51 | *
52 | * @param candidate
53 | */
54 | async addCandidate(candidate: RTCIceCandidate) {
55 | await this.peerConnection.addIceCandidate(candidate);
56 | }
57 |
58 | onTrack(callback: (event: RTCTrackEvent) => void) {
59 | this.peerConnection.ontrack = callback ;
60 | }
61 |
62 | onIceCandidate(callback: (event: RTCPeerConnectionIceEvent) => void) {
63 | this.peerConnection.onicecandidate = callback ;
64 | }
65 |
66 | async setRemoteDescription(description: RTCSessionDescriptionInit) {
67 | await this.peerConnection.setRemoteDescription(description);
68 | }
69 |
70 | addIceCandidate(candidate: RTCIceCandidate) {
71 | this.peerConnection.addIceCandidate(candidate);
72 | }
73 |
74 | disconnect() {
75 | this.peerConnection.close();
76 | }
77 | }
--------------------------------------------------------------------------------
/meetjs-react/src/utils/identifier.utils.ts:
--------------------------------------------------------------------------------
1 | export function byteToHex(byte) {
2 | return ('0' + byte.toString(16)).slice(-2);
3 | }
4 |
5 | export function generateId(len = 40) {
6 | var arr = new Uint8Array(len / 2);
7 | window.crypto.getRandomValues(arr);
8 | return Array.from(arr, byteToHex).join("");
9 | }
--------------------------------------------------------------------------------
/meetjs-react/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": [
5 | "dom",
6 | "dom.iterable",
7 | "esnext"
8 | ],
9 | "allowJs": true,
10 | "noImplicitAny": false,
11 | "skipLibCheck": true,
12 | "esModuleInterop": true,
13 | "allowSyntheticDefaultImports": true,
14 | "strict": true,
15 | "forceConsistentCasingInFileNames": true,
16 | "module": "esnext",
17 | "moduleResolution": "node",
18 | "resolveJsonModule": true,
19 | "isolatedModules": true,
20 | "noEmit": true,
21 | "jsx": "react"
22 | },
23 | "include": [
24 | "src"
25 | ]
26 | }
27 |
--------------------------------------------------------------------------------
/meetjs-server/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "compile-hero.disable-compile-files-on-did-save-code": false
3 | }
--------------------------------------------------------------------------------
/meetjs-server/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM golang:latest
2 |
3 | ENV GO111MODULE=on
4 |
5 | WORKDIR /app/src
6 |
7 | COPY . /app
8 |
9 | RUN go get -d -v ./...
10 | RUN go install -v ./...
11 |
12 | EXPOSE 9000
13 |
14 | CMD ["src"]
--------------------------------------------------------------------------------
/meetjs-server/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: "3.2"
2 | services:
3 | mongo-database:
4 | restart: always
5 | image: mongo:latest
6 | environment:
7 | MONGO_INITDB_ROOT_USERNAME: root
8 | MONGO_INITDB_ROOT_PASSWORD: rootpassword
9 | ports:
10 | - 27017:27017
11 | volumes:
12 | - mongodb_data_container:/data/db
13 | networks:
14 | - meetjs
15 |
16 | server:
17 | restart: always
18 | build:
19 | context: .
20 | dockerfile: Dockerfile
21 | ports:
22 | - "9000:9000"
23 | environment:
24 | DB_URL: mongo-database
25 | DB_PORT: 27017
26 | PORT: 9000
27 | HOST_URL: localhost
28 | networks:
29 | - meetjs
30 | depends_on:
31 | - mongo-database
32 |
33 | volumes:
34 | mongodb_data_container:
35 |
36 | networks:
37 | meetjs:
38 | driver: bridge
39 |
--------------------------------------------------------------------------------
/meetjs-server/go.mod:
--------------------------------------------------------------------------------
1 | module meetjs-server
2 |
3 | go 1.14
4 |
5 | require (
6 | github.com/gin-contrib/cors v1.3.1
7 | github.com/gin-gonic/gin v1.6.3
8 | github.com/go-playground/validator/v10 v10.4.0 // indirect
9 | github.com/golang/protobuf v1.4.2 // indirect
10 | github.com/gorilla/websocket v1.4.2
11 | github.com/json-iterator/go v1.1.10 // indirect
12 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
13 | github.com/modern-go/reflect2 v1.0.1 // indirect
14 | github.com/rs/cors v1.7.0
15 | github.com/ugorji/go v1.1.10 // indirect
16 | go.mongodb.org/mongo-driver v1.4.2
17 | golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0
18 | golang.org/x/sys v0.0.0-20201008064518-c1f3e3309c71 // indirect
19 | google.golang.org/protobuf v1.25.0 // indirect
20 | gopkg.in/yaml.v2 v2.3.0 // indirect
21 | )
22 |
--------------------------------------------------------------------------------
/meetjs-server/go.sum:
--------------------------------------------------------------------------------
1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
2 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
3 | github.com/aws/aws-sdk-go v1.34.28 h1:sscPpn/Ns3i0F4HPEWAVcwdIRaZZCuL7llJ2/60yPIk=
4 | github.com/aws/aws-sdk-go v1.34.28/go.mod h1:H7NKnBqNVzoTJpGfLrQkkD+ytBA93eiDYi/+8rV9s48=
5 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
6 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
7 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
8 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
9 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
10 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
11 | github.com/gin-contrib/cors v1.3.1 h1:doAsuITavI4IOcd0Y19U4B+O0dNWihRyX//nn4sEmgA=
12 | github.com/gin-contrib/cors v1.3.1/go.mod h1:jjEJ4268OPZUcU7k9Pm653S7lXUGcqMADzFA61xsmDk=
13 | github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
14 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
15 | github.com/gin-gonic/gin v1.5.0/go.mod h1:Nd6IXA8m5kNZdNEHMBd93KT+mdY3+bewLgRvmCsR2Do=
16 | github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14=
17 | github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
18 | github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
19 | github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM=
20 | github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
21 | github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
22 | github.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY=
23 | github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
24 | github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
25 | github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY=
26 | github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
27 | github.com/go-playground/validator/v10 v10.4.0 h1:72qIR/m8ybvL8L5TIyfgrigqkrw7kVYAvjEvpT85l70=
28 | github.com/go-playground/validator/v10 v10.4.0/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
29 | github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
30 | github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
31 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
32 | github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0=
33 | github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY=
34 | github.com/gobuffalo/depgen v0.1.0/go.mod h1:+ifsuy7fhi15RWncXQQKjWS9JPkdah5sZvtHc2RXGlg=
35 | github.com/gobuffalo/envy v1.6.15/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI=
36 | github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI=
37 | github.com/gobuffalo/flect v0.1.0/go.mod h1:d2ehjJqGOH/Kjqcoz+F7jHTBbmDb38yXA598Hb50EGs=
38 | github.com/gobuffalo/flect v0.1.1/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI=
39 | github.com/gobuffalo/flect v0.1.3/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI=
40 | github.com/gobuffalo/genny v0.0.0-20190329151137-27723ad26ef9/go.mod h1:rWs4Z12d1Zbf19rlsn0nurr75KqhYp52EAGGxTbBhNk=
41 | github.com/gobuffalo/genny v0.0.0-20190403191548-3ca520ef0d9e/go.mod h1:80lIj3kVJWwOrXWWMRzzdhW3DsrdjILVil/SFKBzF28=
42 | github.com/gobuffalo/genny v0.1.0/go.mod h1:XidbUqzak3lHdS//TPu2OgiFB+51Ur5f7CSnXZ/JDvo=
43 | github.com/gobuffalo/genny v0.1.1/go.mod h1:5TExbEyY48pfunL4QSXxlDOmdsD44RRq4mVZ0Ex28Xk=
44 | github.com/gobuffalo/gitgen v0.0.0-20190315122116-cc086187d211/go.mod h1:vEHJk/E9DmhejeLeNt7UVvlSGv3ziL+djtTr3yyzcOw=
45 | github.com/gobuffalo/gogen v0.0.0-20190315121717-8f38393713f5/go.mod h1:V9QVDIxsgKNZs6L2IYiGR8datgMhB577vzTDqypH360=
46 | github.com/gobuffalo/gogen v0.1.0/go.mod h1:8NTelM5qd8RZ15VjQTFkAW6qOMx5wBbW4dSCS3BY8gg=
47 | github.com/gobuffalo/gogen v0.1.1/go.mod h1:y8iBtmHmGc4qa3urIyo1shvOD8JftTtfcKi+71xfDNE=
48 | github.com/gobuffalo/logger v0.0.0-20190315122211-86e12af44bc2/go.mod h1:QdxcLw541hSGtBnhUc4gaNIXRjiDppFGaDqzbrBd3v8=
49 | github.com/gobuffalo/mapi v1.0.1/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc=
50 | github.com/gobuffalo/mapi v1.0.2/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc=
51 | github.com/gobuffalo/packd v0.0.0-20190315124812-a385830c7fc0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4=
52 | github.com/gobuffalo/packd v0.1.0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4=
53 | github.com/gobuffalo/packr/v2 v2.0.9/go.mod h1:emmyGweYTm6Kdper+iywB6YK5YzuKchGtJQZ0Odn4pQ=
54 | github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/VCm/3ptBN+0=
55 | github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw=
56 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
57 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
58 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
59 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
60 | github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I=
61 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
62 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
63 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
64 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
65 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
66 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
67 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
68 | github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
69 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
70 | github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
71 | github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
72 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
73 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
74 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
75 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
76 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
77 | github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
78 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
79 | github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
80 | github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
81 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
82 | github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
83 | github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
84 | github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
85 | github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
86 | github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
87 | github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns=
88 | github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
89 | github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68=
90 | github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
91 | github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4=
92 | github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA=
93 | github.com/klauspost/compress v1.9.5 h1:U+CaK85mrNNb4k8BNOfgJtJ/gr6kswUCFj6miSzVC6M=
94 | github.com/klauspost/compress v1.9.5/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
95 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
96 | github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
97 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
98 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
99 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
100 | github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw=
101 | github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
102 | github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
103 | github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE=
104 | github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0=
105 | github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
106 | github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
107 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
108 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
109 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
110 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
111 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
112 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg=
113 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
114 | github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
115 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
116 | github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
117 | github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE=
118 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
119 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
120 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
121 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
122 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
123 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
124 | github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
125 | github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
126 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
127 | github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik=
128 | github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
129 | github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
130 | github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
131 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
132 | github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
133 | github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
134 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
135 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
136 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
137 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
138 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
139 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
140 | github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
141 | github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
142 | github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
143 | github.com/ugorji/go v1.1.10 h1:Mh7W3N/hGJJ8fRQNHIgomNTa0CgZc0aKDFvbgHl+U7A=
144 | github.com/ugorji/go v1.1.10/go.mod h1:/tC+H0R6N4Lcv4DoSdphIa9y/RAs4QFHDtN9W2oQcHw=
145 | github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
146 | github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
147 | github.com/ugorji/go/codec v1.1.10 h1:otofY/FAoRTMVqlVeDv/Kpm04D13lfJdrDqPbc3axg4=
148 | github.com/ugorji/go/codec v1.1.10/go.mod h1:jdPQoxvTq1mb8XV6RmofOz5UgNKV2czR6xvxXGwy1Bo=
149 | github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c h1:u40Z8hqBAAQyv+vATcGgV0YCnDjqSL7/q/JyPhhJSPk=
150 | github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I=
151 | github.com/xdg/stringprep v0.0.0-20180714160509-73f8eece6fdc h1:n+nNi93yXLkJvKwXNP9d55HC7lGK4H/SRcwB5IaUZLo=
152 | github.com/xdg/stringprep v0.0.0-20180714160509-73f8eece6fdc/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y=
153 | go.mongodb.org/mongo-driver v1.4.2 h1:WlnEglfTg/PfPq4WXs2Vkl/5ICC6hoG8+r+LraPmGk4=
154 | go.mongodb.org/mongo-driver v1.4.2/go.mod h1:WcMNYLx/IlOxLe6JRJiv2uXuCz6zBLndR4SoGjYphSc=
155 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
156 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
157 | golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
158 | golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
159 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
160 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
161 | golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0 h1:hb9wdF1z5waM+dSIICn1l0DkLVDT3hqhhQsDNUmHPRE=
162 | golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
163 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
164 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
165 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
166 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
167 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
168 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
169 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
170 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
171 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
172 | golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
173 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
174 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
175 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
176 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
177 | golang.org/x/sync v0.0.0-20190412183630-56d357773e84/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
178 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
179 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=
180 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
181 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
182 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
183 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
184 | golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
185 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
186 | golang.org/x/sys v0.0.0-20190419153524-e8e3143a4f4a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
187 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
188 | golang.org/x/sys v0.0.0-20190531175056-4c3a928424d2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
189 | golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
190 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg=
191 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
192 | golang.org/x/sys v0.0.0-20201008064518-c1f3e3309c71 h1:ZPX6UakxrJCxWiyGWpXtFY+fp86Esy7xJT/jJCG8bgU=
193 | golang.org/x/sys v0.0.0-20201008064518-c1f3e3309c71/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
194 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
195 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
196 | golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
197 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
198 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
199 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
200 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
201 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
202 | golang.org/x/tools v0.0.0-20190329151228-23e29df326fe/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
203 | golang.org/x/tools v0.0.0-20190416151739-9c9e1878f421/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
204 | golang.org/x/tools v0.0.0-20190420181800-aa740d480789/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
205 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
206 | golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
207 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
208 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
209 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
210 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
211 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
212 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
213 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
214 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
215 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
216 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
217 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
218 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
219 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
220 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
221 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
222 | google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
223 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
224 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
225 | google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
226 | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
227 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
228 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
229 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
230 | gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
231 | gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ=
232 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
233 | gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
234 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
235 | gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
236 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
237 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
238 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
239 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
240 |
--------------------------------------------------------------------------------
/meetjs-server/src/controllers/session.controllers.go:
--------------------------------------------------------------------------------
1 | package controllers
2 |
3 | import (
4 | "meetjs-server/src/interfaces"
5 | "meetjs-server/src/utils"
6 | "net/http"
7 |
8 | "github.com/gin-gonic/gin"
9 | "go.mongodb.org/mongo-driver/bson/primitive"
10 | "go.mongodb.org/mongo-driver/mongo"
11 | )
12 |
13 | // CreateSession - Creates user session
14 | func CreateSession(ctx *gin.Context) {
15 | db := ctx.MustGet("db").(*mongo.Client)
16 | collection := db.Database("MeetJS").Collection("sessions")
17 |
18 | var session interfaces.Session
19 | if err := ctx.ShouldBindJSON(&session); err != nil {
20 | ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
21 | return
22 | }
23 |
24 | session.Password = utils.HashPassword(session.Password)
25 |
26 | result, _ := collection.InsertOne(ctx, session)
27 | insertedID := result.InsertedID.(primitive.ObjectID).Hex()
28 |
29 | url := CreateSocket(session, ctx, insertedID)
30 | ctx.JSON(http.StatusOK, gin.H{"socket": url})
31 | }
32 |
--------------------------------------------------------------------------------
/meetjs-server/src/controllers/socket.controllers.go:
--------------------------------------------------------------------------------
1 | package controllers
2 |
3 | import (
4 | "crypto/sha1"
5 | "encoding/hex"
6 | "meetjs-server/src/interfaces"
7 | "meetjs-server/src/utils"
8 | "net/http"
9 |
10 | "github.com/gin-gonic/gin"
11 | "go.mongodb.org/mongo-driver/bson"
12 | "go.mongodb.org/mongo-driver/bson/primitive"
13 | "go.mongodb.org/mongo-driver/mongo"
14 | )
15 |
16 | // ConnectSession - Given a host and a password returns the session object.
17 | func ConnectSession(ctx *gin.Context) {
18 | db := ctx.MustGet("db").(*mongo.Client)
19 | collection := db.Database("MeetJS").Collection("sockets")
20 |
21 | url := ctx.Param("url")
22 | result := collection.FindOne(ctx, bson.M{"hashedurl": url})
23 |
24 | var input interfaces.Session
25 | if err := ctx.ShouldBindJSON(&input); err != nil {
26 | ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
27 | return
28 | }
29 |
30 | if result.Err() != nil {
31 | ctx.JSON(http.StatusBadRequest, gin.H{"error": "Socket connection not found."})
32 | return
33 | }
34 |
35 | var socket interfaces.Socket
36 | result.Decode(&socket)
37 |
38 | collection = db.Database("MeetJS").Collection("sessions")
39 | objectID, err := primitive.ObjectIDFromHex(socket.SessionID)
40 | if err != nil {
41 | ctx.JSON(http.StatusBadRequest, gin.H{"error": "Session not found."})
42 | return
43 | }
44 |
45 | result = collection.FindOne(ctx, bson.M{"_id": objectID})
46 | if result.Err() != nil {
47 | ctx.JSON(http.StatusBadRequest, gin.H{"error": "Session not found."})
48 | return
49 | }
50 |
51 | var session interfaces.Session
52 | result.Decode(&session)
53 |
54 | if !utils.ComparePasswords(session.Password, []byte(input.Password)) {
55 | ctx.JSON(http.StatusBadRequest, gin.H{"error": "Invalid password."})
56 | return
57 | }
58 |
59 | ctx.JSON(http.StatusOK, gin.H{
60 | "title": session.Title,
61 | "socket": socket.SocketURL,
62 | })
63 | }
64 |
65 | // GetSession - Checks if session exists.
66 | func GetSession(ctx *gin.Context) {
67 |
68 | db := ctx.MustGet("db").(*mongo.Client)
69 | collection := db.Database("MeetJS").Collection("sockets")
70 |
71 | id := ctx.Request.URL.Query()["url"][0]
72 | result := collection.FindOne(ctx, bson.M{"hashedurl": id})
73 |
74 | if result.Err() != nil {
75 | ctx.JSON(http.StatusBadRequest, gin.H{"error": "Socket connection not found."})
76 | return
77 | }
78 |
79 | ctx.Status(http.StatusOK)
80 | }
81 |
82 | // CreateSocket - Creates socket connection with given session
83 | func CreateSocket(session interfaces.Session, ctx *gin.Context, id string) string {
84 | db := ctx.MustGet("db").(*mongo.Client)
85 | collection := db.Database("MeetJS").Collection("sockets")
86 |
87 | var socket interfaces.Socket
88 | hashURL := hashSession(session.Host + session.Title)
89 | socketURL := hashSession(session.Host + session.Password)
90 | socket.SessionID = id
91 | socket.HashedURL = hashURL
92 | socket.SocketURL = socketURL
93 |
94 | collection.InsertOne(ctx, socket)
95 |
96 | return hashURL
97 | }
98 |
99 | func hashSession(str string) string {
100 | hash := sha1.New()
101 | hash.Write([]byte(str))
102 | return hex.EncodeToString(hash.Sum(nil))
103 | }
104 |
--------------------------------------------------------------------------------
/meetjs-server/src/interfaces/connection.interfaces.go:
--------------------------------------------------------------------------------
1 | package interfaces
2 |
3 | import (
4 | "sync"
5 |
6 | "github.com/gorilla/websocket"
7 | )
8 |
9 | // Connection - Websocket connections
10 | type Connection struct {
11 | Socket *websocket.Conn
12 | mu sync.Mutex
13 | }
14 |
15 | // Send - concurrency handling
16 | func (c *Connection) Send(message Message) error {
17 | c.mu.Lock()
18 | defer c.mu.Unlock()
19 | return c.Socket.WriteJSON(message)
20 | }
21 |
--------------------------------------------------------------------------------
/meetjs-server/src/interfaces/mongodb.interfaces.go:
--------------------------------------------------------------------------------
1 | package interfaces
2 |
3 | import "go.mongodb.org/mongo-driver/bson/primitive"
4 |
5 | // HexID struct to get id from DB
6 | type HexID struct {
7 | ID primitive.ObjectID `bson:"_id"`
8 | }
9 |
--------------------------------------------------------------------------------
/meetjs-server/src/interfaces/session.interfaces.go:
--------------------------------------------------------------------------------
1 | package interfaces
2 |
3 | // Session interface
4 | type Session struct {
5 | Host string
6 | Title string
7 | Password string
8 | }
9 |
--------------------------------------------------------------------------------
/meetjs-server/src/interfaces/socket.interfaces.go:
--------------------------------------------------------------------------------
1 | package interfaces
2 |
3 | // Socket interface
4 | type Socket struct {
5 | SessionID string
6 | HashedURL string
7 | SocketURL string
8 | }
9 |
10 | // Message interface
11 | type Message struct {
12 | Type string `json:"type"`
13 | UserID string `json:"userID"`
14 | Description string `json:"description"`
15 | Candidate string `json:"candidate"`
16 | To string `json:"to"`
17 | }
18 |
--------------------------------------------------------------------------------
/meetjs-server/src/server.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "log"
6 | "meetjs-server/src/controllers"
7 | "meetjs-server/src/interfaces"
8 | "net/http"
9 | "os"
10 |
11 | "github.com/gin-contrib/cors"
12 | "github.com/gin-gonic/gin"
13 | "github.com/gorilla/websocket"
14 | "go.mongodb.org/mongo-driver/mongo"
15 | "go.mongodb.org/mongo-driver/mongo/options"
16 | )
17 |
18 | var upgrader = websocket.Upgrader{
19 | CheckOrigin: func(r *http.Request) bool {
20 | return true
21 | },
22 | }
23 | var sockets = make(map[string]map[string]*interfaces.Connection)
24 |
25 | func wshandler(w http.ResponseWriter, r *http.Request, socket string) {
26 | conn, err := upgrader.Upgrade(w, r, nil)
27 | if err != nil {
28 | log.Fatal("Error handling websocket connection.")
29 | return
30 | }
31 |
32 | defer conn.Close()
33 |
34 | if sockets[socket] == nil {
35 | sockets[socket] = make(map[string]*interfaces.Connection)
36 | }
37 |
38 | clients := sockets[socket]
39 |
40 | var message interfaces.Message
41 | for {
42 | err = conn.ReadJSON(&message)
43 | if err != nil {
44 | if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
45 | log.Printf("error: %v", err)
46 | }
47 | break
48 | }
49 |
50 | if clients[message.UserID] == nil {
51 | connection := new(interfaces.Connection)
52 | connection.Socket = conn
53 | clients[message.UserID] = connection
54 | }
55 |
56 | switch message.Type {
57 | case "connect":
58 | message.Type = "session_joined"
59 | err := conn.WriteJSON(message)
60 | if err != nil {
61 | log.Printf("Websocket error: %s", err)
62 | delete(clients, message.UserID)
63 | }
64 | break
65 | case "disconnect":
66 | for user, client := range clients {
67 | err := client.Send(message)
68 | if err != nil {
69 | client.Socket.Close()
70 | delete(clients, user)
71 | }
72 | }
73 | delete(clients, message.UserID)
74 | break
75 | default:
76 | for user, client := range clients {
77 | err := client.Send(message)
78 | if err != nil {
79 | delete(clients, user)
80 | }
81 | }
82 | }
83 | }
84 | }
85 |
86 | func main() {
87 | router := gin.Default()
88 |
89 | config := cors.DefaultConfig()
90 | config.AllowOrigins = []string{getenv("HOST_URL", "localhost")}
91 |
92 | router.Use(cors.Default())
93 |
94 | credential := options.Credential{
95 | Username: "root",
96 | Password: "rootpassword",
97 | }
98 |
99 | clientOptions := options.Client().ApplyURI("mongodb://" + getenv("DB_URL", "localhost") + ":" + getenv("DB_PORT", "27017")).SetAuth(credential)
100 | client, err := mongo.Connect(context.TODO(), clientOptions)
101 | if err != nil {
102 | log.Fatal(err)
103 | }
104 |
105 | err = client.Ping(context.TODO(), nil)
106 | if err != nil {
107 | log.Fatal(err)
108 | }
109 |
110 | log.Println("MongoDB connection ok...")
111 |
112 | // middleware - intercept requests to use our db controller
113 | router.Use(func(context *gin.Context) {
114 | context.Set("db", client)
115 | context.Next()
116 | })
117 |
118 | // REST API
119 | router.POST("/session", controllers.CreateSession)
120 | router.GET("/connect", controllers.GetSession)
121 | router.POST("/connect/:url", controllers.ConnectSession)
122 |
123 | // Websocket connection
124 | router.GET("/ws/:socket", func(c *gin.Context) {
125 | socket := c.Param("socket")
126 | wshandler(c.Writer, c.Request, socket)
127 | })
128 |
129 | router.Run("0.0.0.0:" + getenv("PORT", "9000"))
130 | }
131 |
132 | func getenv(key, fallback string) string {
133 | value := os.Getenv(key)
134 | if len(value) == 0 {
135 | return fallback
136 | }
137 | return value
138 | }
139 |
--------------------------------------------------------------------------------
/meetjs-server/src/utils/password.utils.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "log"
5 |
6 | "golang.org/x/crypto/bcrypt"
7 | )
8 |
9 | // HashPassword - Hash password.
10 | func HashPassword(password string) string {
11 | hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.MinCost)
12 | if err != nil {
13 | log.Println(err)
14 | }
15 |
16 | return string(hash)
17 | }
18 |
19 | // ComparePasswords - Checks if two password match.
20 | func ComparePasswords(hashedPwd string, plainPwd []byte) bool {
21 | byteHash := []byte(hashedPwd)
22 | err := bcrypt.CompareHashAndPassword(byteHash, plainPwd)
23 | if err != nil {
24 | log.Println(err)
25 | return false
26 | }
27 |
28 | return true
29 | }
30 |
--------------------------------------------------------------------------------
/resource/architecture.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thealmarques/meetjs-webrtc-golang/79205d205bdb4624ef664ba6630f53ff19999b77/resource/architecture.png
--------------------------------------------------------------------------------
/resource/demo1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thealmarques/meetjs-webrtc-golang/79205d205bdb4624ef664ba6630f53ff19999b77/resource/demo1.png
--------------------------------------------------------------------------------
/resource/demo2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thealmarques/meetjs-webrtc-golang/79205d205bdb4624ef664ba6630f53ff19999b77/resource/demo2.png
--------------------------------------------------------------------------------
/resource/demo3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thealmarques/meetjs-webrtc-golang/79205d205bdb4624ef664ba6630f53ff19999b77/resource/demo3.png
--------------------------------------------------------------------------------