├── .env.example ├── .gitignore ├── README.md ├── controllers ├── Message │ └── index.js ├── Misc │ └── index.js ├── Room │ └── index.js └── User │ └── index.js ├── models ├── message.model.js ├── room.model.js └── user.model.js ├── package-lock.json ├── package.json ├── public ├── PoseNetModels │ ├── metadata.json │ ├── model.json │ └── weights.bin ├── assets │ ├── .DS_Store │ ├── css │ │ ├── .DS_Store │ │ ├── LineIcons.2.0.css │ │ ├── animate.css │ │ ├── bootstrap-5.0.0-beta2.min.css │ │ ├── main.css │ │ ├── timeline.css │ │ └── tiny-slider.css │ ├── fonts │ │ ├── LineIcons.eot │ │ ├── LineIcons.svg │ │ ├── LineIcons.ttf │ │ ├── LineIcons.woff │ │ └── LineIcons.woff2 │ ├── images │ │ ├── .DS_Store │ │ ├── brands │ │ │ ├── graygrids.svg │ │ │ ├── lineicons.svg │ │ │ ├── pagebulb.svg │ │ │ └── uideck.svg │ │ ├── favicon.svg │ │ ├── feature │ │ │ ├── feature-image-1.svg │ │ │ ├── feature-image-2.svg │ │ │ ├── feature-img-1.svg │ │ │ └── shape.svg │ │ ├── footer │ │ │ └── footer-bg.svg │ │ ├── hero │ │ │ ├── .DS_Store │ │ │ ├── hero-bg.svg │ │ │ ├── hero-image.svg │ │ │ ├── hero-img-1.svg │ │ │ └── hero-img-3.svg │ │ ├── logo │ │ │ ├── 7btrrd.gif │ │ │ ├── logo-1.png │ │ │ ├── logo-2-home.svg │ │ │ ├── logo-2.svg │ │ │ ├── logo.png │ │ │ ├── logo.svg │ │ │ └── macos-bg.jpg │ │ ├── pricing │ │ │ └── standard-bg.svg │ │ ├── service │ │ │ └── service-bg.jpg │ │ ├── team │ │ │ ├── .DS_Store │ │ │ ├── team-1.jpg │ │ │ ├── team-2.jpg │ │ │ └── team-3.jpg │ │ └── testimonial │ │ │ ├── testimonial-1.png │ │ │ └── testimonial-2.png │ └── js │ │ ├── .DS_Store │ │ ├── bootstrap-5.0.0-beta2.min.js │ │ ├── main.js │ │ ├── polyfill.js │ │ ├── tiny-slider.js │ │ └── wow.min.js ├── js │ ├── chat.js │ ├── firebaselogin.js │ ├── home.js │ ├── landing.js │ └── logout.js ├── lib │ └── peerjs.min.js ├── script.js └── styles │ ├── chatroom.css │ ├── home.css │ └── room.css ├── server.js └── views ├── aibot.ejs ├── chatroom.ejs ├── home.ejs ├── landing.ejs ├── room.ejs └── timeline.ejs /.env.example: -------------------------------------------------------------------------------- 1 | MONGODB_URI="" 2 | GAPI_CLIENT_ID="" 3 | GAPI_CLIENT_SECRET="" 4 | GAPI_REFRESH_TOKEN="" 5 | EMAIL="" 6 | PASS="" -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .pnpm-debug.log* 9 | 10 | # Diagnostic reports (https://nodejs.org/api/report.html) 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (https://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # Snowpack dependency directory (https://snowpack.dev/) 46 | web_modules/ 47 | 48 | # TypeScript cache 49 | *.tsbuildinfo 50 | 51 | # Optional npm cache directory 52 | .npm 53 | 54 | # Optional eslint cache 55 | .eslintcache 56 | 57 | # Microbundle cache 58 | .rpt2_cache/ 59 | .rts2_cache_cjs/ 60 | .rts2_cache_es/ 61 | .rts2_cache_umd/ 62 | 63 | # Optional REPL history 64 | .node_repl_history 65 | 66 | # Output of 'npm pack' 67 | *.tgz 68 | 69 | # Yarn Integrity file 70 | .yarn-integrity 71 | 72 | # dotenv environment variables file 73 | .env 74 | .env.test 75 | .env.production 76 | 77 | # parcel-bundler cache (https://parceljs.org/) 78 | .cache 79 | .parcel-cache 80 | 81 | # Next.js build output 82 | .next 83 | out 84 | 85 | # Nuxt.js build / generate output 86 | .nuxt 87 | dist 88 | 89 | # Gatsby files 90 | .cache/ 91 | # Comment in the public line in if your project uses Gatsby and not Next.js 92 | # https://nextjs.org/blog/next-9-1#public-directory-support 93 | # public 94 | 95 | # vuepress build output 96 | .vuepress/dist 97 | 98 | # Serverless directories 99 | .serverless/ 100 | 101 | # FuseBox cache 102 | .fusebox/ 103 | 104 | # DynamoDB Local files 105 | .dynamodb/ 106 | 107 | # TernJS port file 108 | .tern-port 109 | 110 | # Stores VSCode versions used for testing VSCode extensions 111 | .vscode-test 112 | 113 | # yarn v2 114 | .yarn/cache 115 | .yarn/unplugged 116 | .yarn/build-state.yml 117 | .yarn/install-state.gz 118 | .pnp.* 119 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Unite - Video Conferencing Web App 2 | ## Submission for Microsoft Engage 2021 🌟 3 | 4 | [![GitHub code size in bytes](https://img.shields.io/github/languages/code-size/Apurva-tech/unite?logo=github&style=for-the-badge)](https://github.com/Apurva-tech/) 5 | [![GitHub last commit](https://img.shields.io/github/last-commit/Apurva-tech/unite?style=for-the-badge&logo=git)](https://github.com/Apurva-tech/) 6 | [![GitHub stars](https://img.shields.io/github/stars/Apurva-tech/unite?style=for-the-badge)](https://github.com/Apurva-tech/unite/stargazers) 7 | [![My stars](https://img.shields.io/github/stars/Apurva-tech?affiliations=OWNER%2CCOLLABORATOR&style=for-the-badge&label=My%20stars)](https://github.com/Apurva-tech/unite/stargazers) 8 | [![GitHub forks](https://img.shields.io/github/forks/Apurva-tech/unite?style=for-the-badge&logo=git)](https://github.com/Apurva-tech/unite/network) 9 | [![Code size](https://img.shields.io/github/languages/code-size/Apurva-tech/unite?style=for-the-badge)](https://github.com/Apurva-tech/unite) 10 | [![Languages](https://img.shields.io/github/languages/count/Apurva-tech/unite?style=for-the-badge)](https://github.com/Apurva-tech/unite) 11 | [![Top](https://img.shields.io/github/languages/top/Apurva-tech/unite?style=for-the-badge&label=Top%20Languages)](https://github.com/Apurva-tech/unite) 12 | [![Issues](https://img.shields.io/github/issues/Apurva-tech/unite?style=for-the-badge&label=Issues)](https://github.com/Apurva-tech/unite) 13 | [![Watchers]( https://img.shields.io/github/watchers/Apurva-tech/unite?label=Watch&style=for-the-badge)](https://github.com/Apurva-tech/unite/) 14 | 15 | Video conferencing solutions with `Unite` 16 | using peer-to-peer connection. 17 | 18 |

19 | 20 | Unite-logo 21 | 22 |

23 | 24 | [![Generic badge](https://img.shields.io/badge/view-demo-blue?style=for-the-badge&label=View%20Demo%20Video)](https://youtu.be/OKKK1GOnlIU) 25 | 26 | ## Features and Interfaces 27 | 28 | 1. Landing Page and Feeback Form 29 | - Seamless landing page with `Login with Google` button for user Login using Firebase Authentication 30 | - ![image](https://user-images.githubusercontent.com/59837325/125426848-39db8eeb-3e84-424d-869c-5b344ba55ba1.png) 31 | 32 | - One of the most important part of Agile is feedback, therefore the app has a feedback form in the footer 33 | - ![image](https://user-images.githubusercontent.com/59837325/125427701-f6aed5f3-ca93-4cf7-8480-40d42b87e46b.png) 34 | 35 | - Witness by journey of these 4 weeks, through a concise timeline on my landing page 36 | - ![image](https://user-images.githubusercontent.com/59837325/125429293-88f38afe-be80-47ff-b662-2169d87bc476.png) 37 | 38 | 2. Home page 39 | - Has a signout button with app cards for different features and left division shows the rooms created by user as well as recent rooms visited 40 | - ![image](https://user-images.githubusercontent.com/59837325/125428224-08fcf962-46af-470a-abae-aaa893d2e0c7.png) 41 | 42 | - Toggle between dark-light using the moon icon, according to your preference 43 | - ![image](https://user-images.githubusercontent.com/59837325/125429518-79515f76-8392-413a-9d19-53894db9ee95.png) 44 | 45 | - Preview app cards for the application 46 | - ![image](https://user-images.githubusercontent.com/59837325/125429888-3f23518a-e1c1-4500-a345-c5735cf1a8ff.png) 47 | 48 | 3. Create Meeting 49 | - Create a room and invite people to join by copying the meeting code 50 | - ![image](https://user-images.githubusercontent.com/59837325/125431140-7f0a92a8-ca4c-48b5-a91d-e53e69b12cb5.png) 51 | 52 | - Enter the common chat room that **persist the messages** using MongoDB 53 | - ![image](https://user-images.githubusercontent.com/59837325/125432255-d9e05582-5ae0-4b2f-8a07-dba9605a4b6d.png) 54 | 55 | 4. Join Meeting 56 | - Enter the copied meeting ID in a form and join the room with your loved ones. 57 | - ![image](https://user-images.githubusercontent.com/59837325/125438181-fd819177-cb36-45bb-a7dd-8acfa5e21b66.png) 58 | 59 | 5. Video calling & Persistent text chat 60 | - Chat with your friends, family and professional peers before, during and after the meeting. 61 | - ![image](https://user-images.githubusercontent.com/59837325/125486957-00bbd600-02ea-4f4f-b040-fc5d25db3251.png) 62 | - ![image](https://user-images.githubusercontent.com/59837325/125486640-4ff2bdb8-b2ec-40f8-96c1-4dbdae007af3.png) 63 | 64 | - Screen Share options 65 | - ![image](https://user-images.githubusercontent.com/59837325/125486807-1b603597-eb1d-4758-9cb2-d1d34f8462c4.png) 66 | 67 | 6. Schedule Meeting 68 | - Schedule Meeting with Google Calendar and invite people by just entering their email addresses and the app schedules a meet for you with a unique meeting ID as well! 69 | - ![image](https://user-images.githubusercontent.com/59837325/125440936-e8d4141a-c7b8-4577-b5b0-4cba3d9e63ab.png) 70 | 71 | - Mail sent on scheduling a meeting 72 | - ![image](https://user-images.githubusercontent.com/59837325/125441421-ee5199d3-3165-43c0-88d6-f9328477dfe2.png) 73 | - ![image](https://user-images.githubusercontent.com/59837325/125441435-d1a53547-bb63-4b81-97e1-95d005298bac.png) 74 | 75 | 7. AI powered posture bot 76 | - Using `Tf.js` posenet model, the bot notifies the user if they are sitting in a bad posture or too close to the screen. 77 | - Keep your health in check and use this functionality with and without even being in a meeting 78 | - ![image](https://user-images.githubusercontent.com/59837325/125442368-2b7b4202-9f07-4d9a-9122-69b221a03ef9.png) 79 | 80 | ## Tech stack 81 | 82 | ![image](https://user-images.githubusercontent.com/59837325/125461960-da7d575b-b1e8-43f4-ae22-6f3403df44d1.png) 83 | 84 | ### Tools and Languages: 85 |

bootstrap css3 express firebase gcp git heroku html5 javascript mongodb nodejs sass tensorflow

86 | 87 | ## Points to remember while testing the app 88 | 89 | 1. Allow **permissions** for camera and mic 90 | 2. In case any **user is not broadcasted** it is probably due to server overload, **REFRESH** the window to solve this. 91 | 3. Make sure the **URL** is starting with https 92 | 4. While **scheduling a meet** make sure the start and end date follow a logical sequence or else it’ll show an error. 93 | 5. While testing the **Posture** bot, allow permissions for the camera and allow **notifications**, and **REFRESH** the page for changes to take effect. 94 | 6. Wait for the model to analyze, and check for **notifications** 95 | 96 | ## Instructions 97 | 98 | 99 | 1. `git clone https://github.com/Apurva-tech/unite.git` 100 | 2. `cd ./unite` 101 | 3. Install node dependencies 102 | - `npm install` 103 | 4. Replace firebase API keys with your configurations 104 | 5. Create a `.env` file 105 | - Add relevant credentials 106 | - `cp .env.example .env` 107 | 5. `npm run dev` 108 | 6. The app is now running at http://localhost:3030/landing 109 | 110 | 111 | ## Useful Links 112 | 113 | - [Deployed Website](https://unite-apurva.herokuapp.com/landing) 114 | - [Demo Video](https://youtu.be/OKKK1GOnlIU) 115 | - [Sprint Document](https://docs.google.com/presentation/d/11k8pLJPEV-XJwxIX4ysW9fKmHqFEZHcUWizFcFyVsns/edit?usp=sharing) 116 | - [Design Document](https://docs.google.com/document/d/1IJcEbbhsbQna-tgcnfV_9_RhXQi4SURlrl3-0HypArE/edit?usp=sharing) 117 | 118 | ## Need help? 119 | 120 | Feel free to contact me on [LinkedIn](https://www.linkedin.com/in/apurva866/) 121 | 122 | [![Instagram](https://img.shields.io/badge/Instagram-follow-purple.svg?logo=instagram&logoColor=white)](https://www.instagram.com/mind.wrapper/) [![Twitter](https://img.shields.io/badge/Twitter-follow-blue.svg?logo=twitter&logoColor=white)](https://twitter.com/mindwrapper) [![Medium](https://img.shields.io/badge/Medium-follow-black.svg?logo=medium&logoColor=white)](https://medium.com/@apurva866) 123 | 124 | --------- 125 | 126 | ```javascript 127 | 128 | if (youEnjoyed) { 129 | starThisRepository(); 130 | } 131 | 132 | ``` 133 | 134 | ----------- 135 | 136 | -------------------------------------------------------------------------------- /controllers/Message/index.js: -------------------------------------------------------------------------------- 1 | const Room = require("../../models/room.model"); 2 | const Message = require("./../../models/message.model"); 3 | const User = require("./../../models/user.model"); 4 | 5 | // creating messages is kept at service layer as real time messaging 6 | // is via sockets that utilise this 7 | const createMessage = async (user, content, room) => { 8 | console.log(user, content, room); 9 | 10 | try { 11 | user = await User.findOne({ email: user.email }); 12 | user = user._id; 13 | } catch (err) { 14 | console.error(err); 15 | return { success: false, message: "User not found" }; 16 | } 17 | try { 18 | room = await Room.findOne({ roomID: room }); 19 | room = room._id; 20 | } catch (err) { 21 | console.error(err); 22 | return { success: false, message: "Room not found" }; 23 | } 24 | try { 25 | const message = { user, content, room }; 26 | 27 | const messageRes = await Message.create(message); 28 | return { success: true, message: messageRes }; 29 | } catch (error) { 30 | console.error(error); 31 | return { success: false }; 32 | } 33 | }; 34 | 35 | // users can get the last few messages of the room, 36 | // both chat and meet use the same messages and are in sync 37 | const getMessages = async (req, res) => { 38 | console.log(req.body); 39 | 40 | let { room } = req.body; 41 | 42 | try { 43 | room = await Room.findOne({ roomID: room }); 44 | room = room._id; 45 | } catch (err) { 46 | console.error(err); 47 | return res.json({ success: false, message: "Room not found" }).status(404); 48 | } 49 | try { 50 | const messages = await Message.find({ room: room._id }) 51 | .populate("user") 52 | .limit(50) 53 | .sort("createdAt"); 54 | 55 | return res.json({ success: true, messages }).status(200); 56 | } catch (err) { 57 | console.error(err); 58 | return res.json({ success: false }).status(500); 59 | } 60 | }; 61 | 62 | module.exports = { 63 | createMessage, 64 | getMessages, 65 | }; 66 | -------------------------------------------------------------------------------- /controllers/Misc/index.js: -------------------------------------------------------------------------------- 1 | const { google } = require("googleapis"); 2 | const { OAuth2 } = google.auth; 3 | const Room = require("../../models/room.model"); 4 | const { v4: uuidV4 } = require("uuid"); 5 | const nodemailer = require("nodemailer"); 6 | 7 | const serviceMail = process.env.EMAIL; 8 | const pass = process.env.PASS; 9 | 10 | const sendInvite = async (request, response) => { 11 | console.log(request.body); 12 | // email = req.body.email; 13 | 14 | // Create a new room for meet 15 | let newRoom = { 16 | name: "Meetup at " + new Date(request.body.eventStart).toLocaleString(), 17 | roomID: uuidV4(), 18 | }; 19 | 20 | try { 21 | newRoom = await Room.create(newRoom); 22 | } catch (error) { 23 | return console.error(error); 24 | } 25 | 26 | const oAuth2Client = new OAuth2( 27 | process.env.GAPI_CLIENT_ID, 28 | process.env.GAPI_CLIENT_SECRET 29 | ); 30 | 31 | oAuth2Client.setCredentials({ 32 | refresh_token: process.env.GAPI_REFRESH_TOKEN, 33 | }); 34 | 35 | const calendar = google.calendar({ version: "v3", auth: oAuth2Client }); 36 | 37 | const eventStartTime = new Date(request.body.eventStart); 38 | 39 | const eventEndTime = new Date(request.body.eventEnd); 40 | 41 | // evenEndTime.setMinutes(evenEndTime.getMinutes() + 45); 42 | 43 | console.log("Start Time : " + eventStartTime); 44 | console.log("End Time : " + eventEndTime); 45 | const event = { 46 | summary: request.body.summary, 47 | location: `https://unite-videochat-app.herokuapp.com/${newRoom.roomID}`, 48 | description: `Meeting date & time: ${eventStartTime.toLocaleString()} 49 | Meeting chat URL: https://unite-videochat-app.herokuapp.com/chat/${ 50 | newRoom.roomID 51 | }`, 52 | creator: { 53 | email: request.body.organiser, 54 | }, 55 | organizer: { 56 | email: request.body.organiser, 57 | self: true, 58 | }, 59 | start: { 60 | dateTime: eventStartTime, 61 | timeZone: "Asia/Kolkata", 62 | }, 63 | end: { 64 | dateTime: eventEndTime, 65 | timeZone: "Asia/Kolkata", 66 | }, 67 | 68 | attendees: request.body.emails, 69 | colorId: 1, 70 | }; 71 | 72 | calendar.freebusy.query( 73 | { 74 | resource: { 75 | timeMin: eventStartTime, 76 | timeMax: eventEndTime, 77 | timeZone: "Asia/Kolkata", 78 | items: [{ id: "primary" }], 79 | }, 80 | }, 81 | (err, res) => { 82 | if (err) { 83 | console.error("Free bust query error", err); 84 | return response 85 | .json({ 86 | success: false, 87 | message: "Free bust query error", 88 | data: "Query error", 89 | error: err, 90 | }) 91 | .status(500); 92 | } 93 | 94 | const eventsArr = res.data.calendars.primary.busy; 95 | if (!eventsArr.length) { 96 | return calendar.events.insert( 97 | { 98 | calendarId: "primary", 99 | resource: event, 100 | sendUpdates: "all", 101 | }, 102 | (err) => { 103 | if (err) { 104 | console.error("Calender event creation error: ", err); 105 | return response 106 | .json({ 107 | success: false, 108 | message: "Calender event creation error", 109 | data: "Calendar event creation error", 110 | error: err, 111 | }) 112 | .status(500); 113 | } 114 | 115 | console.log("Calender Event Created"); 116 | return response 117 | .json({ 118 | success: true, 119 | message: "Calender Event Created", 120 | room: newRoom, 121 | }) 122 | .status(201); 123 | } 124 | ); 125 | } 126 | console.log(eventsArr); 127 | console.log("Sorry there's another meeting at that time and date"); 128 | return response 129 | .json({ 130 | success: false, 131 | message: "Sorry there's another meeting at that time and date", 132 | data: "Another meeting exists at that date and time", 133 | }) 134 | .status(500); 135 | } 136 | ); 137 | }; 138 | 139 | const mail = async (recipient, content) => { 140 | console.log({ serviceMail, pass }); 141 | const transporter = nodemailer.createTransport({ 142 | service: "gmail", 143 | auth: { 144 | user: serviceMail, 145 | pass: pass, 146 | }, 147 | }); 148 | 149 | const mailOptions = { 150 | from: process.env.email, // sender address 151 | to: recipient, // list of receivers 152 | subject: "Unite App Feedback", // Subject line 153 | html: `

${content}

`, // plain text body 154 | }; 155 | 156 | const trptRes = await transporter.sendMail(mailOptions); 157 | return trptRes; 158 | }; 159 | 160 | const mailFeedback = async (req, res) => { 161 | const { name, email, message } = req.body; 162 | console.log({ name, email, message }); 163 | 164 | const content = `Unite App Feedback

165 | Name: ${name || "no name"}
166 | Email: ${email}
167 | Message: ${message || "empty message"}
`; 168 | 169 | try { 170 | await mail(serviceMail, content); 171 | await mail(email, content); 172 | res 173 | .json({ success: true, message: "Feedback recorded successfully" }) 174 | .status(200); 175 | } catch (err) { 176 | console.error(err); 177 | res 178 | .json({ success: false, message: "Failed to record feedback" }) 179 | .status(200); 180 | } 181 | }; 182 | 183 | module.exports = { 184 | sendInvite, 185 | mailFeedback, 186 | }; 187 | -------------------------------------------------------------------------------- /controllers/Room/index.js: -------------------------------------------------------------------------------- 1 | const Room = require("./../../models/room.model"); 2 | const { v4: uuidV4 } = require("uuid"); 3 | 4 | /* 5 | 6 | ALL ROOM ARE PUBLIC in this release 7 | 8 | */ 9 | 10 | // users can create a room with an optional name 11 | // this allows for rooms that are persistent and can 12 | const createRoom = async (req, res) => { 13 | console.log(req.body); 14 | const { email, name } = req.body; 15 | const roomID = uuidV4(); 16 | 17 | room = { 18 | admin: email, 19 | name, 20 | roomID: roomID, 21 | }; 22 | 23 | try { 24 | const roomRes = await Room.create(room); 25 | res.json({ success: true, room: roomRes }).status(201); 26 | } catch (error) { 27 | console.error(error); 28 | res.json({ success: false }); 29 | } 30 | }; 31 | 32 | // to get a room and its admin details 33 | // so that we can map users with their admins 34 | const getRoom = async (req, res) => { 35 | console.log(req.body); 36 | 37 | let { room } = req.body; 38 | 39 | try { 40 | room = await Room.findOne({ roomID: room }).populate("user"); 41 | 42 | return res.json({ success: true, room }).status(200); 43 | } catch (err) { 44 | console.error(err); 45 | return res.json({ success: false, message: "Room not found" }).status(404); 46 | } 47 | }; 48 | 49 | // get all the rooms that are owned/created by a specific user 50 | // for access on their home page 51 | const getRoomByUser = async (req, res) => { 52 | console.log(req.body); 53 | 54 | let { email } = req.body; 55 | 56 | try { 57 | const rooms = await Room.find({ admin: email }); 58 | console.log(rooms); 59 | 60 | if (!rooms || rooms.length === 0) { 61 | return res 62 | .json({ success: false, message: "Room not found" }) 63 | .status(404); 64 | } else { 65 | return res.json({ success: true, rooms }).status(200); 66 | } 67 | } catch (err) { 68 | console.error(err); 69 | return res 70 | .json({ success: false, message: "Something went wrong" }) 71 | .status(500); 72 | } 73 | }; 74 | 75 | // to check for validity and render 👇🏻 76 | // <--------- chat room ---------> 👈🏻 77 | const chatRoomMW = async (req, res) => { 78 | try { 79 | const room = await Room.findOne({ roomID: req.params.room }); 80 | if (room) { 81 | return res.render("chatroom", { roomId: req.params.room }); 82 | } 83 | } catch (err) { 84 | console.error(err); 85 | return res.redirect("/home"); 86 | } 87 | return res.redirect("/home"); 88 | }; 89 | 90 | // <--------- meet room ---------> 👈🏻 91 | const roomMW = async (req, res) => { 92 | try { 93 | const room = await Room.findOne({ roomID: req.params.room }); 94 | if (room) { 95 | return res.render("room", { roomId: req.params.room }); 96 | } 97 | } catch (err) { 98 | console.error(err); 99 | return res.redirect("/home"); 100 | } 101 | return res.redirect("/home"); 102 | }; 103 | 104 | module.exports = { 105 | createRoom, 106 | getRoom, 107 | getRoomByUser, 108 | chatRoomMW, 109 | roomMW, 110 | }; 111 | -------------------------------------------------------------------------------- /controllers/User/index.js: -------------------------------------------------------------------------------- 1 | const User = require("./../../models/user.model"); 2 | const Room = require("./../../models/room.model"); 3 | 4 | // firebase auth-ed users need to be persisted in a database 5 | // as firebase-auth doesn't permit retrieving users 6 | const createUser = async (req, res) => { 7 | console.log(req.body); 8 | const { uid, displayName, photoURL, email } = req.body; 9 | 10 | const user = { 11 | uid, 12 | displayName, 13 | photoURL, 14 | email, 15 | }; 16 | 17 | try { 18 | const userRes = await User.create(user); 19 | res.json({ success: true, user: userRes }).status(201); 20 | } catch (error) { 21 | console.error(error); 22 | res.json({ success: false }); 23 | } 24 | }; 25 | 26 | // when retrieving users we populate their recent rooms 27 | // for displaying on their home page, the rooms they visited 28 | // which they can quickly re-access 29 | const getUser = async (req, res) => { 30 | console.log(req.body); 31 | 32 | let query = {}; 33 | const { email, uid } = req.body; 34 | if (uid) query["uid"] = uid; 35 | if (email) query["email"] = email; 36 | console.log(query); 37 | 38 | try { 39 | const user = await User.findOne(query).populate("rooms"); 40 | 41 | return res.json({ success: true, user }).status(200); 42 | } catch (err) { 43 | console.error(err); 44 | return res.json({ success: false, message: "User not found" }).status(404); 45 | } 46 | }; 47 | 48 | // everytime a user visits a room 49 | // we add it to the set of visited rooms 👆🏻 (in an LRU-ish fashion) 50 | // that can be retreived to visit again (max prev. rooms = 7) 51 | const addRooms = async (req, res) => { 52 | console.log(req.body); 53 | const { email, roomId } = req.body; 54 | let room; 55 | try { 56 | room = await Room.findOne({ roomID: roomId }); 57 | console.log(room); 58 | } catch (error) { 59 | return res.json({ success: false, message: "Room not found" }).status(404); 60 | } 61 | 62 | try { 63 | const user = await User.findOneAndUpdate( 64 | { email }, 65 | { $addToSet: { rooms: room._id } } 66 | ); 67 | if (user.rooms.length >= 7) { 68 | user = await User.findOneAndUpdate({ email }, { $pop: { rooms: -1 } }); 69 | } 70 | return res.json({ success: true, user: user }).status(200); 71 | } catch (err) { 72 | console.error(err); 73 | return res 74 | .json({ success: false, message: "Something went wrong" }) 75 | .status(500); 76 | } 77 | }; 78 | 79 | module.exports = { 80 | createUser, 81 | getUser, 82 | addRooms, 83 | }; 84 | -------------------------------------------------------------------------------- /models/message.model.js: -------------------------------------------------------------------------------- 1 | const { model, Schema } = require("mongoose"); 2 | 3 | // Message has a 1 to N relationship with User 4 | // Message also has a 1 to N relationship with Room 5 | // So we store the refs alongiwth the message content 6 | const messageSchema = new Schema( 7 | { 8 | user: { 9 | type: Schema.Types.ObjectId, 10 | ref: "User", 11 | }, 12 | content: { 13 | type: String, 14 | }, 15 | room: { 16 | type: Schema.Types.ObjectId, 17 | ref: "Room", 18 | }, 19 | }, 20 | { 21 | timestamps: true, 22 | } 23 | ); 24 | 25 | module.exports = model("Message", messageSchema); 26 | -------------------------------------------------------------------------------- /models/room.model.js: -------------------------------------------------------------------------------- 1 | const { model, Schema } = require("mongoose"); 2 | 3 | // room has a 1 to N relationship with user 4 | // here we are storing the email, which is unique 5 | // and can be populated from the user model 6 | // with a new virtual path "user" where room.admin <=> user.email 7 | const roomSchema = new Schema( 8 | { 9 | roomID: { 10 | type: String, 11 | }, 12 | admin: { 13 | type: String, 14 | }, 15 | name: { 16 | type: String, 17 | }, 18 | }, 19 | { 20 | timestamps: true, 21 | toObject: { virtuals: true }, 22 | toJSON: { virtuals: true }, 23 | } 24 | ); 25 | 26 | roomSchema.virtual("user", { 27 | ref: "User", 28 | localField: "admin", 29 | foreignField: "email", 30 | justOne: true, 31 | }); 32 | 33 | module.exports = model("Room", roomSchema); 34 | -------------------------------------------------------------------------------- /models/user.model.js: -------------------------------------------------------------------------------- 1 | const { model, Schema } = require("mongoose"); 2 | 3 | // in the user schema, we store: 4 | // the details necessary for the messaging 5 | // rooms, to keep track of previously visited room 6 | 7 | const userSchema = new Schema({ 8 | uid: { 9 | type: String, 10 | }, 11 | displayName: { 12 | type: String, 13 | }, 14 | email: { 15 | type: String, 16 | }, 17 | photoURL: { 18 | type: String, 19 | }, 20 | rooms: [ 21 | { 22 | type: Schema.Types.ObjectId, 23 | ref: "Room", 24 | }, 25 | ], 26 | }); 27 | 28 | module.exports = model("User", userSchema); 29 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "zoom-clone", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "server.js", 6 | "scripts": { 7 | "start": "node server.js", 8 | "dev": "nodemon server.js" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "dependencies": { 14 | "cors": "^2.8.5", 15 | "dotenv": "^10.0.0", 16 | "ejs": "^3.1.3", 17 | "express": "^4.17.1", 18 | "googleapis": "^80.1.0", 19 | "mongoose": "^5.13.2", 20 | "nodemailer": "^6.6.2", 21 | "peer": "^0.5.3", 22 | "peerjs": "^1.3.1", 23 | "socket.io": "^2.3.0", 24 | "uuid": "^8.3.0" 25 | }, 26 | "devDependencies": { 27 | "@types/mongoose": "^5.11.97", 28 | "nodemon": "^2.0.7" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /public/PoseNetModels/metadata.json: -------------------------------------------------------------------------------- 1 | {"tfjsVersion":"1.3.1","tmVersion":"0.8.6","packageVersion":"0.8.6","packageName":"@teachablemachine/pose","timeStamp":"2020-11-21T18:30:15.108Z","userMetadata":{},"modelName":"my-pose-model","labels":["good posture","bad posture","near screen"],"modelSettings":{"posenet":{"architecture":"MobileNetV1","outputStride":16,"inputResolution":257,"multiplier":0.75}}} -------------------------------------------------------------------------------- /public/PoseNetModels/model.json: -------------------------------------------------------------------------------- 1 | {"modelTopology":{"class_name":"Sequential","config":{"name":"sequential_2","layers":[{"class_name":"Dense","config":{"units":100,"activation":"relu","use_bias":true,"kernel_initializer":{"class_name":"VarianceScaling","config":{"scale":1,"mode":"fan_in","distribution":"normal","seed":null}},"bias_initializer":{"class_name":"Zeros","config":{}},"kernel_regularizer":null,"bias_regularizer":null,"activity_regularizer":null,"kernel_constraint":null,"bias_constraint":null,"name":"dense_Dense1","trainable":true,"batch_input_shape":[null,14739],"dtype":"float32"}},{"class_name":"Dropout","config":{"rate":0.5,"noise_shape":null,"seed":null,"name":"dropout_Dropout1","trainable":true}},{"class_name":"Dense","config":{"units":3,"activation":"softmax","use_bias":false,"kernel_initializer":{"class_name":"VarianceScaling","config":{"scale":1,"mode":"fan_in","distribution":"normal","seed":null}},"bias_initializer":{"class_name":"Zeros","config":{}},"kernel_regularizer":null,"bias_regularizer":null,"activity_regularizer":null,"kernel_constraint":null,"bias_constraint":null,"name":"dense_Dense2","trainable":true}}]},"keras_version":"tfjs-layers 1.3.1","backend":"tensor_flow.js"},"weightsManifest":[{"paths":["weights.bin"],"weights":[{"name":"dense_Dense1/kernel","shape":[14739,100],"dtype":"float32"},{"name":"dense_Dense1/bias","shape":[100],"dtype":"float32"},{"name":"dense_Dense2/kernel","shape":[100,3],"dtype":"float32"}]}]} -------------------------------------------------------------------------------- /public/PoseNetModels/weights.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apurva-tech/unite/2f0ee34f4136838f7f617417e268efd17c8cd984/public/PoseNetModels/weights.bin -------------------------------------------------------------------------------- /public/assets/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apurva-tech/unite/2f0ee34f4136838f7f617417e268efd17c8cd984/public/assets/.DS_Store -------------------------------------------------------------------------------- /public/assets/css/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apurva-tech/unite/2f0ee34f4136838f7f617417e268efd17c8cd984/public/assets/css/.DS_Store -------------------------------------------------------------------------------- /public/assets/css/timeline.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | background-image: url(https://4kwallpapers.com/images/wallpapers/macos-big-sur-apple-layers-fluidic-colorful-dark-wwdc-2020-5120x2880-1432.jpg); 5 | background-size: cover; 6 | background-position: center; 7 | display: flex; 8 | justify-content: center; 9 | align-items: center; 10 | font-family: "Open Sans", sans-serif; 11 | font-size: 112.5%; 12 | line-height: 1.6em; 13 | } 14 | 15 | /* ================ The Timeline ================ */ 16 | 17 | .timeline { 18 | position: relative; 19 | width: 660px; 20 | margin: 0 auto; 21 | margin-top: 20px; 22 | padding: 1em 0; 23 | list-style-type: none; 24 | } 25 | 26 | .timeline:before { 27 | position: absolute; 28 | left: 50%; 29 | top: 0; 30 | content: " "; 31 | display: block; 32 | width: 6px; 33 | height: 100%; 34 | margin-left: -3px; 35 | background: rgb(241, 238, 238); 36 | background: -moz-linear-gradient( 37 | top, 38 | rgba(255, 255, 255, 0.616) 0%, 39 | rgb(255, 255, 255) 8%, 40 | rgb(255, 255, 255) 92%, 41 | rgba(255, 255, 255, 0.616) 100% 42 | ); 43 | background: -webkit-gradient( 44 | linear, 45 | left top, 46 | left bottom, 47 | color-stop(0%, rgba(30, 87, 153, 1)), 48 | color-stop(100%, rgba(125, 185, 232, 1)) 49 | ); 50 | background: -webkit-linear-gradient( 51 | top, 52 | rgba(255, 255, 255, 0.616) 0%, 53 | rgb(255, 255, 255) 8%, 54 | rgb(255, 255, 255) 92%, 55 | rgba(255, 255, 255, 0.616) 100% 56 | ); 57 | background: -o-linear-gradient( 58 | top, 59 | rgba(255, 255, 255, 0.616) 0%, 60 | rgb(255, 255, 255) 8%, 61 | rgb(255, 255, 255) 92%, 62 | rgba(255, 255, 255, 0.616) 100% 63 | ); 64 | background: -ms-linear-gradient( 65 | top, 66 | rgba(255, 255, 255, 0.616) 0%, 67 | rgb(255, 255, 255) 8%, 68 | rgb(255, 255, 255) 92%, 69 | rgba(255, 255, 255, 0.616) 100% 70 | ); 71 | background: linear-gradient( 72 | to bottom, 73 | rgba(255, 255, 255, 0.616) 0%, 74 | rgb(255, 255, 255) 8%, 75 | rgb(255, 255, 255) 92%, 76 | rgba(255, 255, 255, 0.616) 100% 77 | ); 78 | 79 | z-index: 5; 80 | } 81 | 82 | .timeline li { 83 | padding: 1em 0; 84 | } 85 | 86 | .timeline li:after { 87 | content: ""; 88 | display: block; 89 | height: 0; 90 | clear: both; 91 | visibility: hidden; 92 | } 93 | 94 | .direction-l { 95 | position: relative; 96 | width: 300px; 97 | float: left; 98 | text-align: right; 99 | } 100 | 101 | .direction-r { 102 | position: relative; 103 | width: 300px; 104 | float: right; 105 | } 106 | 107 | .flag-wrapper { 108 | position: relative; 109 | display: inline-block; 110 | 111 | text-align: center; 112 | } 113 | 114 | .flag { 115 | position: relative; 116 | display: inline; 117 | background: rgb(248, 248, 248); 118 | padding: 6px 10px; 119 | border-radius: 5px; 120 | color: rgba(53, 53, 53, 0.801); 121 | font-weight: 600; 122 | text-align: left; 123 | } 124 | 125 | .direction-l .flag { 126 | -webkit-box-shadow: -1px 1px 1px rgba(53, 53, 53, 0.801), 127 | 0 0 1px rgba(53, 53, 53, 0.801); 128 | -moz-box-shadow: -1px 1px 1px rgba(53, 53, 53, 0.801), 129 | 0 0 1px rgba(53, 53, 53, 0.801); 130 | box-shadow: -1px 1px 1px rgba(53, 53, 53, 0.801), 131 | 0 0 1px rgba(53, 53, 53, 0.801); 132 | } 133 | 134 | .direction-r .flag { 135 | -webkit-box-shadow: 1px 1px 1px rgba(53, 53, 53, 0.801), 136 | 0 0 1px rgba(53, 53, 53, 0.801); 137 | -moz-box-shadow: 1px 1px 1px rgba(53, 53, 53, 0.801), 138 | 0 0 1px rgba(53, 53, 53, 0.801); 139 | box-shadow: 1px 1px 1px rgba(53, 53, 53, 0.801), 140 | 0 0 1px rgba(53, 53, 53, 0.801); 141 | } 142 | 143 | .direction-l .flag:before, 144 | .direction-r .flag:before { 145 | position: absolute; 146 | top: 50%; 147 | right: -40px; 148 | content: " "; 149 | display: block; 150 | width: 12px; 151 | height: 12px; 152 | margin-top: -10px; 153 | background: #fff; 154 | border-radius: 10px; 155 | border: 4px solid rgb(255, 80, 80); 156 | z-index: 10; 157 | } 158 | 159 | .direction-r .flag:before { 160 | left: -40px; 161 | } 162 | 163 | .direction-l .flag:after { 164 | content: ""; 165 | position: absolute; 166 | left: 100%; 167 | top: 50%; 168 | height: 0; 169 | width: 0; 170 | margin-top: -8px; 171 | border: solid transparent; 172 | border-left-color: rgb(248, 248, 248); 173 | border-width: 8px; 174 | pointer-events: none; 175 | } 176 | 177 | .direction-r .flag:after { 178 | content: ""; 179 | position: absolute; 180 | right: 100%; 181 | top: 50%; 182 | height: 0; 183 | width: 0; 184 | margin-top: -8px; 185 | border: solid transparent; 186 | border-right-color: rgb(248, 248, 248); 187 | border-width: 8px; 188 | pointer-events: none; 189 | } 190 | 191 | .time-wrapper { 192 | display: inline; 193 | 194 | line-height: 1em; 195 | font-size: 0.66666em; 196 | color: rgb(250, 80, 80); 197 | vertical-align: middle; 198 | } 199 | 200 | .direction-l .time-wrapper { 201 | float: left; 202 | } 203 | 204 | .direction-r .time-wrapper { 205 | float: right; 206 | } 207 | 208 | .time { 209 | display: inline-block; 210 | padding: 4px 6px; 211 | background: rgb(248, 248, 248); 212 | } 213 | 214 | .desc { 215 | margin: 1em 0.75em 0 0; 216 | 217 | font-size: 0.77777em; 218 | font-style: italic; 219 | line-height: 1.5em; 220 | } 221 | 222 | .direction-r .desc { 223 | margin: 1em 0 0 0.75em; 224 | } 225 | 226 | /* ================ Timeline Media Queries ================ */ 227 | 228 | @media screen and (max-width: 660px) { 229 | .timeline { 230 | width: 100%; 231 | padding: 4em 0 1em 0; 232 | } 233 | 234 | .timeline li { 235 | padding: 2em 0; 236 | } 237 | 238 | .direction-l, 239 | .direction-r { 240 | float: none; 241 | width: 100%; 242 | 243 | text-align: center; 244 | } 245 | 246 | .flag-wrapper { 247 | text-align: center; 248 | } 249 | 250 | .flag { 251 | background: rgb(255, 255, 255); 252 | z-index: 15; 253 | } 254 | 255 | .direction-l .flag:before, 256 | .direction-r .flag:before { 257 | position: absolute; 258 | top: -30px; 259 | left: 50%; 260 | content: " "; 261 | display: block; 262 | width: 12px; 263 | height: 12px; 264 | margin-left: -9px; 265 | background: #fff; 266 | border-radius: 10px; 267 | border: 4px solid rgb(255, 80, 80); 268 | z-index: 10; 269 | } 270 | 271 | .direction-l .flag:after, 272 | .direction-r .flag:after { 273 | content: ""; 274 | position: absolute; 275 | left: 50%; 276 | top: -8px; 277 | height: 0; 278 | width: 0; 279 | margin-left: -8px; 280 | border: solid transparent; 281 | border-bottom-color: #5f6fff; 282 | border-width: 8px; 283 | pointer-events: none; 284 | } 285 | 286 | .time-wrapper { 287 | display: block; 288 | position: relative; 289 | margin: 4px 0 0 0; 290 | z-index: 14; 291 | } 292 | 293 | .direction-l .time-wrapper { 294 | float: none; 295 | } 296 | 297 | .direction-r .time-wrapper { 298 | float: none; 299 | } 300 | 301 | .desc { 302 | position: relative; 303 | margin: 1em 0 0 0; 304 | padding: 1em; 305 | background: #5f6fff; 306 | -webkit-box-shadow: 0 0 1px rgba(53, 53, 53, 0.863); 307 | -moz-box-shadow: 0 0 1px rgba(53, 53, 53, 0.863); 308 | box-shadow: 0 0 1px rgba(53, 53, 53, 0.863); 309 | 310 | z-index: 15; 311 | } 312 | 313 | .direction-l .desc, 314 | .direction-r .desc { 315 | position: relative; 316 | margin: 1em 1em 0 1em; 317 | padding: 1em; 318 | 319 | z-index: 15; 320 | } 321 | } 322 | 323 | @media screen and (max-width: 660px) { 324 | .direction-l .desc, 325 | .direction-r .desc { 326 | margin: 1em 4em 0 4em; 327 | } 328 | } 329 | 330 | ul.desc-list li a { 331 | color: rgb(230, 230, 230); 332 | } 333 | li { 334 | color: white; 335 | } 336 | li a { 337 | color: white; 338 | } 339 | -------------------------------------------------------------------------------- /public/assets/css/tiny-slider.css: -------------------------------------------------------------------------------- 1 | .tns-outer{padding:0 !important}.tns-outer [hidden]{display:none !important}.tns-outer [aria-controls],.tns-outer [data-action]{cursor:pointer}.tns-slider{-webkit-transition:all 0s;-moz-transition:all 0s;transition:all 0s}.tns-slider>.tns-item{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.tns-horizontal.tns-subpixel{white-space:nowrap}.tns-horizontal.tns-subpixel>.tns-item{display:inline-block;vertical-align:top;white-space:normal}.tns-horizontal.tns-no-subpixel:after{content:'';display:table;clear:both}.tns-horizontal.tns-no-subpixel>.tns-item{float:left}.tns-horizontal.tns-carousel.tns-no-subpixel>.tns-item{margin-right:-100%}.tns-no-calc{position:relative;left:0}.tns-gallery{position:relative;left:0;min-height:1px}.tns-gallery>.tns-item{position:absolute;left:-100%;-webkit-transition:transform 0s, opacity 0s;-moz-transition:transform 0s, opacity 0s;transition:transform 0s, opacity 0s}.tns-gallery>.tns-slide-active{position:relative;left:auto !important}.tns-gallery>.tns-moving{-webkit-transition:all 0.25s;-moz-transition:all 0.25s;transition:all 0.25s}.tns-autowidth{display:inline-block}.tns-lazy-img{-webkit-transition:opacity 0.6s;-moz-transition:opacity 0.6s;transition:opacity 0.6s;opacity:0.6}.tns-lazy-img.tns-complete{opacity:1}.tns-ah{-webkit-transition:height 0s;-moz-transition:height 0s;transition:height 0s}.tns-ovh{overflow:hidden}.tns-visually-hidden{position:absolute;left:-10000em}.tns-transparent{opacity:0;visibility:hidden}.tns-fadeIn{opacity:1;filter:alpha(opacity=100);z-index:0}.tns-normal,.tns-fadeOut{opacity:0;filter:alpha(opacity=0);z-index:-1}.tns-vpfix{white-space:nowrap}.tns-vpfix>div,.tns-vpfix>li{display:inline-block}.tns-t-subp2{margin:0 auto;width:310px;position:relative;height:10px;overflow:hidden}.tns-t-ct{width:2333.3333333%;width:-webkit-calc(100% * 70 / 3);width:-moz-calc(100% * 70 / 3);width:calc(100% * 70 / 3);position:absolute;right:0}.tns-t-ct:after{content:'';display:table;clear:both}.tns-t-ct>div{width:1.4285714%;width:-webkit-calc(100% / 70);width:-moz-calc(100% / 70);width:calc(100% / 70);height:10px;float:left} 2 | 3 | /*# sourceMappingURL=sourcemaps/tiny-slider.css.map */ 4 | -------------------------------------------------------------------------------- /public/assets/fonts/LineIcons.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apurva-tech/unite/2f0ee34f4136838f7f617417e268efd17c8cd984/public/assets/fonts/LineIcons.eot -------------------------------------------------------------------------------- /public/assets/fonts/LineIcons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apurva-tech/unite/2f0ee34f4136838f7f617417e268efd17c8cd984/public/assets/fonts/LineIcons.ttf -------------------------------------------------------------------------------- /public/assets/fonts/LineIcons.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apurva-tech/unite/2f0ee34f4136838f7f617417e268efd17c8cd984/public/assets/fonts/LineIcons.woff -------------------------------------------------------------------------------- /public/assets/fonts/LineIcons.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apurva-tech/unite/2f0ee34f4136838f7f617417e268efd17c8cd984/public/assets/fonts/LineIcons.woff2 -------------------------------------------------------------------------------- /public/assets/images/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apurva-tech/unite/2f0ee34f4136838f7f617417e268efd17c8cd984/public/assets/images/.DS_Store -------------------------------------------------------------------------------- /public/assets/images/brands/graygrids.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/assets/images/brands/lineicons.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/assets/images/brands/pagebulb.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/assets/images/brands/uideck.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/assets/images/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /public/assets/images/feature/shape.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/assets/images/footer/footer-bg.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /public/assets/images/hero/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apurva-tech/unite/2f0ee34f4136838f7f617417e268efd17c8cd984/public/assets/images/hero/.DS_Store -------------------------------------------------------------------------------- /public/assets/images/hero/hero-bg.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/assets/images/logo/7btrrd.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apurva-tech/unite/2f0ee34f4136838f7f617417e268efd17c8cd984/public/assets/images/logo/7btrrd.gif -------------------------------------------------------------------------------- /public/assets/images/logo/logo-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apurva-tech/unite/2f0ee34f4136838f7f617417e268efd17c8cd984/public/assets/images/logo/logo-1.png -------------------------------------------------------------------------------- /public/assets/images/logo/logo-2-home.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 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 | -------------------------------------------------------------------------------- /public/assets/images/logo/logo-2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 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 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /public/assets/images/logo/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apurva-tech/unite/2f0ee34f4136838f7f617417e268efd17c8cd984/public/assets/images/logo/logo.png -------------------------------------------------------------------------------- /public/assets/images/logo/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 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 | -------------------------------------------------------------------------------- /public/assets/images/logo/macos-bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apurva-tech/unite/2f0ee34f4136838f7f617417e268efd17c8cd984/public/assets/images/logo/macos-bg.jpg -------------------------------------------------------------------------------- /public/assets/images/pricing/standard-bg.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/assets/images/service/service-bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apurva-tech/unite/2f0ee34f4136838f7f617417e268efd17c8cd984/public/assets/images/service/service-bg.jpg -------------------------------------------------------------------------------- /public/assets/images/team/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apurva-tech/unite/2f0ee34f4136838f7f617417e268efd17c8cd984/public/assets/images/team/.DS_Store -------------------------------------------------------------------------------- /public/assets/images/team/team-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apurva-tech/unite/2f0ee34f4136838f7f617417e268efd17c8cd984/public/assets/images/team/team-1.jpg -------------------------------------------------------------------------------- /public/assets/images/team/team-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apurva-tech/unite/2f0ee34f4136838f7f617417e268efd17c8cd984/public/assets/images/team/team-2.jpg -------------------------------------------------------------------------------- /public/assets/images/team/team-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apurva-tech/unite/2f0ee34f4136838f7f617417e268efd17c8cd984/public/assets/images/team/team-3.jpg -------------------------------------------------------------------------------- /public/assets/images/testimonial/testimonial-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apurva-tech/unite/2f0ee34f4136838f7f617417e268efd17c8cd984/public/assets/images/testimonial/testimonial-1.png -------------------------------------------------------------------------------- /public/assets/images/testimonial/testimonial-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apurva-tech/unite/2f0ee34f4136838f7f617417e268efd17c8cd984/public/assets/images/testimonial/testimonial-2.png -------------------------------------------------------------------------------- /public/assets/js/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apurva-tech/unite/2f0ee34f4136838f7f617417e268efd17c8cd984/public/assets/js/.DS_Store -------------------------------------------------------------------------------- /public/assets/js/main.js: -------------------------------------------------------------------------------- 1 | 2 | (function() { 3 | //===== Preloader 4 | 5 | window.onload = function () { 6 | window.setTimeout(fadeout, 500); 7 | } 8 | 9 | function fadeout() { 10 | document.querySelector('.preloader').style.opacity = '0'; 11 | document.querySelector('.preloader').style.display = 'none'; 12 | } 13 | 14 | 15 | /*===================================== 16 | Sticky 17 | ======================================= */ 18 | window.onscroll = function () { 19 | const header_navbar = document.querySelector(".navbar-area"); 20 | const sticky = header_navbar.offsetTop; 21 | const logo = document.querySelector('.navbar-brand img'); 22 | 23 | 24 | if (window.pageYOffset > sticky) { 25 | header_navbar.classList.add("sticky"); 26 | logo.src = 'assets/images/logo/logo-2.svg'; 27 | } else { 28 | header_navbar.classList.remove("sticky"); 29 | logo.src = 'assets/images/logo/logo.svg'; 30 | } 31 | 32 | // show or hide the back-top-top button 33 | var backToTo = document.querySelector(".scroll-top"); 34 | if (document.body.scrollTop > 50 || document.documentElement.scrollTop > 50) { 35 | backToTo.style.display = "flex"; 36 | } else { 37 | backToTo.style.display = "none"; 38 | } 39 | }; 40 | 41 | // Get the navbar 42 | 43 | 44 | // for menu scroll 45 | var pageLink = document.querySelectorAll('.page-scroll'); 46 | 47 | pageLink.forEach(elem => { 48 | elem.addEventListener('click', e => { 49 | e.preventDefault(); 50 | document.querySelector(elem.getAttribute('href')).scrollIntoView({ 51 | behavior: 'smooth', 52 | offsetTop: 1 - 60, 53 | }); 54 | }); 55 | }); 56 | 57 | // section menu active 58 | function onScroll(event) { 59 | var sections = document.querySelectorAll('.page-scroll'); 60 | var scrollPos = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop; 61 | 62 | for (var i = 0; i < sections.length; i++) { 63 | var currLink = sections[i]; 64 | var val = currLink.getAttribute('href'); 65 | var refElement = document.querySelector(val); 66 | var scrollTopMinus = scrollPos + 73; 67 | if (refElement.offsetTop <= scrollTopMinus && (refElement.offsetTop + refElement.offsetHeight > scrollTopMinus)) { 68 | document.querySelector('.page-scroll').classList.remove('active'); 69 | currLink.classList.add('active'); 70 | } else { 71 | currLink.classList.remove('active'); 72 | } 73 | } 74 | }; 75 | 76 | window.document.addEventListener('scroll', onScroll); 77 | 78 | 79 | //===== close navbar-collapse when a clicked 80 | let navbarToggler = document.querySelector(".navbar-toggler"); 81 | var navbarCollapse = document.querySelector(".navbar-collapse"); 82 | 83 | document.querySelectorAll(".page-scroll").forEach(e => 84 | e.addEventListener("click", () => { 85 | navbarToggler.classList.remove("active"); 86 | navbarCollapse.classList.remove('show') 87 | }) 88 | ); 89 | navbarToggler.addEventListener('click', function() { 90 | navbarToggler.classList.toggle("active"); 91 | }) 92 | 93 | 94 | // WOW active 95 | new WOW().init(); 96 | 97 | // ====== scroll top js 98 | function scrollTo(element, to = 0, duration= 1000) { 99 | 100 | const start = element.scrollTop; 101 | const change = to - start; 102 | const increment = 20; 103 | let currentTime = 0; 104 | 105 | const animateScroll = (() => { 106 | 107 | currentTime += increment; 108 | 109 | const val = Math.easeInOutQuad(currentTime, start, change, duration); 110 | 111 | element.scrollTop = val; 112 | 113 | if (currentTime < duration) { 114 | setTimeout(animateScroll, increment); 115 | } 116 | }); 117 | 118 | animateScroll(); 119 | }; 120 | 121 | Math.easeInOutQuad = function (t, b, c, d) { 122 | 123 | t /= d/2; 124 | if (t < 1) return c/2*t*t + b; 125 | t--; 126 | return -c/2 * (t*(t-2) - 1) + b; 127 | }; 128 | 129 | document.querySelector('.scroll-top').onclick = function () { 130 | scrollTo(document.documentElement); 131 | } 132 | 133 | })(); -------------------------------------------------------------------------------- /public/assets/js/polyfill.js: -------------------------------------------------------------------------------- 1 | 2 | !function() { 3 | "use strict"; 4 | function o() { 5 | var o = window, 6 | t = document; 7 | if (!("scrollBehavior" in t.documentElement.style && !0 !== o.__forceSmoothScrollPolyfill__)) { 8 | var l, 9 | e = o.HTMLElement || o.Element, 10 | r = 468, 11 | i = { 12 | scroll: o.scroll || o.scrollTo, 13 | scrollBy: o.scrollBy, 14 | elementScroll: e.prototype.scroll || n, 15 | scrollIntoView: e.prototype.scrollIntoView 16 | }, 17 | s = o.performance && o.performance.now ? o.performance.now.bind(o.performance) : Date.now, 18 | c = (l = o.navigator.userAgent, new RegExp(["MSIE ", "Trident/", "Edge/"].join("|")).test(l) ? 1 : 0); 19 | o.scroll = o.scrollTo = function() { 20 | void 0 !== arguments[0] && (!0 !== f(arguments[0]) ? h.call(o, t.body, void 0 !== arguments[0].left ? ~~arguments[0].left : o.scrollX || o.pageXOffset, void 0 !== arguments[0].top ? ~~arguments[0].top : o.scrollY || o.pageYOffset) : i.scroll.call(o, void 0 !== arguments[0].left ? arguments[0].left : "object" != typeof arguments[0] ? arguments[0] : o.scrollX || o.pageXOffset, void 0 !== arguments[0].top ? arguments[0].top : void 0 !== arguments[1] ? arguments[1] : o.scrollY || o.pageYOffset)) 21 | }, 22 | o.scrollBy = function() { 23 | void 0 !== arguments[0] && (f(arguments[0]) ? i.scrollBy.call(o, void 0 !== arguments[0].left ? arguments[0].left : "object" != typeof arguments[0] ? arguments[0] : 0, void 0 !== arguments[0].top ? arguments[0].top : void 0 !== arguments[1] ? arguments[1] : 0) : h.call(o, t.body, ~~arguments[0].left + (o.scrollX || o.pageXOffset), ~~arguments[0].top + (o.scrollY || o.pageYOffset))) 24 | }, 25 | e.prototype.scroll = e.prototype.scrollTo = function() { 26 | if (void 0 !== arguments[0]) 27 | if (!0 !== f(arguments[0])) { 28 | var o = arguments[0].left, 29 | t = arguments[0].top; 30 | h.call(this, this, void 0 === o ? this.scrollLeft : ~~o, void 0 === t ? this.scrollTop : ~~t) 31 | } else { 32 | if ("number" == typeof arguments[0] && void 0 === arguments[1]) 33 | throw new SyntaxError("Value could not be converted"); 34 | i.elementScroll.call(this, void 0 !== arguments[0].left ? ~~arguments[0].left : "object" != typeof arguments[0] ? ~~arguments[0] : this.scrollLeft, void 0 !== arguments[0].top ? ~~arguments[0].top : void 0 !== arguments[1] ? ~~arguments[1] : this.scrollTop) 35 | } 36 | }, 37 | e.prototype.scrollBy = function() { 38 | void 0 !== arguments[0] && (!0 !== f(arguments[0]) ? this.scroll({ 39 | left: ~~arguments[0].left + this.scrollLeft, 40 | top: ~~arguments[0].top + this.scrollTop, 41 | behavior: arguments[0].behavior 42 | }) : i.elementScroll.call(this, void 0 !== arguments[0].left ? ~~arguments[0].left + this.scrollLeft : ~~arguments[0] + this.scrollLeft, void 0 !== arguments[0].top ? ~~arguments[0].top + this.scrollTop : ~~arguments[1] + this.scrollTop)) 43 | }, 44 | e.prototype.scrollIntoView = function() { 45 | if (!0 !== f(arguments[0])) { 46 | var l = function(o) { 47 | for (; o !== t.body && !1 === (e = p(l = o, "Y") && a(l, "Y"), r = p(l, "X") && a(l, "X"), e || r);) 48 | o = o.parentNode || o.host; 49 | var l, 50 | e, 51 | r; 52 | return o 53 | }(this), 54 | e = l.getBoundingClientRect(), 55 | r = this.getBoundingClientRect(); 56 | l !== t.body ? (h.call(this, l, l.scrollLeft + r.left - e.left, l.scrollTop + r.top - e.top), "fixed" !== o.getComputedStyle(l).position && o.scrollBy({ 57 | left: e.left, 58 | top: e.top, 59 | behavior: "smooth" 60 | })) : o.scrollBy({ 61 | left: r.left, 62 | top: r.top, 63 | behavior: "smooth" 64 | }) 65 | } else 66 | i.scrollIntoView.call(this, void 0 === arguments[0] || arguments[0]) 67 | } 68 | } 69 | function n(o, t) { 70 | this.scrollLeft = o, 71 | this.scrollTop = t 72 | } 73 | function f(o) { 74 | if (null === o || "object" != typeof o || void 0 === o.behavior || "auto" === o.behavior || "instant" === o.behavior) 75 | return !0; 76 | if ("object" == typeof o && "smooth" === o.behavior) 77 | return !1; 78 | throw new TypeError("behavior member of ScrollOptions " + o.behavior + " is not a valid value for enumeration ScrollBehavior.") 79 | } 80 | function p(o, t) { 81 | return "Y" === t ? o.clientHeight + c < o.scrollHeight : "X" === t ? o.clientWidth + c < o.scrollWidth : void 0 82 | } 83 | function a(t, l) { 84 | var e = o.getComputedStyle(t, null)["overflow" + l]; 85 | return "auto" === e || "scroll" === e 86 | } 87 | function d(t) { 88 | var l, 89 | e, 90 | i, 91 | c, 92 | n = (s() - t.startTime) / r; 93 | c = n = n > 1 ? 1 : n, 94 | l = .5 * (1 - Math.cos(Math.PI * c)), 95 | e = t.startX + (t.x - t.startX) * l, 96 | i = t.startY + (t.y - t.startY) * l, 97 | t.method.call(t.scrollable, e, i), 98 | e === t.x && i === t.y || o.requestAnimationFrame(d.bind(o, t)) 99 | } 100 | function h(l, e, r) { 101 | var c, 102 | f, 103 | p, 104 | a, 105 | h = s(); 106 | l === t.body ? (c = o, f = o.scrollX || o.pageXOffset, p = o.scrollY || o.pageYOffset, a = i.scroll) : (c = l, f = l.scrollLeft, p = l.scrollTop, a = n), 107 | d({ 108 | scrollable: c, 109 | method: a, 110 | startTime: h, 111 | startX: f, 112 | startY: p, 113 | x: e, 114 | y: r 115 | }) 116 | } 117 | } 118 | "object" == typeof exports && "undefined" != typeof module ? module.exports = { 119 | polyfill: o 120 | } : o() 121 | }(); 122 | -------------------------------------------------------------------------------- /public/assets/js/wow.min.js: -------------------------------------------------------------------------------- 1 | /*! WOW wow.js - v1.3.0 - 2016-10-04 2 | * https://wowjs.uk 3 | * Copyright (c) 2016 Thomas Grainger; Licensed MIT */!function(a,b){if("function"==typeof define&&define.amd)define(["module","exports"],b);else if("undefined"!=typeof exports)b(module,exports);else{var c={exports:{}};b(c,c.exports),a.WOW=c.exports}}(this,function(a,b){"use strict";function c(a,b){if(!(a instanceof b))throw new TypeError("Cannot call a class as a function")}function d(a,b){return b.indexOf(a)>=0}function e(a,b){for(var c in b)if(null==a[c]){var d=b[c];a[c]=d}return a}function f(a){return/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(a)}function g(a){var b=arguments.length<=1||void 0===arguments[1]?!1:arguments[1],c=arguments.length<=2||void 0===arguments[2]?!1:arguments[2],d=arguments.length<=3||void 0===arguments[3]?null:arguments[3],e=void 0;return null!=document.createEvent?(e=document.createEvent("CustomEvent"),e.initCustomEvent(a,b,c,d)):null!=document.createEventObject?(e=document.createEventObject(),e.eventType=a):e.eventName=a,e}function h(a,b){null!=a.dispatchEvent?a.dispatchEvent(b):b in(null!=a)?a[b]():"on"+b in(null!=a)&&a["on"+b]()}function i(a,b,c){null!=a.addEventListener?a.addEventListener(b,c,!1):null!=a.attachEvent?a.attachEvent("on"+b,c):a[b]=c}function j(a,b,c){null!=a.removeEventListener?a.removeEventListener(b,c,!1):null!=a.detachEvent?a.detachEvent("on"+b,c):delete a[b]}function k(){return"innerHeight"in window?window.innerHeight:document.documentElement.clientHeight}Object.defineProperty(b,"__esModule",{value:!0});var l,m,n=function(){function a(a,b){for(var c=0;c=0){var b=a.target||a.srcElement;b.className=b.className.replace(this.config.animateClass,"").trim()}}},{key:"customStyle",value:function(a,b,c,d,e){return b&&this.cacheAnimationName(a),a.style.visibility=b?"hidden":"visible",c&&this.vendorSet(a.style,{animationDuration:c}),d&&this.vendorSet(a.style,{animationDelay:d}),e&&this.vendorSet(a.style,{animationIterationCount:e}),this.vendorSet(a.style,{animationName:b?"none":this.cachedAnimationName(a)}),a}},{key:"vendorSet",value:function(a,b){for(var c in b)if(b.hasOwnProperty(c)){var d=b[c];a[""+c]=d;for(var e=0;e=e&&f>=c}},{key:"disabled",value:function(){return!this.config.mobile&&f(navigator.userAgent)}}]),a}();b["default"]=r,a.exports=b["default"]}); -------------------------------------------------------------------------------- /public/js/chat.js: -------------------------------------------------------------------------------- 1 | const socket = io(`${window.location.origin}/`); 2 | const roomUsers = []; 3 | 4 | addRoomsToUser(); 5 | // To toggle between dark and light theme 6 | const toggleButton = document.querySelector(".dark-light"); 7 | 8 | toggleButton.addEventListener("click", () => { 9 | document.body.classList.toggle("light-mode"); 10 | }); 11 | 12 | const scrollToBottom = () => { 13 | var d = $(".main__chat_window"); 14 | d.scrollTop(d.prop("scrollHeight")); 15 | }; 16 | // To convert URL in the text to links 17 | function linkify(inputText) { 18 | let replacedText, replacePattern1, replacePattern2, replacePattern3; 19 | 20 | //URLs starting with http://, https://, or ftp:// 21 | replacePattern1 = 22 | /(\b(https?|ftp):\/\/[-A-Z0-9+&@#\/%?=_|!:,.;]*[-A-Z0-9+&@#\/%=_|])/gim; 23 | replacedText = inputText.replace( 24 | replacePattern1, 25 | '$1' 26 | ); 27 | 28 | //URLs starting with "www." (without // before it, or it'd re-link the ones done above). 29 | replacePattern2 = /(^|[^\/])(www\.[\S]+(\b|$))/gim; 30 | replacedText = replacedText.replace( 31 | replacePattern2, 32 | '$1$2' 33 | ); 34 | 35 | //Change email addresses to mailto:: links. 36 | replacePattern3 = /(([a-zA-Z0-9\-\_\.])+@[a-zA-Z\_]+?(\.[a-zA-Z]{2,6})+)/gim; 37 | replacedText = replacedText.replace( 38 | replacePattern3, 39 | '$1' 40 | ); 41 | 42 | return replacedText; 43 | } 44 | // Join Room and Render messages with user name and photo from Firebase 45 | firebase.auth().onAuthStateChanged(async function (user) { 46 | if (user) { 47 | console.log(user); 48 | socket.emit("join-room", ROOM_ID, user.uid); 49 | let resp = await fetch(`${window.location.origin}/message/get`, { 50 | method: "POST", 51 | headers: { 52 | Accept: "application/json", 53 | "Content-Type": "application/json", 54 | }, 55 | body: JSON.stringify({ room: ROOM_ID }), 56 | }); 57 | resp = await resp.json(); 58 | resp.messages.forEach((message) => { 59 | $("ul").append( 60 | `
  • ${message.user.displayName}
    ${linkify(message.content)}
  • ` 63 | ); 64 | scrollToBottom(); 65 | }); 66 | } 67 | }); 68 | 69 | socket.on("user-connected", (userId) => { 70 | roomUsers.push(userId); 71 | }); 72 | // On enter press, to send message 73 | let text = $("input"); 74 | $("html").keydown(function (e) { 75 | if (e.which == 13 && text.val().length !== 0) { 76 | const user = getUser(); 77 | console.log(user); 78 | // let authId = user.uuid; 79 | socket.emit("message", { message: text.val(), user }); 80 | text.val(""); 81 | } 82 | }); 83 | $("#send-text").on("click", function () { 84 | if (text.val().length !== 0) { 85 | // const user = getUser(); 86 | // let authId = user.uuid; 87 | socket.emit("message", { message: text.val(), user: globaluser }); 88 | text.val(""); 89 | } 90 | }); 91 | socket.on("createMessage", ({ message, user }) => { 92 | console.log(message); 93 | $("ul").append( 94 | `
  • ${user.displayName}
    ${linkify(message.message.content)}
  • ` 97 | ); 98 | scrollToBottom(); 99 | }); 100 | -------------------------------------------------------------------------------- /public/js/firebaselogin.js: -------------------------------------------------------------------------------- 1 | // Your web app's Firebase configuration 2 | var firebaseConfig = { 3 | apiKey: "AIzaSyDUTgqbHDfQHC6Nav6K2EgOhAYGSk7hmeI", 4 | authDomain: "authtest-75624.firebaseapp.com", 5 | projectId: "authtest-75624", 6 | storageBucket: "authtest-75624.appspot.com", 7 | messagingSenderId: "522422328170", 8 | appId: "1:522422328170:web:40c7fe5f75b6ac59a46313", 9 | }; 10 | // Initialize Firebase 11 | 12 | if (!firebase.apps.length) { 13 | firebase.initializeApp(firebaseConfig); 14 | } else { 15 | firebase.app(); // if already initialized, use that one 16 | } 17 | 18 | let provider = new firebase.auth.GoogleAuthProvider(); 19 | var globaluser = null; 20 | 21 | async function GoogleLogin() { 22 | console.log("Login Btn Call"); 23 | firebase 24 | .auth() 25 | .signInWithPopup(provider) 26 | .then(async (res) => { 27 | if (res.additionalUserInfo.isNewUser) { 28 | const { user } = res; 29 | 30 | const data = await fetch(`${window.location.origin}/createuser`, { 31 | method: "POST", 32 | headers: { 33 | Accept: "application/json", 34 | "Content-Type": "application/json", 35 | }, 36 | body: JSON.stringify(user), 37 | }); 38 | const response = await data.json(); 39 | console.log("user:", response); 40 | } 41 | }) 42 | .catch((e) => { 43 | console.log(e); 44 | }); 45 | } 46 | 47 | function joinMeet() { 48 | let meetingID = document.getElementById("meeting-id").value; 49 | // let meetingID = prompt("Enter meeting ID"); 50 | if (meetingID.trim() !== "") { 51 | document.location.href = `/chat/${meetingID}`; 52 | } 53 | } 54 | 55 | function checkAuthState() { 56 | firebase.auth().onAuthStateChanged((user) => { 57 | if (user) { 58 | globaluser = user; 59 | } 60 | }); 61 | } 62 | 63 | function getUser() { 64 | const user = firebase.auth().currentUser; 65 | return user; 66 | } 67 | 68 | function LogoutUser() { 69 | console.log("Logout Btn Call"); 70 | firebase 71 | .auth() 72 | .signOut() 73 | .then(() => { 74 | document.getElementById("LoginScreen").style.display = "block"; 75 | document.getElementById("dashboard").style.display = "none"; 76 | }) 77 | .catch((e) => { 78 | console.log(e); 79 | }); 80 | } 81 | checkAuthState(); 82 | 83 | function copyToClipboard(text) { 84 | navigator.clipboard.writeText(text).then( 85 | function () { 86 | return true; 87 | }, 88 | function (err) { 89 | console.error("Async: Could not copy text: ", err); 90 | } 91 | ); 92 | } 93 | 94 | function addRoomsToUser() { 95 | firebase.auth().onAuthStateChanged(async function (user) { 96 | if (user) { 97 | try { 98 | await fetch(`${location.origin}/room/add`, { 99 | method: "POST", 100 | headers: { 101 | "Content-Type": "application/json", 102 | }, 103 | body: JSON.stringify({ 104 | email: user.email, 105 | roomId: ROOM_ID, 106 | }), 107 | }); 108 | } catch (err) { 109 | console.error(err); 110 | } 111 | } 112 | }); 113 | } 114 | -------------------------------------------------------------------------------- /public/js/home.js: -------------------------------------------------------------------------------- 1 | $(function () { 2 | $(".status-button:not(.open)").on("click", function (e) { 3 | $(".overlay-app").addClass("is-active"); 4 | }); 5 | $(".pop-up .close").click(function () { 6 | $(".overlay-app").removeClass("is-active"); 7 | }); 8 | }); 9 | 10 | $(".status-button:not(.open)").click(function () { 11 | $(".pop-up").addClass("visible"); 12 | }); 13 | 14 | $(".pop-up .close").click(function () { 15 | $(".pop-up").removeClass("visible"); 16 | }); 17 | 18 | $(function () { 19 | $(".status-button1:not(.open1)").on("click", function (e) { 20 | $(".overlay-app").addClass("is-active"); 21 | }); 22 | $(".pop-up1 .close1").click(function () { 23 | $(".overlay-app").removeClass("is-active"); 24 | }); 25 | }); 26 | 27 | $(".status-button1:not(.open1)").click(function () { 28 | $(".pop-up1").addClass("visible"); 29 | }); 30 | 31 | $(".pop-up1 .close1").click(function () { 32 | $(".pop-up1").removeClass("visible"); 33 | }); 34 | // Pop up for join room 35 | $(function () { 36 | $(".status-button2:not(.open2)").on("click", function (e) { 37 | $(".overlay-app").addClass("is-active"); 38 | }); 39 | $(".pop-up2 .close2").click(function () { 40 | $(".overlay-app").removeClass("is-active"); 41 | }); 42 | }); 43 | 44 | $(".status-button2:not(.open2)").click(function () { 45 | $(".pop-up2").addClass("visible"); 46 | }); 47 | 48 | $(".pop-up2 .close2").click(function () { 49 | $(".pop-up2").removeClass("visible"); 50 | }); 51 | const toggleButton = document.querySelector(".dark-light"); 52 | 53 | toggleButton.addEventListener("click", () => { 54 | document.body.classList.toggle("light-mode"); 55 | }); 56 | 57 | const createRoom = async () => { 58 | const roomId = document.querySelector("#room-id"); 59 | const enterRoom = document.querySelector("#enter-room"); 60 | const copyRoomId = document.querySelector("#copy-room-id"); 61 | const roomName = document.querySelector("#room-name").value; 62 | const data = await fetch(`${window.location.origin}/room`, { 63 | method: "POST", 64 | headers: { 65 | Accept: "application/json", 66 | "Content-Type": "application/json", 67 | }, 68 | body: JSON.stringify({ 69 | email: globaluser.email, 70 | name: roomName, 71 | }), 72 | }); 73 | const response = await data.json(); 74 | 75 | roomId.style.display = "block"; 76 | if (response.success === true) { 77 | enterRoom.style.display = "inline-block"; 78 | copyRoomId.style.display = "inline-block"; 79 | roomId.innerHTML += response.room.roomID; 80 | roomId.onclick = () => { 81 | console.log("copying: " + response.room.roomID); 82 | roomId.style.background = "rgba(255,255, 255, 0.5)"; 83 | copyToClipboard(response.room.roomID); 84 | setTimeout(() => { 85 | roomId.style.background = "inherit"; 86 | }, 1500); 87 | }; 88 | enterRoom.addEventListener("click", () => { 89 | window.location.href = `/chat/${response.room.roomID}`; 90 | }); 91 | } else { 92 | roomId.style.color = "red"; 93 | roomId.innerHTML = response.message; 94 | } 95 | }; 96 | 97 | const roomRender = (ele, room) => { 98 | ele.innerHTML += ` 99 | 100 | 101 | 102 | 103 | 104 | ${room.name || room.roomID.substring(0, 18) + "..."} 105 | 106 | `; 107 | }; 108 | 109 | firebase.auth().onAuthStateChanged(async function (user) { 110 | if (user) { 111 | document.querySelector("#username").innerHTML = 112 | user?.displayName || "username"; // 113 | document.querySelector("#useremail").innerHTML = user?.email || "email"; // 114 | document.getElementById("profile-img").src = user?.photoURL; 115 | 116 | // <--------------------- Get user & user's rooms ------------------------> 117 | // After the user is authenticated, get their rooms and populate the dropdown 118 | // This is done on an authStateCHanged event which is fired when the user is 119 | // signed in or when the user signs out 120 | 121 | let ownedRooms, dbUser; 122 | const userRooms = document.getElementById("user-rooms"); 123 | const lastRooms = document.getElementById("last-rooms"); 124 | 125 | try { 126 | ownedRooms = await fetch(`${location.origin}/room/user`, { 127 | method: "POST", 128 | headers: { 129 | "Content-Type": "application/json", 130 | }, 131 | body: JSON.stringify({ 132 | email: user.email, 133 | }), 134 | }); 135 | ownedRooms = await ownedRooms.json(); 136 | console.log(ownedRooms); 137 | if (!ownedRooms?.success) { 138 | userRooms.innerHTML = ` 139 | 140 | You have not created any rooms 141 | 142 | `; 143 | } else if (ownedRooms.rooms.length > 0) { 144 | userRooms.innerHTML = ""; 145 | ownedRooms?.rooms?.forEach((room) => roomRender(userRooms, room)); 146 | } 147 | } catch (err) { 148 | console.error(err); 149 | } 150 | try { 151 | dbUser = await fetch(`${location.origin}/user/get`, { 152 | method: "POST", 153 | headers: { 154 | "Content-Type": "application/json", 155 | }, 156 | body: JSON.stringify({ 157 | email: user.email, 158 | }), 159 | }); 160 | dbUser = await dbUser.json(); 161 | console.log(dbUser); 162 | if (dbUser.user.rooms.length === 0) { 163 | lastRooms.innerHTML = ` 164 | 165 | You have not visited any rooms 166 | 167 | `; 168 | } else { 169 | lastRooms.innerHTML = ""; 170 | dbUser.user?.rooms?.forEach((room) => roomRender(lastRooms, room)); 171 | } 172 | } catch (err) { 173 | console.error(err); 174 | } 175 | } else { 176 | document.querySelector("#username").innerHTML = "Not Signed in"; // 177 | document.querySelector("#useremail").innerHTML = ""; // 178 | } 179 | }); 180 | 181 | // < -------------------- Event Scheduling -------------------- > 182 | async function SendSchedule() { 183 | let obj = {}; 184 | 185 | let emails; 186 | emails = validateMultipleEmails($("#emails").val()); 187 | emails.push({ email: globaluser.email }); 188 | let eventStart = $("#startday").val(); 189 | let eventEnd = $("#endday").val(); 190 | const meet_summary = $("#summary").val(); 191 | 192 | obj["emails"] = emails; 193 | obj["eventStart"] = eventStart; 194 | obj["eventEnd"] = eventEnd; 195 | obj["summary"] = meet_summary; 196 | obj["organiser"] = globaluser.email; 197 | console.log(obj); 198 | 199 | let res = await fetch(`${window.location.origin}/invite`, { 200 | method: "POST", 201 | body: JSON.stringify(obj), 202 | headers: { 203 | "content-type": "application/json", 204 | }, 205 | }); 206 | let data = await res.json(); 207 | // parse the response as JSON 208 | console.log("Response data: " + data["success"]); 209 | if (data["success"]) { 210 | document.getElementById("status").innerHTML = "Meeting has been scheduled"; 211 | } else { 212 | document.getElementById("status").innerHTML = data["data"]; 213 | } 214 | console.log(data); 215 | } 216 | function validateMultipleEmails(emailInput) { 217 | // Get value on emails input as a string 218 | let emails = emailInput; 219 | // Split string by comma into an array 220 | emails = emails.split(","); 221 | let valid = true; 222 | let regex = 223 | /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; 224 | let invalidEmails = [], 225 | validEmails = []; 226 | for (let i = 0; i < emails.length; i++) { 227 | // Trim whitespaces from email address 228 | emails[i] = emails[i].trim(); 229 | // Check email against our regex to determine if email is valid 230 | if (emails[i] == "" || !regex.test(emails[i])) { 231 | invalidEmails.push(emails[i]); 232 | } else { 233 | validEmails.push({ email: emails[i] }); 234 | } 235 | } 236 | // Output invalid emails 237 | $(".form-group .text-danger").remove(); 238 | if (invalidEmails != 0) { 239 | $(".form-group").append( 240 | '

    Invalid emails: ' + 241 | invalidEmails.join(", ") + 242 | "

    " 243 | ); 244 | } 245 | if (validEmails.length === emails.length) { 246 | return validEmails; 247 | } else return null; 248 | } 249 | -------------------------------------------------------------------------------- /public/js/landing.js: -------------------------------------------------------------------------------- 1 | const loginBtn = document.querySelector("#login-btn"); 2 | 3 | const goHome = () => (document.location.href = "/home"); 4 | 5 | firebase.auth().onAuthStateChanged(function (user) { 6 | if (user) { 7 | loginBtn.innerHTML = "Home Page"; 8 | loginBtn.removeEventListener("click", GoogleLogin, false); 9 | loginBtn.addEventListener("click", goHome, false); 10 | } else { 11 | loginBtn.innerHTML = "Login with Google"; 12 | loginBtn.removeEventListener("click", goHome, false); 13 | loginBtn.addEventListener("click", GoogleLogin, false); 14 | } 15 | }); 16 | async function SendFeedback() { 17 | let obj = {}; 18 | 19 | // get values of all inputs 20 | userEmail = document.getElementById("user-email").value; 21 | userName = document.getElementById("user-name").value; 22 | userMessage = document.getElementById("user-query").value; 23 | 24 | // add them to req body object 25 | obj["email"] = userEmail; 26 | obj["name"] = userName; 27 | obj["message"] = userMessage; 28 | 29 | console.log(obj); 30 | 31 | let res = await fetch(`${window.location.origin}/feedback`, { 32 | method: "POST", 33 | body: JSON.stringify(obj), 34 | headers: { 35 | "content-type": "application/json", 36 | }, 37 | }); 38 | 39 | // parse the response as JSON 40 | let data = await res.json(); 41 | 42 | console.log(data); 43 | } -------------------------------------------------------------------------------- /public/js/logout.js: -------------------------------------------------------------------------------- 1 | // authentication protection 2 | 3 | function checkAuthState() { 4 | firebase.auth().onAuthStateChanged((user) => { 5 | if (user) { 6 | console.log("User is signed in."); 7 | } else { 8 | location.href = "/landing"; 9 | } 10 | }); 11 | } 12 | 13 | function LogoutUser() { 14 | console.log("Logout Btn Call"); 15 | firebase 16 | .auth() 17 | .signOut() 18 | .then(() => { 19 | location.href = "/landing"; 20 | }) 21 | .catch((e) => { 22 | console.log(e); 23 | }); 24 | } 25 | checkAuthState(); 26 | -------------------------------------------------------------------------------- /public/script.js: -------------------------------------------------------------------------------- 1 | // socket.io client initialization 2 | const socket = io("/"); 3 | // Js method to get ID 4 | const videoGrid = document.getElementById("video-grid"); 5 | // To establish new WebRTC connection using PeerJS 6 | const myPeer = new Peer(); 7 | let myVideoStream; 8 | const myVideo = document.createElement("video"); 9 | myVideo.muted = true; 10 | let peers = {}, 11 | currentPeer = []; 12 | 13 | // Persist the data of last few rooms visited on DB 14 | addRoomsToUser(); 15 | 16 | const toggleButton = document.querySelector(".dark-light"); 17 | 18 | toggleButton.addEventListener("click", () => { 19 | document.body.classList.toggle("light-mode"); 20 | }); 21 | 22 | function linkify(inputText) { 23 | let replacedText, replacePattern1, replacePattern2, replacePattern3; 24 | 25 | //URLs starting with http://, https://, or ftp:// 26 | replacePattern1 = 27 | /(\b(https?|ftp):\/\/[-A-Z0-9+&@#\/%?=_|!:,.;]*[-A-Z0-9+&@#\/%=_|])/gim; 28 | replacedText = inputText.replace( 29 | replacePattern1, 30 | '$1' 31 | ); 32 | 33 | //URLs starting with "www." (without // before it, or it'd re-link the ones done above). 34 | replacePattern2 = /(^|[^\/])(www\.[\S]+(\b|$))/gim; 35 | replacedText = replacedText.replace( 36 | replacePattern2, 37 | '$1$2' 38 | ); 39 | 40 | //Change email addresses to mailto:: links. 41 | replacePattern3 = /(([a-zA-Z0-9\-\_\.])+@[a-zA-Z\_]+?(\.[a-zA-Z]{2,6})+)/gim; 42 | replacedText = replacedText.replace( 43 | replacePattern3, 44 | '$1' 45 | ); 46 | 47 | return replacedText; 48 | } 49 | // Browser API to get user video & audio 50 | navigator.mediaDevices 51 | .getUserMedia({ 52 | video: true, 53 | audio: true, 54 | }) 55 | .then((stream) => { 56 | myVideoStream = stream; 57 | // Add video stream to video element 58 | addVideoStream(myVideo, stream); 59 | // Event of myPeer object 60 | myPeer.on("call", (call) => { 61 | call.answer(stream); 62 | currentPeer.push(call.peerConnection); 63 | const video = document.createElement("video"); 64 | call.on("stream", (userVideoStream) => { 65 | addVideoStream(video, userVideoStream); 66 | }); 67 | call.on("close", () => { 68 | video.remove(); 69 | }); 70 | }); 71 | 72 | socket.on("user-connected", (userId) => { 73 | connectToNewUser(userId, stream); 74 | }); 75 | // Input text value 76 | let text = $("input"); 77 | // When press enter send message 78 | $("html").keydown(function (e) { 79 | if (e.which == 13 && text.val().length !== 0) { 80 | const user = getUser(); 81 | console.log(user); 82 | // let authId = user.uuid; 83 | // Emit event on the connected socket connection 84 | socket.emit("message", { message: text.val(), user }); 85 | text.val(""); 86 | } 87 | }); 88 | // Callback function for user and message to reflect on the interface 89 | socket.on("createMessage", ({ message, user }) => { 90 | console.log(message); 91 | $("ul").append( 92 | `
  • ${user.displayName}
    ${linkify(message.message.content)}
  • ` 95 | ); 96 | scrollToBottom(); 97 | }); 98 | }); 99 | 100 | // Remove peers once the connection is deleted 101 | socket.on("user-disconnected", (userId) => { 102 | if (peers[userId]) { 103 | peers[userId].close(); 104 | let i = currentPeer.indexOf(peers[userId].peerConnection); 105 | currentPeer.slice(i, i + 1); 106 | delete peers[userId]; 107 | } 108 | }); 109 | 110 | // Fetch and display previous messages from the DB onto the application 111 | myPeer.on("open", async (id) => { 112 | socket.emit("join-room", ROOM_ID, id); 113 | let resp = await fetch(`${window.location.origin}/message/get`, { 114 | method: "POST", 115 | headers: { 116 | Accept: "application/json", 117 | "Content-Type": "application/json", 118 | }, 119 | body: JSON.stringify({ room: ROOM_ID }), 120 | }); 121 | resp = await resp.json(); 122 | resp.messages.forEach((message) => { 123 | $("ul").append( 124 | `
  • ${message.user.displayName}
    ${linkify(message.content)}
  • ` 127 | ); 128 | }); 129 | scrollToBottom(); 130 | }); 131 | 132 | function connectToNewUser(userId, stream) { 133 | const call = myPeer.call(userId, stream); 134 | const video = document.createElement("video"); 135 | call.on("stream", (userVideoStream) => { 136 | addVideoStream(video, userVideoStream); 137 | }); 138 | call.on("close", () => { 139 | video.remove(); 140 | }); 141 | 142 | peers[userId] = call; 143 | currentPeer.push(call.peerConnection); 144 | } 145 | 146 | function addVideoStream(video, stream) { 147 | video.srcObject = stream; 148 | video.addEventListener("loadedmetadata", () => { 149 | video.play(); 150 | }); 151 | video.ondblclick = (event) => { 152 | video.webkitEnterFullScreen(); 153 | video.play(); 154 | }; 155 | video.onpause = () => video.play(); 156 | videoGrid.append(video); 157 | } 158 | 159 | const scrollToBottom = () => { 160 | var d = $(".main__chat_window"); 161 | d.scrollTop(d.prop("scrollHeight")); 162 | }; 163 | 164 | const muteUnmute = () => { 165 | const enabled = myVideoStream.getAudioTracks()[0].enabled; 166 | if (enabled) { 167 | myVideoStream.getAudioTracks()[0].enabled = false; 168 | setUnmuteButton(); 169 | } else { 170 | setMuteButton(); 171 | myVideoStream.getAudioTracks()[0].enabled = true; 172 | } 173 | }; 174 | 175 | const playStop = () => { 176 | let enabled = myVideoStream.getVideoTracks()[0].enabled; 177 | if (enabled) { 178 | myVideoStream.getVideoTracks()[0].enabled = false; 179 | setPlayVideo(); 180 | } else { 181 | setStopVideo(); 182 | myVideoStream.getVideoTracks()[0].enabled = true; 183 | } 184 | }; 185 | 186 | const setMuteButton = () => { 187 | const html = ` 188 | 189 | Mute 190 | `; 191 | document.querySelector(".main__mute_button").innerHTML = html; 192 | }; 193 | 194 | const setUnmuteButton = () => { 195 | const html = ` 196 | 197 | Unmute 198 | `; 199 | document.querySelector(".main__mute_button").innerHTML = html; 200 | }; 201 | 202 | const setStopVideo = () => { 203 | const html = ` 204 | 205 | Stop Video 206 | `; 207 | document.querySelector(".main__video_button").innerHTML = html; 208 | }; 209 | 210 | const setPlayVideo = () => { 211 | const html = ` 212 | 213 | Play Video 214 | `; 215 | document.querySelector(".main__video_button").innerHTML = html; 216 | }; 217 | 218 | //screenShare 219 | const screenshare = () => { 220 | navigator.mediaDevices 221 | .getDisplayMedia({ 222 | video: { 223 | cursor: "always", 224 | }, 225 | audio: { 226 | echoCancellation: true, 227 | noiseSuppression: true, 228 | }, 229 | }) 230 | .then((stream) => { 231 | let videoTrack = stream.getVideoTracks()[0]; 232 | videoTrack.onended = function () { 233 | stopScreenShare(); 234 | }; 235 | for (let x = 0; x < currentPeer.length; x++) { 236 | let sender = currentPeer[x].getSenders().find(function (s) { 237 | return s.track.kind == videoTrack.kind; 238 | }); 239 | 240 | sender.replaceTrack(videoTrack); 241 | } 242 | }); 243 | }; 244 | 245 | function stopScreenShare() { 246 | let videoTrack = myVideoStream.getVideoTracks()[0]; 247 | for (let x = 0; x < currentPeer.length; x++) { 248 | let sender = currentPeer[x].getSenders().find(function (s) { 249 | return s.track.kind == videoTrack.kind; 250 | }); 251 | sender.replaceTrack(videoTrack); 252 | } 253 | } 254 | -------------------------------------------------------------------------------- /public/styles/chatroom.css: -------------------------------------------------------------------------------- 1 | @import url("https://fonts.googleapis.com/css2?family=Poppins:wght@200;300;400;500;600;700&display=swap"); 2 | * { 3 | outline: none; 4 | box-sizing: border-box; 5 | } 6 | 7 | :root { 8 | --theme-bg-color: rgba(16 18 27 / 40%); 9 | --border-color: rgba(113 119 144 / 25%); 10 | --theme-color: #f9fafb; 11 | --inactive-color: rgb(113 119 144 / 78%); 12 | --body-font: "Poppins", sans-serif; 13 | --hover-menu-bg: rgba(12 15 25 / 30%); 14 | --content-title-color: #999ba5; 15 | --content-bg: rgb(146 151 179 / 13%); 16 | --button-inactive: rgb(249 250 251 / 55%); 17 | --dropdown-bg: #21242d; 18 | --dropdown-hover: rgb(42 46 60); 19 | --popup-bg: rgb(22 25 37); 20 | --search-bg: #14162b; 21 | --overlay-bg: rgba(36, 39, 59, 0.3); 22 | --scrollbar-bg: rgb(1 2 3 / 40%); 23 | } 24 | 25 | .light-mode { 26 | --theme-bg-color: rgb(255 255 255 / 31%); 27 | --theme-color: #3c3a3a; 28 | --inactive-color: #333333; 29 | --button-inactive: #3c3a3a; 30 | --search-bg: rgb(255 255 255 / 31%); 31 | --dropdown-bg: #f7f7f7; 32 | --overlay-bg: rgb(255 255 255 / 30%); 33 | --dropdown-hover: rgb(236 236 236); 34 | --border-color: rgb(255 255 255 / 35%); 35 | --popup-bg: rgb(255 255 255); 36 | --hover-menu-bg: rgba(255 255 255 / 35%); 37 | --scrollbar-bg: rgb(255 253 253 / 57%); 38 | --content-title-color: --theme-color; 39 | } 40 | 41 | html { 42 | box-sizing: border-box; 43 | -webkit-font-smoothing: antialiased; 44 | max-width: 100%; 45 | overflow-x: hidden; 46 | } 47 | 48 | body { 49 | max-width: 100%; 50 | overflow-x: hidden; 51 | 52 | font-family: var(--body-font); 53 | background-image: url("/assets/images/logo/macos-bg.jpg"); 54 | background-size: cover; 55 | background-position: center; 56 | display: flex; 57 | justify-content: center; 58 | align-items: center; 59 | flex-direction: column; 60 | padding: 2em; 61 | width: 100%; 62 | height: 100vh; 63 | } 64 | @media screen and (max-width: 480px) { 65 | body { 66 | padding: 0.8em; 67 | } 68 | } 69 | 70 | img { 71 | max-width: 100%; 72 | } 73 | 74 | .dark-light { 75 | position: fixed; 76 | top: 50px; 77 | right: 30px; 78 | background-color: var(--dropdown-bg); 79 | box-shadow: -1px 3px 8px -1px rgba(0, 0, 0, 0.2); 80 | padding: 8px; 81 | border-radius: 50%; 82 | z-index: 3; 83 | cursor: pointer; 84 | } 85 | .dark-light svg { 86 | width: 24px; 87 | flex-shrink: 0; 88 | fill: #ffce45; 89 | stroke: #ffce45; 90 | transition: 0.5s; 91 | } 92 | 93 | .light-mode .dark-light svg { 94 | fill: transparent; 95 | stroke: var(--theme-color); 96 | } 97 | .light-mode .profile-img { 98 | border: 2px solid var(--theme-bg-color); 99 | } 100 | .light-mode .content-section ul { 101 | background-color: var(--theme-bg-color); 102 | } 103 | .light-mode .pop-up__title { 104 | border-color: var(--theme-color); 105 | } 106 | .light-mode .dropdown.is-active ul { 107 | background-color: rgba(255, 255, 255, 0.94); 108 | } 109 | .light-mode .messages li { 110 | color: rgba(0, 0, 0, 0.94); 111 | } 112 | .light-mode h4 { 113 | color: rgba(0, 0, 0, 0.94); 114 | } 115 | .light-mode #chat_message { 116 | color: rgba(0, 0, 0, 0.94); 117 | } 118 | .light-mode .back-button img { 119 | content: url("https://img.icons8.com/ios-glyphs/90/000000/circled-left.png"); 120 | } 121 | 122 | .back-button { 123 | position: fixed; 124 | bottom: 50px; 125 | left: 30px; 126 | background-color: var(--dropdown-bg); 127 | box-shadow: -1px 3px 8px -1px rgba(0, 0, 0, 0.2); 128 | padding: 8px; 129 | border-radius: 50%; 130 | z-index: 3; 131 | cursor: pointer; 132 | } 133 | 134 | body.light-mode:before { 135 | content: ""; 136 | position: absolute; 137 | left: 0; 138 | top: 0; 139 | width: 100%; 140 | height: 100vh; 141 | background: linear-gradient( 142 | 180deg, 143 | rgba(255, 255, 255, 0.72) 0%, 144 | rgba(255, 255, 255, 0.45) 100% 145 | ); 146 | -webkit-backdrop-filter: saturate(3); 147 | backdrop-filter: saturate(3); 148 | } 149 | 150 | .app { 151 | background-color: var(--theme-bg-color); 152 | max-width: 1300px; 153 | max-height: 950px; 154 | height: 150vh; 155 | display: flex; 156 | flex-direction: column; 157 | overflow: hidden; 158 | width: 100%; 159 | border-radius: 14px; 160 | backdrop-filter: blur(20px); 161 | -webkit-backdrop-filter: blur(20px); 162 | font-size: 15px; 163 | font-weight: 500; 164 | } 165 | 166 | .content-button { 167 | background-color: #3a6df0; 168 | border: none; 169 | /* padding: 8px 26px; */ 170 | color: #fff; 171 | height: 2.5rem; 172 | width: 2.5rem; 173 | border-radius: 50%; 174 | cursor: pointer; 175 | transition: 0.3s; 176 | white-space: nowrap; 177 | font-weight: bold; 178 | font-size: 1.5em; 179 | line-height: 0; 180 | position: absolute; 181 | right: 0.5em; 182 | top: 1rem; 183 | /* bottom: 0.8rem; */ 184 | } 185 | .wrapper { 186 | display: flex; 187 | flex-grow: 1; 188 | overflow: hidden; 189 | } 190 | 191 | ::-webkit-scrollbar { 192 | width: 6px; 193 | border-radius: 10px; 194 | } 195 | 196 | ::-webkit-scrollbar-thumb { 197 | background: var(--scrollbar-bg); 198 | border-radius: 10px; 199 | } 200 | 201 | .main__header { 202 | padding-top: 5px; 203 | color: #f5f5f5; 204 | text-align: center; 205 | } 206 | 207 | .main__chat_window { 208 | flex-grow: 1; 209 | overflow-y: auto; 210 | } 211 | 212 | .messages { 213 | color: white; 214 | list-style: none; 215 | } 216 | 217 | .main__message_container { 218 | position: relative; 219 | padding: 22px 12px; 220 | display: flex; 221 | } 222 | 223 | .main__message_container input { 224 | flex-grow: 1; 225 | background-color: transparent; 226 | border-radius: 99em; 227 | /* border: none; */ 228 | color: #f5f5f5; 229 | } 230 | -------------------------------------------------------------------------------- /public/styles/room.css: -------------------------------------------------------------------------------- 1 | @import url("https://fonts.googleapis.com/css2?family=Poppins:wght@200;300;400;500;600;700&display=swap"); 2 | * { 3 | outline: none; 4 | box-sizing: border-box; 5 | } 6 | 7 | :root { 8 | --theme-bg-color: rgba(16 18 27 / 40%); 9 | --border-color: rgba(113 119 144 / 25%); 10 | --theme-color: #f9fafb; 11 | --inactive-color: rgb(113 119 144 / 78%); 12 | --body-font: "Poppins", sans-serif; 13 | --hover-menu-bg: rgba(12 15 25 / 30%); 14 | --content-title-color: #999ba5; 15 | --content-bg: rgb(146 151 179 / 13%); 16 | --button-inactive: rgb(249 250 251 / 55%); 17 | --dropdown-bg: #21242d; 18 | --dropdown-hover: rgb(42 46 60); 19 | --popup-bg: rgb(22 25 37); 20 | --search-bg: #14162b; 21 | --overlay-bg: rgba(36, 39, 59, 0.3); 22 | --scrollbar-bg: rgb(1 2 3 / 40%); 23 | } 24 | 25 | .light-mode { 26 | --theme-bg-color: rgb(255 255 255 / 31%); 27 | --theme-color: #3c3a3a; 28 | --inactive-color: #333333; 29 | --button-inactive: #3c3a3a; 30 | --search-bg: rgb(255 255 255 / 31%); 31 | --dropdown-bg: #f7f7f7; 32 | --overlay-bg: rgb(255 255 255 / 30%); 33 | --dropdown-hover: rgb(236 236 236); 34 | --border-color: rgb(255 255 255 / 35%); 35 | --popup-bg: rgb(255 255 255); 36 | --hover-menu-bg: rgba(255 255 255 / 35%); 37 | --scrollbar-bg: rgb(255 253 253 / 57%); 38 | --content-title-color: --theme-color; 39 | } 40 | 41 | html { 42 | box-sizing: border-box; 43 | -webkit-font-smoothing: antialiased; 44 | max-width: 100%; 45 | overflow-x: hidden; 46 | } 47 | video { 48 | border: 0.09em solid #40b3ab; 49 | padding: 0.08em; 50 | margin: 0.2em; 51 | border-radius: 0.5rem; 52 | /**Or add your own style**/ 53 | } 54 | 55 | body { 56 | max-width: 100%; 57 | overflow-x: hidden; 58 | 59 | font-family: var(--body-font); 60 | background-image: url("/assets/images/logo/macos-bg.jpg"); 61 | background-size: cover; 62 | background-position: center; 63 | display: flex; 64 | justify-content: center; 65 | align-items: center; 66 | flex-direction: column; 67 | padding: 2em; 68 | width: 100%; 69 | height: 100vh; 70 | } 71 | @media screen and (max-width: 480px) { 72 | body { 73 | padding: 0.8em; 74 | } 75 | } 76 | 77 | img { 78 | max-width: 100%; 79 | } 80 | 81 | .dark-light { 82 | position: fixed; 83 | top: 50px; 84 | right: 30px; 85 | background-color: var(--dropdown-bg); 86 | box-shadow: -1px 3px 8px -1px rgba(0, 0, 0, 0.2); 87 | padding: 8px; 88 | border-radius: 50%; 89 | z-index: 3; 90 | cursor: pointer; 91 | } 92 | .dark-light svg { 93 | width: 24px; 94 | flex-shrink: 0; 95 | fill: #ffce45; 96 | stroke: #ffce45; 97 | transition: 0.5s; 98 | } 99 | 100 | .light-mode .dark-light svg { 101 | fill: transparent; 102 | stroke: var(--theme-color); 103 | } 104 | .light-mode .profile-img { 105 | border: 2px solid var(--theme-bg-color); 106 | } 107 | .light-mode .content-section ul { 108 | background-color: var(--theme-bg-color); 109 | } 110 | .light-mode .pop-up__title { 111 | border-color: var(--theme-color); 112 | } 113 | .light-mode .dropdown.is-active ul { 114 | background-color: rgba(255, 255, 255, 0.94); 115 | } 116 | .light-mode .messages li { 117 | color: rgba(0, 0, 0, 0.94); 118 | } 119 | .light-mode h4 { 120 | color: rgba(0, 0, 0, 0.94); 121 | } 122 | .light-mode .fas { 123 | color: rgba(0, 0, 0, 0.94); 124 | } 125 | .light-mode span { 126 | color: rgba(0, 0, 0, 0.94); 127 | } 128 | .light-mode .main__controls__button:hover { 129 | background-color: #cccccc; 130 | border-radius: 5px; 131 | } 132 | body.light-mode:before { 133 | content: ""; 134 | position: absolute; 135 | left: 0; 136 | top: 0; 137 | width: 100%; 138 | height: 100vh; 139 | background: linear-gradient( 140 | 180deg, 141 | rgba(255, 255, 255, 0.72) 0%, 142 | rgba(255, 255, 255, 0.45) 100% 143 | ); 144 | -webkit-backdrop-filter: saturate(3); 145 | backdrop-filter: saturate(3); 146 | } 147 | 148 | .app { 149 | background-color: var(--theme-bg-color); 150 | max-width: 1300px; 151 | max-height: 950px; 152 | height: 150vh; 153 | display: flex; 154 | flex-direction: column; 155 | overflow: hidden; 156 | width: 100%; 157 | border-radius: 14px; 158 | backdrop-filter: blur(20px); 159 | -webkit-backdrop-filter: blur(20px); 160 | font-size: 15px; 161 | font-weight: 500; 162 | } 163 | 164 | .wrapper { 165 | display: flex; 166 | flex-grow: 1; 167 | overflow: hidden; 168 | } 169 | 170 | ::-webkit-scrollbar { 171 | width: 6px; 172 | border-radius: 10px; 173 | } 174 | 175 | ::-webkit-scrollbar-thumb { 176 | background: var(--scrollbar-bg); 177 | border-radius: 10px; 178 | } 179 | 180 | .main { 181 | display: flex; 182 | background-color: var(--theme-bg-color); 183 | width: 100%; 184 | } 185 | 186 | .main__left { 187 | display: flex; 188 | flex-grow: 1; 189 | overflow: hidden; 190 | } 191 | 192 | .main__right { 193 | flex: 0.3; 194 | background-color: var(--theme-bg-color); 195 | border-left: 1px solid #3d3d42; 196 | } 197 | 198 | .main__videos { 199 | flex-grow: 1; 200 | background-color: var(--theme-bg-color); 201 | display: flex; 202 | justify-content: center; 203 | align-items: center; 204 | padding: 40px; 205 | } 206 | 207 | .main__controls { 208 | background-color: var(--theme-bg-color); 209 | color: #d2d2d2; 210 | display: flex; 211 | justify-content: space-between; 212 | padding: 5px; 213 | position: absolute; 214 | bottom: 3px; 215 | border-radius: 10em; 216 | margin-left: 8em; 217 | margin-bottom: 10px; 218 | } 219 | 220 | .main__controls__block { 221 | margin-right: 2em; 222 | display: flex; 223 | } 224 | 225 | .main__controls__button { 226 | display: flex; 227 | flex-direction: column; 228 | justify-content: center; 229 | align-items: center; 230 | padding: 8px 10px; 231 | min-width: 80px; 232 | cursor: pointer; 233 | } 234 | 235 | .main__controls__button:hover { 236 | background-color: #343434; 237 | border-radius: 5px; 238 | } 239 | 240 | .main__controls__button i { 241 | font-size: 24px; 242 | } 243 | 244 | .main__right { 245 | display: flex; 246 | flex-direction: column; 247 | } 248 | 249 | .main__header { 250 | padding-top: 5px; 251 | color: #f5f5f5; 252 | text-align: center; 253 | } 254 | 255 | .main__chat_window { 256 | flex-grow: 1; 257 | overflow-y: auto; 258 | } 259 | 260 | .messages { 261 | color: white; 262 | list-style: none; 263 | padding: 0px 16px; 264 | } 265 | 266 | .main__message_container { 267 | padding: 22px 12px; 268 | display: flex; 269 | } 270 | 271 | .main__message_container input { 272 | flex-grow: 1; 273 | background-color: transparent; 274 | border: none; 275 | color: #f5f5f5; 276 | } 277 | 278 | .leave_meeting { 279 | color: #eb534b; 280 | } 281 | 282 | .unmute, 283 | .stop { 284 | color: #cc3b33; 285 | } 286 | 287 | #video-grid { 288 | overflow-y: auto; 289 | height: 100%; 290 | width: 100%; 291 | } 292 | 293 | #video-grid { 294 | display: flex; 295 | justify-content: center; 296 | flex-wrap: wrap; 297 | } 298 | video { 299 | background-color: var(--theme-bg-color); 300 | height: 250px; 301 | width: 450px; 302 | object-fit: cover; 303 | } 304 | video::-webkit-media-controls-fullscreen-button { 305 | display: none; 306 | } 307 | video::-webkit-media-controls-play-button { 308 | display: none; 309 | } 310 | video::-webkit-media-controls-timeline { 311 | display: none; 312 | } 313 | video::-webkit-media-controls-current-time-display { 314 | display: none; 315 | } 316 | video::-webkit-media-controls-time-remaining-display { 317 | display: none; 318 | } 319 | video::-webkit-media-controls-mute-button { 320 | display: none; 321 | } 322 | video::-webkit-media-controls-toggle-closed-captions-button { 323 | display: none; 324 | } 325 | video::-webkit-media-controls-volume-slider { 326 | display: none; 327 | } 328 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | require("dotenv").config(); 2 | const express = require("express"); 3 | const mongoose = require("mongoose"); 4 | const path = require("path"); 5 | const app = express(); 6 | const server = require("http").Server(app); 7 | const io = require("socket.io")(server); 8 | 9 | const { 10 | createRoom, 11 | getRoomByUser, 12 | getRoom, 13 | chatRoomMW, 14 | roomMW, 15 | } = require("./controllers/Room/index"); 16 | const { sendInvite, mailFeedback } = require("./controllers/Misc"); 17 | const { createUser, addRooms, getUser } = require("./controllers/User"); 18 | const { createMessage, getMessages } = require("./controllers/Message"); 19 | 20 | app.use(express.json()); 21 | 22 | app.set("view engine", "ejs"); 23 | app.use(express.static(path.join(__dirname, "public"))); 24 | 25 | // <---------- VIEWS ----------> 26 | app.get("/", (req, res) => { 27 | res.redirect(`/home`); 28 | }); 29 | app.get("/timeline", (req, res) => { 30 | res.render("timeline"); 31 | }); 32 | 33 | app.get("/landing", (req, res) => { 34 | res.render("landing"); 35 | }); 36 | 37 | app.get("/home", (req, res) => { 38 | res.render("home"); 39 | }); 40 | 41 | app.get("/aibot", (req, res) => { 42 | res.render("aibot"); 43 | }); 44 | // <---------- ROUTES ----------> 45 | app.post("/invite", sendInvite); 46 | 47 | app.post("/feedback", mailFeedback); 48 | 49 | app.post("/room", createRoom); 50 | 51 | app.post("/room/get", getRoom); 52 | 53 | app.post("/room/user", getRoomByUser); 54 | 55 | app.post("/room/add", addRooms); 56 | 57 | app.post("/message/get", getMessages); 58 | 59 | app.post("/createuser", createUser); 60 | 61 | app.post("/user/get", getUser); 62 | 63 | // <---------- MW-VIEWS ----------> 64 | app.get("/chat/:room", chatRoomMW); 65 | 66 | app.get("/:room", roomMW); 67 | 68 | // <---------- SOCKET ----------> 69 | io.on("connection", (socket) => { 70 | socket.on("join-room", (roomId, userId) => { 71 | console.log(roomId, userId); 72 | socket.join(roomId); 73 | socket.to(roomId).broadcast.emit("user-connected", userId); 74 | // messages 75 | socket.on("message", async ({ message, user }) => { 76 | //send message to the same room 77 | console.log(user); 78 | let messageResp = await createMessage(user, message, roomId); 79 | io.to(roomId).emit("createMessage", { message: messageResp, user }); 80 | }); 81 | 82 | socket.on("disconnect", () => { 83 | socket.to(roomId).broadcast.emit("user-disconnected", userId); 84 | }); 85 | }); 86 | }); 87 | 88 | // <--------- MONGOOSE + SERVER START ---------> 89 | mongoose 90 | .connect(process.env.MONGODB_URI, { 91 | useNewUrlParser: true, 92 | useUnifiedTopology: true, 93 | useFindAndModify: false, 94 | useCreateIndex: true, 95 | poolSize: 10, //increase poolSize from default 5 96 | }) 97 | .then(() => { 98 | console.log("Connected MONGODB"); 99 | 100 | server.listen(process.env.PORT || 3030, () => { 101 | console.log( 102 | `listening on port: ${process.env.PORT || 3030}, http://localhost:${ 103 | process.env.PORT || 3030 104 | }` 105 | ); 106 | }); 107 | }) 108 | .catch((err) => { 109 | console.error(err); 110 | }); 111 | -------------------------------------------------------------------------------- /views/aibot.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Moniter Me 7 | 8 | 9 | 14 | 20 | 21 | 26 | 31 | 32 | 33 | 34 | 216 | 217 | 218 | 219 | 220 | 221 |
    222 | 230 | 231 | 232 |
    233 |
    234 | 238 |
    239 |
    240 |
    250 | 261 |
    262 | 269 | 270 |
    271 |
    272 |
    273 | 274 | 282 | 283 | -------------------------------------------------------------------------------- /views/chatroom.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Chat 7 | 11 | 12 | 17 | 23 | 24 | 29 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 |
    49 | 57 | 58 | 59 |
    60 |
    61 | 65 |
    66 |
    67 |
    68 |

    69 | Chat Room 70 | 89 |

    90 |
    91 |
    92 |
      93 |
      94 |
      95 | 101 | 102 |
      103 |
      104 | 105 | 106 | -------------------------------------------------------------------------------- /views/home.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Home page 8 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 |
      25 | 33 | 34 | 35 |
      36 |
      37 | 38 |
      39 | 40 |
      41 | Home 42 |
      43 |
      44 | 56 | 62 |
      63 |
      64 |
      65 | 66 |
      67 |
      68 |
      69 | 70 | Logo 75 | 76 |
      77 |
      User Details
      78 |
      79 | Loading... 80 | Loading... 81 |
      82 |
      83 |
      84 |
      My Rooms
      85 |
      Loading...
      86 |
      Recent rooms
      87 |
      Loading...
      88 |
      89 |
      90 |
      91 |
      92 | Your Dashboard 93 |
      94 |
      95 |
      96 |
      97 |

      Unite App

      98 |
      99 | Web application, to create chat rooms and join video meetings. 100 | Meet with as much people you wish to! Schedule meeting Google 101 | Calendars. And keep a check on your health with posture bot. 102 |
      103 | 109 | 110 | 111 | 112 | 113 | 114 |
      115 |
      116 | Schedule Meeting 117 | 128 | 129 | 130 | 131 |
      132 |
      133 | Google Calendar Meeting Scheduler 134 |
      135 | 136 |
      137 | 138 |
      139 | 140 |
      141 |
      142 |
      143 | 150 |
      151 |
      152 |
      153 | 161 |
      162 |

      163 |
      164 | 165 | 166 |
      167 |
      168 | Create Room 169 | 180 | 181 | 182 | 183 |
      184 |
      185 | Enter the chat and Invite people 186 |
      187 | 188 |
      193 | 194 |
      195 |
      196 | 205 | 213 |

      222 | 239 |

      240 |
      241 |
      242 | 243 |
      244 |
      245 | Join Room 246 | 257 | 258 | 259 | 260 |
      261 | 262 | 263 |
      Enter the chat or Room ID
      264 | 265 |
      266 |
      267 | 275 |
      276 |
      277 |
      278 | 279 | 284 |
      285 |
      286 |
      Apps in your plan
      287 |
      288 |
      289 | 290 | 295 | Create Meeting 296 | 297 |
      Create your own room
      298 |
      299 | 302 |
      303 |
      304 |
      305 | 306 | 311 | 312 | Join Meeting 313 | 314 |
      Join meeting by meet ID
      315 |
      316 | 317 |
      318 |
      319 |
      320 | 321 | 326 | 327 | Schedule Meeting 328 | 329 |
      330 | Schedule with Google Calendar 331 |
      332 | 333 |
      334 | 337 |
      338 |
      339 |
      340 | 341 | 346 | AI Posture Bot 347 | 348 |
      349 | AI Based Posture Detection 350 |
      351 | 352 |
      353 | 360 |
      361 |
      362 |
      363 |
      364 |
      365 |
      366 |
      367 |
      368 | 369 | 370 | 371 | -------------------------------------------------------------------------------- /views/landing.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | UNITE | Video conferencing App 7 | 8 | 9 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 31 | 32 | 33 |
      34 |
      35 |
      36 |
      37 |
      38 |
      39 |
      40 |
      41 |
      42 |
      43 |
      44 |
      45 |
      46 |
      47 |
      48 |
      49 | 50 | 51 | 52 |
      53 | 113 | 114 |
      115 | 116 | 117 | 118 |
      119 |
      120 |
      121 |
      122 |
      123 |

      Video conferencing solutions with Unite

      124 |

      using peer-to-peer connections.

      125 | 126 | Demo 127 |
      128 |
      129 |
      130 |
      131 | 132 |
      133 |
      134 |
      135 |
      136 |
      137 | 138 | 139 | 140 |
      141 |
      142 |
      143 |
      144 |
      145 |

      146 | Modern design
      147 | with Essential Features 148 |

      149 |
      150 |
      151 |
      152 | 153 |
      154 |
      155 |
      156 |
      157 | 158 |
      159 |
      160 |

      One-on-One meetings

      161 |

      162 | Connect with your loved ones with Unite. Peer-to-peer 163 | connection between two users. 164 |

      165 |
      166 |
      167 |
      168 |
      169 |
      170 |
      171 | 172 |
      173 |
      174 |

      Active Customer Feedback

      175 |

      176 | Get immediate feedback from customers with 177 | Feeback forms, supporting the concept of 178 | Agile. 179 |

      180 |
      181 |
      182 |
      183 |
      184 |
      185 |
      186 | 187 |
      188 |
      189 |

      Multiple Room Connections with Screen sharing options

      190 |

      191 | Join and connect with as many friends that you wish to 192 | connect! 193 |

      194 |
      195 |
      196 |
      197 |
      198 |
      199 |
      200 | 201 |
      202 |
      203 |

      Google Authentication

      204 |

      205 | Sign in with Google and connect with your loved ones, just by 206 | one click! 207 |

      208 |
      209 |
      210 |
      211 |
      212 |
      213 |
      214 | 215 |
      216 |
      217 |

      Persistent Text chat

      218 |

      219 | Join chat rooms and continue conversation before, after and 220 | during the meet! 221 |

      222 |
      223 |
      224 |
      225 | 226 |
      227 |
      228 |
      229 | 230 |
      231 |
      232 |

      Schedule Meetings

      233 |

      234 | Schedule your meetings with Google Calendar API, and invite 235 | people to join! 236 |

      237 |
      238 |
      239 |
      240 |
      244 |
      245 |
      246 | 247 |
      248 |
      249 |

      AI Bot

      250 |

      Keep your health in check with posture bot!

      251 |
      252 |
      253 |
      254 |
      255 |
      256 |
      257 | 258 | 259 | 260 |
      261 |
      262 | 263 |
      264 |
      265 |
      266 |
      267 |
      268 | 269 |
      270 |
      271 |
      272 |
      273 |
      274 |

      Witness my journey through this timeline!

      275 |

      276 | Different challenges that I faced all through these 4 weeks, 277 | how I overcame them and what I learnt from those mistakes. 278 |

      279 | My timeline 282 |
      283 |
      284 |
      285 |
      286 |
      287 |
      288 | 289 | 290 | 291 |
      292 |
      293 |
      294 |
      295 |
      296 |

      Main features

      297 |
      298 |
      299 |
      300 | 301 |
      302 |
      303 |
      304 |
      305 | 306 |
      307 |
      308 |

      Video calling

      309 |
      310 |
      311 |
      312 |
      313 |
      314 |
      315 | 316 |
      317 |
      318 |

      Persistent Text Chat

      319 |
      320 |
      321 |
      322 |
      323 |
      324 |
      325 | 326 |
      327 |
      328 |

      Schedule Meet

      329 |
      330 |
      331 |
      332 |
      333 |
      334 |
      335 | 336 |
      337 |
      338 |

      AI Bot

      339 |
      340 |
      341 |
      342 |
      343 |
      344 |
      345 | 346 | 347 | 348 |
      349 |
      350 |
      351 |
      352 |
      353 |

      Contact Us

      354 |

      apurva.sharma866@gmail.com || +91 8319943063 || CG, India

      355 |
      356 | 376 |
      377 |
      378 |
      379 |
      380 | 388 |
      389 |
      390 | 406 |
      407 |
      408 |
      409 |
      410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | -------------------------------------------------------------------------------- /views/room.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Meet 7 | 11 | 16 | 17 | 18 | 24 | 25 | 30 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 |
      50 | 58 | 59 | 60 |
      61 |
      62 |
      63 |
      64 |
      65 |
      66 |
      67 |
      68 | 69 |
      70 |
      71 |
      75 | 76 | Mute 77 |
      78 |
      82 | 83 | Stop Video 84 |
      85 |
      86 | 87 | ScreenShare 88 |
      89 |
      90 |
      91 |
      95 | Leave Meeting 98 |
      99 |
      100 |
      101 |
      105 | Go Back to ChatRoom 110 |
      111 |
      112 |
      113 |
      114 | 115 |
      116 |
      117 |

      Chat

      118 |
      119 |
      120 |
        121 |
        122 |
        123 | 128 |
        129 |
        130 |
        131 |
        132 |
        133 | 134 | 135 | 136 | -------------------------------------------------------------------------------- /views/timeline.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | UNITE | Video conferencing App 7 | 8 | 13 | 14 | 19 | 20 | 21 | 26 | 27 | 28 | 29 | 30 |
          31 | 32 |
        • 33 |
          34 |
          35 | Design phase 36 | Week 1 37 |
          38 |
          39 |
            40 |
          • 41 | Researched about WebRTC, and PeerJS and why is NodeJS the best 42 | suit for me! 43 |
          • 44 |
          • 45 | My first prototype was a Simple application 46 | with connection between two people, and had text chat feature. 47 |
          • 48 |
          • 49 | Learnt how to use peerJS and creating a unique ID to connect two 50 | people. 51 |
          • 52 |
          • 53 | In the 1st mentor session, I learnt about 54 | various design requirements and how to make the code modular. 55 | And also, how to integrate features in an application in an 56 | industry environment. 57 |
          • 58 |
          59 |
          60 |
          61 |
        • 62 | 63 |
        • 64 |
          65 |
          66 | Build phase 67 | Week 2 68 |
          69 |
          70 |
            71 |
          • 72 | The build phase started with 73 | Requirement Specification. 74 |
          • 75 |
          • 76 | These were the Functional requirements for the 77 | application: 78 |
              79 |
            • Video chat b/w multiple users
            • 80 |
            • Text chat during meet
            • 81 |
            • Google based auth using firebase
            • 82 |
            • Google Calendar API to schedule meet
            • 83 |
            • User friendly interface
            • 84 |
            • Create an interface for user feedbacks
            • 85 |
            86 |
          • 87 |
          • 88 | These were the Non-Functional requirements for 89 | the application: 90 |
              91 |
            • 92 | The application be able to accomodate atleast 1000 requests 93 | per minute 94 |
            • 95 |
            • The response time of the app should not exceed 30ms
            • 96 |
            • The firebase auth should be secure
            • 97 |
            • 98 | The application should scheldule meet and send response 99 | within 20ms 100 |
            • 101 |
            102 |
          • 103 |
          • 104 | The second mentor session was about Architecure 105 | and interface design! 106 |
          • 107 |
          108 |
          109 |
          110 |
        • 111 | 112 |
        • 113 |
          114 |
          115 | Adopt phase 116 | Week 3 117 |
          118 |
          119 |
            120 |
          • The biggest challange
          • 121 |
          • 122 | After carefull code evaluation, here's how I solved this: 123 |
              124 |
            • Build the video call on top of chat room
            • 125 |
            • 126 | Use uuid to generate unique for each room ID, and use same 127 | for chat and video call feature. 128 |
            • 129 |
            • 130 | Persist the chat using MongoDB, as well as keep track of 131 | user's recent room ID 132 |
            • 133 |
            134 |
          • 135 |
          • I was able to implement this feature effectively!
          • 136 |
          137 |
          138 |
          139 |
        • 140 | 141 | 142 |
        • 143 |
          144 |
          145 | Deploy and Testing 146 | Week 4 147 |
          148 |
          149 |
            150 |
          • 151 | Use Heroku for deployment and try implementing 152 | it on AWS EC2 153 |
          • 154 |
          • 155 | Evaluate performance testing and document the 156 | results. 157 |
          • 158 |
          159 |
          160 |
          161 |
        • 162 |
        163 | 164 | 165 | 166 | 167 | --------------------------------------------------------------------------------