├── .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 | [![Netlify Status](https://api.netlify.com/api/v1/badges/3fab6827-60bb-4548-b465-9e86b62150cf/deploy-status)](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 \"Logo\"\n MeetJS\n
\n
\n Github\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 setHost(event.target.value)} required autoComplete={\"on\"}/>\n setTitle(event.target.value)} required autoComplete={\"on\"}/>\n setPassword(event.target.value)} required autoComplete={\"on\"}/>\n
\n
\n \n Secure and simple\n
\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
\n Connect with your friends\n Enter your session credentials\n setHost(event.target.value)} required autoComplete={\"on\"} />\n setPassword(event.target.value)} type=\"password\" required autoComplete={\"on\"} />\n \n {props.error && submitted && Invalid credentials}\n
\n
\n
\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 \"Logo\"\n MeetJS\n
\n
\n Github\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 \"Change setViewMode(!viewMode)} />\n \"Mic\" setAudio(!audio)} />\n \"Webcam\" 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 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /meetjs-react/build/static/media/logo.acb38cf1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /meetjs-react/build/static/media/mic-off.99246803.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /meetjs-react/build/static/media/mic-on.5ce2865f.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /meetjs-react/build/static/media/view.d319b0ef.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 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 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /meetjs-react/src/assets/images/cam-on.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /meetjs-react/src/assets/images/close.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 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 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /meetjs-react/src/assets/images/mic-off.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /meetjs-react/src/assets/images/mic-on.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /meetjs-react/src/assets/images/video-off.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /meetjs-react/src/assets/images/video-on.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /meetjs-react/src/assets/images/view.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 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 | Logo 28 | MeetJS 29 |
30 |
31 | Github 32 |
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 |
38 |
39 | setHost(event.target.value)} required autoComplete={"on"}/> 40 | setTitle(event.target.value)} required autoComplete={"on"}/> 41 | setPassword(event.target.value)} required autoComplete={"on"}/> 42 |
43 |
44 | 45 | Secure and simple 46 |
47 |
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 | Logo 232 | MeetJS 233 |
234 |
235 | Github 236 |
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 | Change view setViewMode(!viewMode)} /> 279 | Mic setAudio(!audio)} /> 280 | Webcam 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 |
22 |
23 |
24 |
25 | Connect with your friends 26 | Enter your session credentials 27 | setHost(event.target.value)} required autoComplete={"on"} /> 28 | setPassword(event.target.value)} type="password" required autoComplete={"on"} /> 29 | 30 | {props.error && submitted && Invalid credentials} 31 |
32 |
33 |
34 |
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 | 2 | 3 | 4 | 5 | 6 | 7 | 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 --------------------------------------------------------------------------------