├── .gitignore
├── LICENSE
├── README.md
├── package-lock.json
├── package.json
├── public
├── _redirects
├── favicons
│ ├── android-chrome-192x192.png
│ ├── android-chrome-512x512.png
│ ├── apple-touch-icon.png
│ ├── browserconfig.xml
│ ├── favicon-16x16.png
│ ├── favicon-32x32.png
│ ├── favicon.ico
│ ├── mstile-150x150.png
│ ├── safari-pinned-tab.svg
│ └── site.webmanifest
├── index.html
└── robots.txt
└── src
├── App.css
├── App.js
├── Audio
├── music.mp3
└── outgoing.mp3
├── Components
├── CallUI
│ ├── CallUI.css
│ └── CallUI.js
├── Chats
│ ├── Chats.css
│ └── Chats.js
├── Home
│ ├── Home.css
│ └── Home.js
├── PageNotFound404
│ ├── PageNotFound404.css
│ └── PageNotFound404.js
├── Room
│ ├── Room.css
│ └── Room.js
└── Video
│ ├── Video.css
│ └── Video.js
├── Images
└── logo.png
├── Utils
└── truncate.js
├── index.css
├── index.js
├── normalize.css
├── reportWebVitals.js
├── service-worker.js
├── serviceWorkerRegistration.js
└── setupTests.js
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | .env
22 | debug.log
23 | .eslintcache
24 |
25 | npm-debug.log*
26 | yarn-debug.log*
27 | yarn-error.log*
28 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Piyush Sati
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
15 |
16 |
17 |
18 | # Call Buddy - Frontend
19 |
20 | Video Chat webapp built using React.js, Node.js, Socket.io and Peer.js
21 |
22 |
23 |
24 | - [Backend Code](https://github.com/Pinqua/Call-Buddy-Backend)
25 |
26 |
27 |
28 |
29 | ## Demo
30 |
31 | https://call-buddy.vercel.app
32 |
33 |
34 | ## Screenshots
35 |
36 | 
37 |
38 |
39 | ## Features
40 |
41 | - Progressive Web App (PWA)
42 | - No sign up and sign in needed
43 | - Fully Responsive (android + desktop)
44 | - Instant messaging
45 | - All basic functionalities included like mute and unmute mic, video on and off, end call
46 |
47 |
48 |
49 |
50 | ## Run Locally
51 |
52 | Clone the project
53 |
54 | ```bash
55 | git clone https://github.com/Pinqua/Call-Buddy-Frontend.git
56 | ```
57 |
58 | Go to the project directory
59 |
60 | ```bash
61 | cd Call-Buddy-Frontend
62 | ```
63 |
64 | Install dependencies
65 |
66 | ```bash
67 | npm install
68 | ```
69 |
70 | Create a **.env** file inside project directory with fields given below.
71 |
72 | ```
73 | REACT_APP_SERVER_URL= add server url (wherever your backend code is hosted add its url over here)
74 | REACT_APP_PEERJS_HOST= add peerjs hosted server url
75 |
76 | GENERATE_SOURCEMAP=false
77 | ```
78 |
79 | Start the server
80 |
81 | ```bash
82 | npm run start
83 | ```
84 |
85 |
86 | ## Contributing
87 |
88 | Contributions are always welcome!
89 |
90 | ## License
91 |
92 | [MIT](https://choosealicense.com/licenses/mit/)
93 |
94 |
95 |
96 |
97 |
98 | If you liked the repository, show your ❤️ by starring and forking it.
99 |
100 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "client",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@material-ui/core": "^4.11.2",
7 | "@material-ui/icons": "^4.11.2",
8 | "@testing-library/jest-dom": "^5.11.9",
9 | "@testing-library/react": "^11.2.3",
10 | "@testing-library/user-event": "^12.6.1",
11 | "moment": "^2.29.1",
12 | "peerjs": "^1.3.1",
13 | "react": "^17.0.1",
14 | "react-dom": "^17.0.1",
15 | "react-loader-spinner": "^3.1.14",
16 | "react-router-dom": "^5.2.0",
17 | "react-scripts": "4.0.1",
18 | "react-share": "^4.3.1",
19 | "socket.io-client": "^3.1.0",
20 | "web-vitals": "^0.2.4",
21 | "workbox-background-sync": "^5.1.4",
22 | "workbox-broadcast-update": "^5.1.4",
23 | "workbox-cacheable-response": "^5.1.4",
24 | "workbox-core": "^5.1.4",
25 | "workbox-expiration": "^5.1.4",
26 | "workbox-google-analytics": "^5.1.4",
27 | "workbox-navigation-preload": "^5.1.4",
28 | "workbox-precaching": "^5.1.4",
29 | "workbox-range-requests": "^5.1.4",
30 | "workbox-routing": "^5.1.4",
31 | "workbox-strategies": "^5.1.4",
32 | "workbox-streams": "^5.1.4"
33 | },
34 | "scripts": {
35 | "start": "react-scripts start",
36 | "build": "react-scripts build",
37 | "test": "react-scripts test",
38 | "eject": "react-scripts eject"
39 | },
40 | "eslintConfig": {
41 | "extends": [
42 | "react-app",
43 | "react-app/jest"
44 | ]
45 | },
46 | "browserslist": {
47 | "production": [
48 | ">0.2%",
49 | "not dead",
50 | "not op_mini all"
51 | ],
52 | "development": [
53 | "last 1 chrome version",
54 | "last 1 firefox version",
55 | "last 1 safari version"
56 | ]
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/public/_redirects:
--------------------------------------------------------------------------------
1 | /* /index.html 200
--------------------------------------------------------------------------------
/public/favicons/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Pinqua/Call-Buddy-Frontend/f8d2500ccede37adccfb6052e7cc85c8b36ec1fc/public/favicons/android-chrome-192x192.png
--------------------------------------------------------------------------------
/public/favicons/android-chrome-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Pinqua/Call-Buddy-Frontend/f8d2500ccede37adccfb6052e7cc85c8b36ec1fc/public/favicons/android-chrome-512x512.png
--------------------------------------------------------------------------------
/public/favicons/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Pinqua/Call-Buddy-Frontend/f8d2500ccede37adccfb6052e7cc85c8b36ec1fc/public/favicons/apple-touch-icon.png
--------------------------------------------------------------------------------
/public/favicons/browserconfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | #ffffff
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/public/favicons/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Pinqua/Call-Buddy-Frontend/f8d2500ccede37adccfb6052e7cc85c8b36ec1fc/public/favicons/favicon-16x16.png
--------------------------------------------------------------------------------
/public/favicons/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Pinqua/Call-Buddy-Frontend/f8d2500ccede37adccfb6052e7cc85c8b36ec1fc/public/favicons/favicon-32x32.png
--------------------------------------------------------------------------------
/public/favicons/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Pinqua/Call-Buddy-Frontend/f8d2500ccede37adccfb6052e7cc85c8b36ec1fc/public/favicons/favicon.ico
--------------------------------------------------------------------------------
/public/favicons/mstile-150x150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Pinqua/Call-Buddy-Frontend/f8d2500ccede37adccfb6052e7cc85c8b36ec1fc/public/favicons/mstile-150x150.png
--------------------------------------------------------------------------------
/public/favicons/safari-pinned-tab.svg:
--------------------------------------------------------------------------------
1 |
2 |
4 |
7 |
8 | Created by potrace 1.11, written by Peter Selinger 2001-2013
9 |
10 |
12 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/public/favicons/site.webmanifest:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Call Buddy",
3 | "short_name": "Call Buddy",
4 | "description": "Video Chat Progressive Web App build with ❤️ by Piyush Sati",
5 | "icons": [
6 | {
7 | "src": "/favicons/android-chrome-192x192.png",
8 | "sizes": "192x192",
9 | "type": "image/png"
10 | },
11 | {
12 | "src": "/favicons/android-chrome-512x512.png",
13 | "sizes": "512x512",
14 | "type": "image/png"
15 | }
16 | ],
17 | "start_url": "/",
18 | "theme_color": "#ffffff",
19 | "background_color": "#ffffff",
20 | "display": "standalone"
21 | }
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
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 | Call Buddy
30 |
31 |
32 |
33 |
34 |
35 |
39 |
40 |
41 |
42 | You need to enable JavaScript to run this app.
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/src/App.css:
--------------------------------------------------------------------------------
1 | .app {
2 | background-color: #111;
3 | width: 100%;
4 | height: 100%;
5 | color: white;
6 | }
7 |
--------------------------------------------------------------------------------
/src/App.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect, useRef, useCallback } from "react";
2 | import "./App.css";
3 | import Home from "./Components/Home/Home";
4 | import { BrowserRouter as Router, Switch, Route } from "react-router-dom";
5 | import Room from "./Components/Room/Room";
6 | import PageNotFound404 from "./Components/PageNotFound404/PageNotFound404";
7 | import io from "socket.io-client";
8 | import Peer from "peerjs";
9 |
10 | function App() {
11 | const [outgoingCall, setOutgoingCall] = useState(false);
12 | const [incomingCall, setIncomingCall] = useState(false);
13 | const socket = useRef();
14 | const [, setCam] = useState("user");
15 | const [notificationMsg, setNotificationMsg] = useState("");
16 | const [userInfo, setUserInfo] = useState({
17 | id: "",
18 | displayName: "",
19 | stream: null,
20 | videoCamOff: false,
21 | micOff: false,
22 | });
23 | const [myInfo, setMyInfo] = useState({
24 | id: "",
25 | displayName: "",
26 | stream: null,
27 | videoCamOff: false,
28 | micOff: false,
29 | });
30 | const myPeer = useRef(
31 | new Peer(
32 | /*undefined, { host: "localhost", port: 5000, path: "/myapp" }*/
33 | undefined,
34 | {
35 | host: `${process.env.REACT_APP_PEERJS_HOST}`,
36 | port: 443,
37 | path: "/",
38 | secure: true,
39 | }
40 | )
41 | );
42 |
43 | const answerCall = useCallback(() => {
44 | socket.current.emit("call-attended", myInfo, userInfo.id);
45 | setIncomingCall(false);
46 | }, [myInfo, userInfo.id]);
47 |
48 | const callUser = useCallback(
49 | (userID) => {
50 | if (myInfo.id === userID) {
51 | setNotificationMsg("Enter someone else ID");
52 | return;
53 | }
54 | socket.current.emit("call-user", { ...myInfo, stream: null }, userID);
55 | setOutgoingCall(true);
56 | setUserInfo((userInfo) => ({ ...userInfo, id: userID }));
57 | },
58 | [myInfo]
59 | );
60 |
61 | const displayNameHandler = useCallback((displayName) => {
62 | setMyInfo((myInfo) => ({ ...myInfo, displayName: displayName }));
63 | }, []);
64 |
65 | const endCall = useCallback(() => {
66 | socket.current.emit("end-call", userInfo.id);
67 | setUserInfo({
68 | id: "",
69 | displayName: "",
70 | stream: null,
71 | videoCamOff: false,
72 | micOff: false,
73 | });
74 | }, [userInfo.id]);
75 |
76 | const rejectCall = useCallback(() => {
77 | socket.current.emit("call-rejected", userInfo.id);
78 | setIncomingCall(false);
79 | setOutgoingCall(false);
80 | setUserInfo({
81 | id: "",
82 | displayName: "",
83 | stream: null,
84 | videoCamOff: false,
85 | micOff: false,
86 | });
87 | }, [userInfo.id]);
88 |
89 | const videoCamOffHandler = useCallback(() => {
90 | if (userInfo.stream) {
91 | socket.current.emit(
92 | "user-info",
93 | { ...myInfo, videoCamOff: !myInfo.videoCamOff },
94 | userInfo.id
95 | );
96 | }
97 | setMyInfo((myInfo) => ({ ...myInfo, videoCamOff: !myInfo.videoCamOff }));
98 | }, [myInfo, userInfo.id, userInfo.stream]);
99 |
100 | const micOffHandler = useCallback(() => {
101 | if (userInfo.stream) {
102 | socket.current.emit(
103 | "user-info",
104 | { ...myInfo, micOff: !myInfo.micOff },
105 | userInfo.id
106 | );
107 | }
108 | setMyInfo((myInfo) => ({ ...myInfo, micOff: !myInfo.micOff }));
109 | }, [userInfo.stream, userInfo.id, myInfo]);
110 |
111 | const switchCamera = useCallback(() => {
112 | const supported = navigator.mediaDevices.getSupportedConstraints();
113 | if (!supported["facingMode"]) {
114 | alert("Not supported by browser");
115 | return;
116 | }
117 | setCam((cam) => {
118 | if (cam === "user") {
119 | socket.current.emit("switch-cam", "environment", userInfo.id);
120 | return "environment";
121 | }
122 | socket.current.emit("switch-cam", "user", userInfo.id);
123 | return "user";
124 | });
125 | }, [userInfo.id]);
126 |
127 | //automatically hide notification message after some time.
128 | useEffect(() => {
129 | let timeOut = null;
130 | if (notificationMsg) {
131 | timeOut = setTimeout(() => {
132 | setNotificationMsg("");
133 | }, 4000);
134 | }
135 | return () => {
136 | clearTimeout(timeOut);
137 | };
138 | }, [notificationMsg]);
139 |
140 | //automatically cut the call after some time.
141 | useEffect(() => {
142 | let timeout = null;
143 | if (outgoingCall) {
144 | timeout = setTimeout(() => {
145 | rejectCall();
146 | setNotificationMsg(
147 | "User doesn't exist or user is not picking up your call"
148 | );
149 | }, 60000);
150 | }
151 | return () => {
152 | if (timeout) {
153 | clearTimeout(timeout);
154 | }
155 | };
156 | }, [outgoingCall, rejectCall]);
157 |
158 | useEffect(() => {
159 | socket.current = io(`${process.env.REACT_APP_SERVER_URL}/media`);
160 | //socket.current = io("http://localhost:9000/media");
161 |
162 | let Stream;
163 |
164 | function CALL(userID, stream) {
165 | const call = myPeer.current.call(userID, stream);
166 | call.on("stream", (userStream) => {
167 | setUserInfo((userInfo) => ({ ...userInfo, stream: userStream }));
168 | });
169 | call.on("close", () => {
170 | setUserInfo({
171 | id: "",
172 | displayName: "",
173 | stream: null,
174 | videoCamOff: false,
175 | micOff: false,
176 | });
177 | });
178 | }
179 |
180 | navigator.mediaDevices
181 | .getUserMedia({
182 | video: {
183 | facingMode: "user",
184 | width: { min: 160, ideal: 640, max: 1280 },
185 | height: { min: 120, ideal: 360, max: 720 },
186 | aspectRatio: { ideal: 1.7777777778 },
187 | },
188 | audio: {
189 | sampleRate: { ideal: 48000 },
190 | sampleSize: { ideal: 16 },
191 | channelCount: { ideal: 2, min: 1 },
192 | echoCancellation: true,
193 | noiseSuppression: true,
194 | autoGainControl: true,
195 | },
196 | })
197 | .then((stream) => {
198 | setMyInfo((myInfo) => ({ ...myInfo, stream: stream }));
199 | Stream = stream;
200 | myPeer.current.on("call", (call) => {
201 | call.answer(stream);
202 | call.on("stream", (userStream) => {
203 | setUserInfo((userInfo) => ({ ...userInfo, stream: userStream }));
204 | });
205 | });
206 | socket.current.on("call--attended", (userInfo) => {
207 | setUserInfo((usrInfo) => ({ ...userInfo, stream: usrInfo.stream }));
208 | CALL(userInfo.id, stream);
209 | setOutgoingCall(false);
210 | });
211 | })
212 | .catch((err) => {
213 | console.log(err);
214 | alert(err);
215 | });
216 |
217 | // on open will be launch when you successfully connect to PeerServer
218 | myPeer.current.on("open", (id) => {
219 | setMyInfo((myInfo) => ({ ...myInfo, id: id }));
220 | socket.current.emit("join-media", id);
221 | myPeer.current.on("error", (err) => {
222 | console.log(err);
223 | alert(err);
224 | setUserInfo((userInfo) => {
225 | socket.current.emit("end-call", userInfo.id);
226 | return {
227 | id: "",
228 | displayName: "",
229 | stream: null,
230 | videoCamOff: false,
231 | micOff: false,
232 | };
233 | });
234 | });
235 | });
236 |
237 | myPeer.current.on("disconnected", () => {
238 | setUserInfo((userInfo) => {
239 | socket.current.emit("end-call", userInfo.id);
240 | return {
241 | id: "",
242 | displayName: "",
243 | stream: null,
244 | videoCamOff: false,
245 | micOff: false,
246 | };
247 | });
248 | });
249 |
250 | myPeer.current.on("close", () => {
251 | setUserInfo((userInfo) => {
252 | socket.current.emit("end-call", userInfo.id);
253 | return {
254 | id: "",
255 | displayName: "",
256 | stream: null,
257 | videoCamOff: false,
258 | micOff: false,
259 | };
260 | });
261 | });
262 |
263 | socket.current.on("connect", () => {
264 | socket.current.on("incoming-call", (userInfo) => {
265 | setUserInfo((usrInfo) => {
266 | if (usrInfo.stream || usrInfo.id) {
267 | socket.current.emit("busy", userInfo.id);
268 | if (usrInfo.stream) {
269 | setIncomingCall(false);
270 | }
271 | return usrInfo;
272 | }
273 | setIncomingCall(true);
274 | return userInfo;
275 | });
276 | });
277 |
278 | socket.current.on("user-busy", () => {
279 | setOutgoingCall(false);
280 | setNotificationMsg("User is busy");
281 | setUserInfo({
282 | id: "",
283 | displayName: "",
284 | stream: null,
285 | videoCamOff: false,
286 | micOff: false,
287 | });
288 | });
289 | socket.current.on("user--info", (userInfo) => {
290 | setUserInfo((usrInfo) => ({ ...userInfo, stream: usrInfo.stream }));
291 | });
292 | socket.current.on("call--rejected", () => {
293 | setOutgoingCall(false);
294 | setIncomingCall(false);
295 | setUserInfo({
296 | id: "",
297 | displayName: "",
298 | stream: null,
299 | videoCamOff: false,
300 | micOff: false,
301 | });
302 | });
303 | socket.current.on("call-ended", () => {
304 | setUserInfo({
305 | id: "",
306 | displayName: "",
307 | stream: null,
308 | videoCamOff: false,
309 | micOff: false,
310 | });
311 | });
312 |
313 | socket.current.on("user-disconnected", (ID) => {
314 | setUserInfo((Info) => {
315 | if (ID === Info.id) {
316 | return { ...Info, stream: null };
317 | }
318 | return { ...Info };
319 | });
320 | });
321 |
322 | socket.current.on("switch-camera", (camera, userID) => {
323 | //stop the tracks
324 | const tracks = Stream.getTracks();
325 | tracks.forEach((tracks) => tracks.stop());
326 | navigator.mediaDevices
327 | .getUserMedia({
328 | video: {
329 | facingMode: camera,
330 | width: { min: 160, ideal: 640, max: 1280 },
331 | height: { min: 120, ideal: 360, max: 720 },
332 | aspectRatio: { ideal: 1.7777777778 },
333 | },
334 | audio: {
335 | sampleRate: { ideal: 48000 },
336 | sampleSize: { ideal: 16 },
337 | channelCount: { ideal: 2, min: 1 },
338 | echoCancellation: true,
339 | noiseSuppression: true,
340 | autoGainControl: true,
341 | },
342 | })
343 | .then((stream) => {
344 | Stream = stream;
345 | setMyInfo((myInfo) => ({ ...myInfo, stream: stream }));
346 | CALL(userID, stream);
347 | })
348 | .catch((err) => {
349 | console.log(err);
350 | alert(err);
351 | });
352 | });
353 | });
354 |
355 | const SOCKET = socket.current;
356 | return () => {
357 | SOCKET.removeAllListeners();
358 | };
359 | }, []);
360 |
361 | //to stop echo and mute my audio
362 | useEffect(() => {
363 | if (myInfo.stream && myInfo.stream.getAudioTracks().length > 0) {
364 | myInfo.stream.getAudioTracks().forEach((track) => {
365 | track.enabled = false;
366 | });
367 | }
368 | }, [myInfo.stream]);
369 |
370 | return (
371 |
372 |
373 |
374 |
375 | {!userInfo.stream ? (
376 |
389 | ) : (
390 |
398 | )}
399 |
400 |
401 |
402 |
403 |
404 |
405 |
406 | );
407 | }
408 |
409 | export default App;
410 |
--------------------------------------------------------------------------------
/src/Audio/music.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Pinqua/Call-Buddy-Frontend/f8d2500ccede37adccfb6052e7cc85c8b36ec1fc/src/Audio/music.mp3
--------------------------------------------------------------------------------
/src/Audio/outgoing.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Pinqua/Call-Buddy-Frontend/f8d2500ccede37adccfb6052e7cc85c8b36ec1fc/src/Audio/outgoing.mp3
--------------------------------------------------------------------------------
/src/Components/CallUI/CallUI.css:
--------------------------------------------------------------------------------
1 | @-webkit-keyframes callAnimation {
2 | from {
3 | transform: scale(1.05);
4 | }
5 | to {
6 | transform: scale(1);
7 | }
8 | }
9 |
10 | @keyframes callAnimation {
11 | from {
12 | transform: scale(1.05);
13 | }
14 | to {
15 | transform: scale(1);
16 | }
17 | }
18 |
19 | @-webkit-keyframes call-animation {
20 | from {
21 | right: -100%;
22 | }
23 | to {
24 | right: 4%;
25 | }
26 | }
27 |
28 | @keyframes call-animation {
29 | from {
30 | right: -100%;
31 | }
32 | to {
33 | right: 4%;
34 | }
35 | }
36 |
37 | .callUI {
38 | position: fixed;
39 | top: 40px;
40 | right: 4%;
41 | padding: 20px;
42 | z-index: 999;
43 | border-radius: 8px;
44 | background-color: #111111e8;
45 | color: white;
46 | font-size: 0.9rem;
47 | max-width: 380px;
48 | width: 380px;
49 | }
50 | .call__incomingCall {
51 | -webkit-animation: callAnimation 0.6s infinite, call-animation 0.5s;
52 | animation: callAnimation 0.6s infinite, call-animation 0.5s;
53 | }
54 | .call__outgoingCall {
55 | -webkit-animation: call-animation 0.5s;
56 | animation: call-animation 0.5s;
57 | }
58 |
59 | .call__options {
60 | display: flex;
61 | align-items: center;
62 | justify-content: center;
63 | }
64 |
65 | .call__option {
66 | background-color: #5277f0;
67 | width: 50px;
68 | height: 50px;
69 | margin: 0 10px;
70 | display: flex;
71 | justify-content: center;
72 | align-items: center;
73 | border-radius: 50%;
74 | cursor: pointer;
75 | }
76 | .callUI .call__end {
77 | background-color: #ff0000;
78 | }
79 | .callUI .call__answer {
80 | background-color: #10af10;
81 | }
82 | .call__user {
83 | display: flex;
84 | flex-direction: column;
85 | justify-content: center;
86 | margin-bottom: 20px;
87 | }
88 | .call__userID {
89 | margin: 5px 0;
90 | line-height: 1.2rem;
91 | word-break: break-all;
92 | word-wrap: break-word;
93 | }
94 | .call__info {
95 | padding: 10px 0;
96 | text-align: center;
97 | }
98 |
99 | .callUI .call__option > .MuiSvgIcon-root {
100 | color: white;
101 | }
102 |
103 | @media screen and (max-width: 400px) {
104 | .callUI {
105 | max-width: 90%;
106 | width: 90%;
107 | min-width: 290px;
108 | }
109 | .call__option {
110 | width: 48px;
111 | height: 48px;
112 | margin: 0 8px;
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/src/Components/CallUI/CallUI.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import "./CallUI.css";
3 | import {
4 | Call,
5 | CallEnd,
6 | Mic,
7 | MicOff,
8 | Videocam,
9 | VideocamOff,
10 | } from "@material-ui/icons";
11 | import { truncate } from "../../Utils/truncate";
12 |
13 | function CallUI({
14 | displayName,
15 | id,
16 | incoming,
17 | music,
18 | answerCall,
19 | rejectCall,
20 | myInfo,
21 | videoCamOffHandler,
22 | micOffHandler,
23 | }) {
24 | return (
25 |
30 |
31 | {truncate(displayName, 30)}
32 |
ID - {id}
33 |
{incoming ? "Incoming" : "Calling"}
34 |
35 |
36 | {incoming && (
37 |
38 |
39 |
40 | )}
41 | {!incoming && (
42 |
43 |
44 |
45 | )}
46 |
47 | {myInfo.videoCamOff ? : }
48 |
49 |
50 | {myInfo.micOff ? : }
51 |
52 | {incoming && (
53 |
54 |
55 |
56 | )}
57 |
58 | {/* music for incoming and outgoing calls */}
59 |
65 |
66 | );
67 | }
68 |
69 | export default CallUI;
70 |
--------------------------------------------------------------------------------
/src/Components/Chats/Chats.css:
--------------------------------------------------------------------------------
1 | .chats__container {
2 | background-color: #1b1b1b;
3 | position: fixed;
4 | bottom: 10px;
5 | right: 10px;
6 | max-width: 400px;
7 | width: 100%;
8 | z-index: 9999;
9 | display: flex;
10 | flex-direction: column;
11 | transition: max-height 0.4s;
12 | height: 100%;
13 | max-height: 500px;
14 | border-radius: 10px;
15 | overflow: hidden;
16 | }
17 | .chats__top {
18 | background-color: black;
19 | font-size: 1.3rem;
20 | padding: 15px;
21 | position: relative;
22 | }
23 | .chats__close {
24 | position: absolute;
25 | top: 6px;
26 | right: 10px;
27 | cursor: pointer;
28 | }
29 | .chats {
30 | flex: 1;
31 | padding: 30px 20px;
32 | overflow: auto;
33 | -ms-overflow-style: none; /* IE and Edge */
34 | scrollbar-width: none;
35 | }
36 | .chats::-webkit-scrollbar {
37 | display: none;
38 | }
39 | .chats__bottom {
40 | background-color: black;
41 | padding: 10px;
42 | }
43 | .chats__bottom > form {
44 | display: flex;
45 | }
46 | .chats__bottom input {
47 | flex: 1;
48 | padding: 10px 15px;
49 | border: none;
50 | font-size: 0.9rem;
51 | line-height: 1;
52 | }
53 | .chat__submit {
54 | padding: 10px 20px;
55 | border: none;
56 | outline: none;
57 | background-color: #5277f0;
58 | color: white;
59 | border-bottom-right-radius: 3px;
60 | border-top-right-radius: 3px;
61 | }
62 | .chats__message {
63 | margin-bottom: 3rem;
64 | border-radius: 5px;
65 | position: relative;
66 | word-wrap: break-word;
67 | font-size: 0.9rem;
68 | }
69 | .chats__username {
70 | font-size: 0.7rem;
71 | position: absolute;
72 | top: -20px;
73 | left: 0;
74 | }
75 | .chats__time {
76 | text-align: left;
77 | font-size: 0.7rem;
78 | position: absolute;
79 | bottom: -15px;
80 | right: 0;
81 | }
82 |
83 | @media screen and (max-width: 400px) {
84 | .chats__container {
85 | right: 0;
86 | left: 0;
87 | bottom: 0;
88 | max-width: 100%;
89 | min-width: 320px;
90 | max-height: 80%;
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/src/Components/Chats/Chats.js:
--------------------------------------------------------------------------------
1 | import { Close, Send } from "@material-ui/icons";
2 | import React, { useEffect, useRef, useState } from "react";
3 | import "./Chats.css";
4 | import io from "socket.io-client";
5 | import { v4 as uuidv4 } from "uuid";
6 | import moment from "moment";
7 |
8 | function Chats({
9 | chatsHandler,
10 | addStyle,
11 | userID,
12 | myID,
13 | notifyHandler,
14 | displayName,
15 | }) {
16 | const [chats, setChats] = useState([]);
17 | const [msg, setMsg] = useState("");
18 | const socket = useRef();
19 | const chatsContainer = useRef();
20 |
21 | const chatHandler = (e) => {
22 | e.preventDefault();
23 | const new_msg = {
24 | id: uuidv4(),
25 | name: displayName,
26 | msg: msg,
27 | time: moment().format("LLL"),
28 | };
29 | //send msg
30 | socket.current.emit("chat-message", new_msg, userID);
31 | setChats((chats) => [...chats, new_msg]);
32 | setMsg("");
33 | //scroll to bottom of chats
34 | chatsContainer.current.scrollTo(
35 | 0,
36 | chatsContainer.current.scrollHeight - chatsContainer.current.clientHeight
37 | );
38 | };
39 |
40 | useEffect(() => {
41 | socket.current = io(`${process.env.REACT_APP_SERVER_URL}/chats`);
42 | //socket.current = io("https://localhost:9000/chats");
43 | socket.current.on("connect", () => {
44 | socket.current.emit("join-chat", myID);
45 | //received new chat messages
46 | socket.current.on("chat-msg", (msg) => {
47 | setChats((chats) => [...chats, msg]);
48 | notifyHandler();
49 | chatsContainer.current.scrollTo(
50 | 0,
51 | chatsContainer.current.scrollHeight -
52 | chatsContainer.current.clientHeight
53 | );
54 | });
55 | });
56 |
57 | chatsContainer.current.scrollTo(
58 | 0,
59 | chatsContainer.current.scrollHeight - chatsContainer.current.clientHeight
60 | );
61 |
62 | const Socket = socket.current;
63 | return () => {
64 | Socket.removeAllListeners();
65 | };
66 | }, [myID, notifyHandler]);
67 |
68 | return (
69 |
70 |
71 | Chats
72 |
73 |
74 |
75 | {chats.map((msg) => (
76 |
77 |
{msg.name}
78 | {msg.msg}
79 |
{msg.time}
80 |
81 | ))}
82 |
83 |
84 |
96 |
97 |
98 | );
99 | }
100 |
101 | export default Chats;
102 |
--------------------------------------------------------------------------------
/src/Components/Home/Home.css:
--------------------------------------------------------------------------------
1 | .home {
2 | width: 100%;
3 | height: 100%;
4 | position: relative;
5 | background-color: white;
6 | color: black;
7 | }
8 | .home__content {
9 | padding: 9rem 2% 4rem 2%;
10 | display: flex;
11 | flex-direction: column;
12 | align-items: center;
13 | }
14 |
15 | .home .logo {
16 | position: absolute;
17 | top: 3%;
18 | left: 2%;
19 | }
20 | .home .logo img {
21 | -o-object-fit: contain;
22 | object-fit: contain;
23 | width: 100px;
24 | }
25 |
26 | .home__content h1 {
27 | font-weight: 400;
28 | font-size: 2rem;
29 | margin: auto;
30 | text-align: center;
31 | }
32 | .home__content h1 span {
33 | color: #5277f0;
34 | }
35 |
36 | .home__box {
37 | max-width: 450px;
38 | padding: 25px 25px 3rem 25px;
39 | width: 100%;
40 | background-color: white;
41 | box-shadow: 0 4px 8px 0 #dddddd, 0 6px 20px 0 #ddddddea;
42 | border: 1px solid #ddddddea;
43 | height: 100%;
44 | border-radius: 12px;
45 | }
46 |
47 | .home__box h3,
48 | .model__content h3 {
49 | font-weight: 500;
50 | margin-bottom: 10px;
51 | }
52 |
53 | .home__box .myUserID {
54 | margin: 20px 0;
55 | padding: 15px;
56 | border: 2px dashed #5277f0;
57 | border-radius: 12px;
58 | position: relative;
59 | }
60 | .home__box .copyIcon {
61 | position: absolute;
62 | top: 8px;
63 | right: 8px;
64 | color: #5f5f5f;
65 | cursor: pointer;
66 | }
67 | .home__box .copyIcon:hover {
68 | color: black;
69 | }
70 |
71 | .home__box .myUserID span {
72 | line-height: 1.4rem;
73 | color: #5277f0;
74 | word-break: break-all;
75 | font-size: 1.1rem;
76 | }
77 |
78 | .home input {
79 | padding: 18px;
80 | border: none;
81 | width: 100%;
82 | border-radius: 8px;
83 | font-size: 1.1rem;
84 | line-height: 1;
85 | background-color: #eeeeee;
86 | margin-bottom: 20px;
87 | }
88 |
89 | .home button {
90 | padding: 15px 30px;
91 | border: none;
92 | outline: none;
93 | background-color: #5277f0;
94 | color: white;
95 | border-radius: 8px;
96 | width: 100%;
97 | cursor: pointer;
98 | display: flex;
99 | font-size: 1.1rem;
100 | width: 100%;
101 | justify-content: center;
102 | align-items: center;
103 | }
104 | .home button:hover {
105 | background-color: #345de2;
106 | }
107 | .home button .MuiSvgIcon-root {
108 | font-size: inherit;
109 | }
110 | .video__box .home__callOptions {
111 | display: flex;
112 | align-items: center;
113 | justify-content: center;
114 | }
115 |
116 | .video__box .home__callOption {
117 | background-color: #5277f0;
118 | width: 50px;
119 | height: 50px;
120 | margin: 0 15px;
121 | display: flex;
122 | justify-content: center;
123 | align-items: center;
124 | border-radius: 50%;
125 | cursor: pointer;
126 | }
127 |
128 | .video__box .home__callOption > .MuiSvgIcon-root {
129 | color: white;
130 | }
131 | .home__share {
132 | display: flex;
133 | margin-top: 10px;
134 | align-items: center;
135 | }
136 |
137 | .home .video__box {
138 | position: relative;
139 | overflow: hidden;
140 | max-height: 200px;
141 | border-radius: 12px;
142 | margin-bottom: 20px;
143 | }
144 |
145 | .video__box .home__callOptions {
146 | position: absolute;
147 | bottom: 15px;
148 | left: 0;
149 | right: 0;
150 | }
151 |
152 | .home__box .generating__id {
153 | display: flex;
154 | justify-content: center;
155 | align-items: center;
156 | flex-direction: column;
157 | }
158 |
159 | @-webkit-keyframes modelAnimation {
160 | from {
161 | transform: scale(0);
162 | opacity: 0;
163 | }
164 | to {
165 | transform: scale(1);
166 | opacity: 1;
167 | }
168 | }
169 |
170 | @keyframes modelAnimation {
171 | from {
172 | transform: scale(0);
173 | opacity: 0;
174 | }
175 | to {
176 | transform: scale(1);
177 | opacity: 1;
178 | }
179 | }
180 |
181 | .model {
182 | position: fixed;
183 | background-color: #000000e6;
184 | top: 0;
185 | bottom: 0;
186 | left: 0;
187 | right: 0;
188 | display: flex;
189 | justify-content: center;
190 | align-items: center;
191 | z-index: 99999;
192 | }
193 |
194 | .model .model__content {
195 | padding: 2rem 20px;
196 | border-radius: 6px;
197 | background-color: white;
198 | -webkit-animation: modelAnimation 0.6s;
199 | animation: modelAnimation 0.6s;
200 | max-width: 450px;
201 | width: 100%;
202 | margin: 15px;
203 | }
204 |
205 | .model__content h3 {
206 | margin-bottom: 20px;
207 | }
208 | .model__content h3 span {
209 | color: #5277f0;
210 | }
211 |
212 | .model__content p {
213 | margin-top: 20px;
214 | text-align: center;
215 | font-size: 0.9rem;
216 | }
217 |
218 | @-webkit-keyframes notification-animation {
219 | from {
220 | right: -100%;
221 | }
222 | to {
223 | right: 4%;
224 | }
225 | }
226 | @keyframes notification-animation {
227 | from {
228 | right: -100%;
229 | }
230 | to {
231 | right: 4%;
232 | }
233 | }
234 |
235 | .notification {
236 | position: fixed;
237 | top: 20px;
238 | right: 4%;
239 | z-index: 999;
240 | background-color: #111111e8;
241 | color: white;
242 | max-width: 350px;
243 | padding: 20px;
244 | border-radius: 6px;
245 | -webkit-animation: notification-animation 0.5s;
246 | animation: notification-animation 0.5s;
247 | }
248 |
249 | @media screen and (max-width: 800px) {
250 | .home .logo img {
251 | width: 90px;
252 | }
253 | }
254 |
255 | @media screen and (max-width: 400px) {
256 | .notification {
257 | max-width: 290px;
258 | }
259 | .home .logo img {
260 | width: 80px;
261 | }
262 | .home__box {
263 | padding: 5% 5% 2.5rem 5%;
264 | }
265 | .home__content {
266 | padding: 7rem 2% 4rem 2%;
267 | }
268 | .model .model__content {
269 | min-width: 280px;
270 | }
271 | .video__box .home__callOption {
272 | width: 48px;
273 | height: 48px;
274 | }
275 | }
276 |
--------------------------------------------------------------------------------
/src/Components/Home/Home.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useRef, useState } from "react";
2 | import "./Home.css";
3 | import logo from "../../Images/logo.png";
4 | import { Link } from "react-router-dom";
5 | import Video from "../Video/Video";
6 | import {
7 | Call,
8 | FileCopy,
9 | Mic,
10 | MicOff,
11 | Videocam,
12 | VideocamOff,
13 | } from "@material-ui/icons";
14 | import music from "../../Audio/music.mp3";
15 | import OutgoingMusic from "../../Audio/outgoing.mp3";
16 | import CallUI from "../CallUI/CallUI";
17 | import Loader from "react-loader-spinner";
18 | import {
19 | EmailShareButton,
20 | EmailIcon,
21 | FacebookShareButton,
22 | FacebookIcon,
23 | LinkedinShareButton,
24 | LinkedinIcon,
25 | TelegramShareButton,
26 | TelegramIcon,
27 | TwitterShareButton,
28 | TwitterIcon,
29 | FacebookMessengerShareButton,
30 | FacebookMessengerIcon,
31 | WhatsappShareButton,
32 | WhatsappIcon,
33 | } from "react-share";
34 |
35 | function Home({
36 | callUser,
37 | answerCall,
38 | rejectCall,
39 | incomingCall,
40 | outgoingCall,
41 | myInfo,
42 | userInfo,
43 | videoCamOffHandler,
44 | micOffHandler,
45 | displayNameHandler,
46 | notificationMsg,
47 | }) {
48 | const [inputUserID, setInputUserID] = useState("");
49 | const [inputDisplayName, setInputDisplayName] = useState("");
50 | const itemToCopy = useRef();
51 | const [copied, setCopied] = useState(false);
52 |
53 | const formHandler = (e) => {
54 | e.preventDefault();
55 | callUser(inputUserID);
56 | setInputUserID("");
57 | };
58 |
59 | //user display name
60 | const DisplayNameHandler = (e) => {
61 | e.preventDefault();
62 | displayNameHandler(inputDisplayName);
63 | };
64 |
65 | //copy text to clipboard
66 | const copyToClipboard = () => {
67 | const str = itemToCopy.current.innerText;
68 | const el = document.createElement("textarea");
69 | el.value = str;
70 | el.setAttribute("readonly", "");
71 | el.style.position = "absolute";
72 | el.style.left = "-9999px";
73 | document.body.appendChild(el);
74 | el.select();
75 | document.execCommand("copy");
76 | document.body.removeChild(el);
77 | setCopied(true);
78 | };
79 |
80 | useEffect(() => {
81 | let timeout = null;
82 | if (copied) {
83 | timeout = setTimeout(() => {
84 | setCopied(false);
85 | }, 2000);
86 | }
87 | return () => {
88 | if (timeout) {
89 | clearTimeout(timeout);
90 | }
91 | };
92 | }, [copied]);
93 |
94 | return (
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
116 |
117 |
118 | {myInfo.videoCamOff ? : }
119 |
120 |
121 | {myInfo.micOff ? : }
122 |
123 |
124 |
125 |
126 | {myInfo.id ? (
127 | <>
128 |
My ID
129 |
{myInfo.id}
130 |
131 |
132 |
133 |
137 |
138 |
139 |
143 |
144 |
145 |
149 |
150 |
151 |
155 |
156 |
157 |
161 |
162 |
163 |
167 |
168 |
169 |
173 |
174 |
175 | >
176 | ) : (
177 | <>
178 |
Generating ID
179 |
180 | >
181 | )}
182 |
183 |
196 |
197 |
198 | {incomingCall && (
199 |
210 | )}
211 | {outgoingCall && (
212 |
221 | )}
222 | {!myInfo.displayName && (
223 |
224 |
225 |
226 | Welcome to Call Buddy .
227 |
228 |
240 |
Build with ❤️ by Piyush Sati
241 |
242 |
243 | )}
244 | {(notificationMsg || copied) && (
245 |
246 |
{notificationMsg ? notificationMsg : "Copied"}
247 |
248 | )}
249 |
250 | );
251 | }
252 |
253 | export default Home;
254 |
--------------------------------------------------------------------------------
/src/Components/PageNotFound404/PageNotFound404.css:
--------------------------------------------------------------------------------
1 | .notFound404 {
2 | position: relative;
3 | height: 100vh;
4 | }
5 |
6 | .notFound404 .notfound {
7 | position: absolute;
8 | left: 50%;
9 | top: 50%;
10 | -webkit-transform: translate(-50%, -50%);
11 | -ms-transform: translate(-50%, -50%);
12 | transform: translate(-50%, -50%);
13 | }
14 |
15 | .notfound {
16 | max-width: 520px;
17 | width: 100%;
18 | line-height: 1.4;
19 | text-align: center;
20 | }
21 |
22 | .notfound .notfound-404 {
23 | position: relative;
24 | height: 240px;
25 | }
26 |
27 | .notfound .notfound-404 h1 {
28 | font-family: "Montserrat", sans-serif;
29 | position: absolute;
30 | left: 50%;
31 | top: 50%;
32 | -webkit-transform: translate(-50%, -50%);
33 | -ms-transform: translate(-50%, -50%);
34 | transform: translate(-50%, -50%);
35 | font-size: 252px;
36 | font-weight: 900;
37 | margin: 0px;
38 | color: #262626;
39 | text-transform: uppercase;
40 | letter-spacing: -40px;
41 | margin-left: -20px;
42 | }
43 |
44 | .notfound .notfound-404 h1 > span {
45 | text-shadow: -8px 0px 0px #fff;
46 | }
47 |
48 | .notfound .notfound-404 h3 {
49 | font-family: "Cabin", sans-serif;
50 | position: relative;
51 | font-size: 16px;
52 | font-weight: 700;
53 | text-transform: uppercase;
54 | /*color: #262626;*/
55 | color: white;
56 | margin: 0px;
57 | letter-spacing: 3px;
58 | padding-left: 6px;
59 | }
60 |
61 | .notfound h2 {
62 | font-family: "Cabin", sans-serif;
63 | font-size: 20px;
64 | font-weight: 400;
65 | text-transform: uppercase;
66 | /*color: #000;*/
67 | color: white;
68 | margin-top: 0px;
69 | margin-bottom: 25px;
70 | }
71 |
72 | @media only screen and (max-width: 767px) {
73 | .notfound .notfound-404 {
74 | height: 200px;
75 | }
76 | .notfound .notfound-404 h1 {
77 | font-size: 200px;
78 | }
79 | }
80 |
81 | @media only screen and (max-width: 480px) {
82 | .notfound .notfound-404 {
83 | height: 162px;
84 | }
85 | .notfound .notfound-404 h1 {
86 | font-size: 162px;
87 | height: 150px;
88 | line-height: 162px;
89 | }
90 | .notfound h2 {
91 | font-size: 16px;
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/src/Components/PageNotFound404/PageNotFound404.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import "./PageNotFound404.css";
3 |
4 | function PageNotFound404() {
5 | return (
6 |
7 |
8 |
9 |
Oops! Page not found
10 |
11 | 4
12 | 0
13 | 4
14 |
15 |
16 |
we are sorry, but the page you requested was not found
17 |
18 |
19 | );
20 | }
21 |
22 | export default PageNotFound404;
23 |
--------------------------------------------------------------------------------
/src/Components/Room/Room.css:
--------------------------------------------------------------------------------
1 | .room {
2 | width: 100%;
3 | height: 100%;
4 | background-color: #111;
5 | cursor: pointer;
6 | }
7 |
8 | .room__box {
9 | width: 100%;
10 | height: 100%;
11 | }
12 | .room__options {
13 | position: fixed;
14 | bottom: 60px;
15 | transition: all 0.4s;
16 | cursor: pointer;
17 | width: 100%;
18 | padding: 30px;
19 | display: flex;
20 | justify-content: center;
21 | align-items: center;
22 | z-index: 999;
23 | }
24 | .room__optionsContainer {
25 | display: flex;
26 | }
27 | .room__icon {
28 | margin: 10px;
29 | width: 60px;
30 | height: 60px;
31 | display: flex;
32 | align-items: center;
33 | justify-content: center;
34 | border-radius: 50%;
35 | background-color: #5277f0;
36 | transition: all 0.15s;
37 | }
38 | .chatIcon {
39 | position: relative;
40 | }
41 |
42 | .room .room__userVideo {
43 | position: fixed;
44 | z-index: 1;
45 | top: 10px;
46 | right: 10px;
47 | border-radius: 5px;
48 | width: 220px;
49 | height: 120px;
50 | overflow: hidden;
51 | border: solid 0.2px #a0a0a026;
52 | }
53 |
54 | .room .volumeOff__icon {
55 | position: fixed;
56 | top: 15px;
57 | left: 15px;
58 | z-index: 999;
59 | background-color: #00000036;
60 | }
61 | .room .volumeOff__icon:hover {
62 | background-color: #00000046;
63 | }
64 | .room__options .room__optionsContainer .chatIcon .chat__notify {
65 | position: absolute;
66 | top: 5px;
67 | right: 8px;
68 | color: red;
69 | font-size: 1.4rem;
70 | }
71 | .room__icon:hover {
72 | background-color: #345de2;
73 | }
74 |
75 | .room__optionsContainer .MuiSvgIcon-root {
76 | font-size: 2rem;
77 | color: white;
78 | }
79 | .room__optionsContainer .endCall .MuiSvgIcon-root {
80 | color: red;
81 | }
82 | .room__optionsContainer .endCall:hover {
83 | background-color: #f7f7f7;
84 | }
85 | .room__optionsContainer .endCall {
86 | background-color: white;
87 | }
88 |
89 | .room .hide {
90 | bottom: -100%;
91 | }
92 |
93 | @media screen and (max-width: 1000px) {
94 | .room__icon {
95 | width: 58px;
96 | height: 58px;
97 | margin: 8px;
98 | }
99 | .room__optionsContainer .MuiSvgIcon-root {
100 | font-size: 1.8rem;
101 | }
102 | }
103 | @media screen and (max-width: 500px) {
104 | .room__icon {
105 | width: 55px;
106 | height: 55px;
107 | margin: 7px;
108 | }
109 | .room__optionsContainer .MuiSvgIcon-root {
110 | font-size: 1.7rem;
111 | }
112 | .room .room__userVideo {
113 | width: 185px;
114 | height: 100px;
115 | }
116 | .room__options .room__optionsContainer .chatIcon .chat__notify {
117 | font-size: 1.4rem;
118 | right: 7px;
119 | }
120 | }
121 |
122 | @media screen and (max-width: 350px) {
123 | .room__icon {
124 | width: 50px;
125 | height: 50px;
126 | margin: 6px;
127 | }
128 | .room .room__userVideo {
129 | width: 180px;
130 | height: 95px;
131 | }
132 | .room__optionsContainer .MuiSvgIcon-root {
133 | font-size: 1.6rem;
134 | }
135 | .room__options .room__optionsContainer .chatIcon .chat__notify {
136 | font-size: 1.2rem;
137 | right: 5px;
138 | }
139 | }
140 |
--------------------------------------------------------------------------------
/src/Components/Room/Room.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect, useCallback } from "react";
2 | import "./Room.css";
3 | import Video from "../Video/Video";
4 | import VideocamIcon from "@material-ui/icons/Videocam";
5 | import {
6 | CallEnd,
7 | Chat,
8 | FiberManualRecord,
9 | FlipCameraIos,
10 | Mic,
11 | MicOff,
12 | VideocamOff,
13 | VolumeOff,
14 | VolumeUp,
15 | } from "@material-ui/icons";
16 | import Chats from "../Chats/Chats";
17 |
18 | function Room({
19 | myInfo,
20 | userInfo,
21 | endCall,
22 | videoCamOffHandler,
23 | micOffHandler,
24 | switchCamera,
25 | }) {
26 | const [notificationIndicator, setNotificationIndicator] = useState(false);
27 | const [showChats, setShowChat] = useState(false);
28 | const [volumeOff, setVolumeOff] = useState(false);
29 | const [hide, setHide] = useState(false);
30 | const [switchIcon, setSwitchIcon] = useState(false);
31 |
32 | //show red dot on top of chat icon on receiving new messages
33 | const notifyHandler = useCallback(() => {
34 | if (!showChats) {
35 | setNotificationIndicator(true);
36 | }
37 | }, [showChats]);
38 |
39 | //automatically hide icons after some time
40 | useEffect(() => {
41 | const show = () => {
42 | setHide((hide) => !hide);
43 | };
44 | window.addEventListener("click", show);
45 | let timeOut = null;
46 | if (!hide) {
47 | timeOut = setTimeout(() => {
48 | setHide(true);
49 | }, 10000);
50 | }
51 | return () => {
52 | if (timeOut) {
53 | clearTimeout(timeOut);
54 | }
55 | window.removeEventListener("click", show);
56 | };
57 | }, [hide]);
58 |
59 | //show icon only in mobile
60 | useEffect(() => {
61 | //no of cameras in a device.
62 | let cameras = 0;
63 | navigator.mediaDevices.enumerateDevices().then((devices) => {
64 | devices.forEach((device) => {
65 | if (device.kind === "videoinput") {
66 | cameras++;
67 | }
68 | if (cameras > 1) {
69 | setSwitchIcon(true);
70 | }
71 | });
72 | });
73 | }, []);
74 |
75 | return (
76 |
77 |
78 |
86 |
87 |
88 |
97 |
98 |
99 |
100 |
101 | {myInfo.micOff ? : }
102 |
103 |
104 | {myInfo.videoCamOff ? : }
105 |
106 |
107 |
108 |
109 |
{
112 | setVolumeOff(!volumeOff);
113 | }}
114 | >
115 | {volumeOff ? : }
116 |
117 |
118 | {switchIcon && (
119 |
123 |
124 |
125 | )}
126 |
{
129 | setShowChat(!showChats);
130 | setNotificationIndicator(false);
131 | }}
132 | >
133 |
134 | {notificationIndicator && !showChats && (
135 |
136 | )}
137 |
138 |
139 |
140 |
setShowChat(!showChats)}
148 | notifyHandler={notifyHandler}
149 | />
150 |
151 | );
152 | }
153 |
154 | export default Room;
155 |
--------------------------------------------------------------------------------
/src/Components/Video/Video.css:
--------------------------------------------------------------------------------
1 | .video {
2 | background-color: #1b1b1b;
3 | position: relative;
4 | overflow: hidden;
5 | width: 100%;
6 | height: 100%;
7 | }
8 | video {
9 | width: 100%;
10 | height: 100%;
11 | -o-object-fit: cover;
12 | object-fit: cover;
13 | }
14 | .fullScreen video {
15 | width: 100vw;
16 | height: 100vh;
17 | }
18 | .fullScreen {
19 | width: 100vw;
20 | height: 100vh;
21 | }
22 | .video__stopped {
23 | position: absolute;
24 | top: 0;
25 | left: 0;
26 | bottom: 0;
27 | right: 0;
28 | background-color: #111;
29 | }
30 |
31 | .video__user {
32 | position: absolute;
33 | left: 0px;
34 | bottom: 0px;
35 | font-size: 0.9rem;
36 | background-color: #000000cc;
37 | border-top-right-radius: 4px;
38 | padding: 5px;
39 | display: flex;
40 | z-index: 1;
41 | align-items: center;
42 | max-width: 90%;
43 | border: solid 0.2px #a0a0a026;
44 | }
45 | .video__user .MuiSvgIcon-root {
46 | color: red;
47 | margin-right: 5px;
48 | }
49 | .video__user .video__info {
50 | margin-right: 5px;
51 | }
52 | .video__userName {
53 | padding: 4px;
54 | }
55 | .video__userID {
56 | margin: 5px 0;
57 | word-wrap: break-word;
58 | word-break: break-all;
59 | line-height: 1.2rem;
60 | }
61 |
--------------------------------------------------------------------------------
/src/Components/Video/Video.js:
--------------------------------------------------------------------------------
1 | import { MicOff, VideocamOff } from "@material-ui/icons";
2 | import React, { useEffect, useRef } from "react";
3 | import "./Video.css";
4 | import { truncate } from "../../Utils/truncate";
5 |
6 | function Video({
7 | stream,
8 | id,
9 | userName,
10 | volumeOff,
11 | videoCamOff,
12 | micOff,
13 | full,
14 | you,
15 | addStyle,
16 | }) {
17 | const Video = useRef();
18 |
19 | useEffect(() => {
20 | if (videoCamOff) {
21 | if (stream && stream.getVideoTracks().length > 0) {
22 | stream.getVideoTracks().forEach((track) => {
23 | track.enabled = false;
24 | });
25 | }
26 | } else {
27 | if (stream && stream.getVideoTracks().length > 0) {
28 | stream.getVideoTracks().forEach((track) => {
29 | track.enabled = true;
30 | });
31 | }
32 | }
33 | if (micOff) {
34 | if (stream && stream.getAudioTracks().length > 0) {
35 | stream.getAudioTracks().forEach((track) => {
36 | track.enabled = false;
37 | });
38 | }
39 | } else {
40 | if (stream && stream.getAudioTracks().length > 0) {
41 | stream.getAudioTracks().forEach((track) => {
42 | track.enabled = true;
43 | });
44 | }
45 | }
46 | if (volumeOff) {
47 | Video.current.volume = 0.0;
48 | Video.current.muted = true;
49 | } else {
50 | Video.current.volume = 1.0;
51 | Video.current.muted = false;
52 | }
53 | Video.current.srcObject = stream;
54 | const playVideo = () => {
55 | Video.current.play();
56 | };
57 | Video.current.addEventListener("loadedmetadata", playVideo);
58 |
59 | const VideoElement = Video.current;
60 | return () => {
61 | VideoElement.removeEventListener("loadedmetadata", playVideo);
62 | };
63 | }, [stream, volumeOff, micOff, videoCamOff]);
64 |
65 | return (
66 |
67 |
72 | {videoCamOff &&
}
73 | {userName && (
74 |
75 | {!you && (
76 |
77 | {videoCamOff && }
78 | {micOff && }
79 |
80 | )}
81 |
82 | {truncate(userName, 30)}
83 | {id &&
ID : {id}
}
84 |
85 |
86 | )}
87 |
88 | );
89 | }
90 |
91 | export default Video;
92 |
--------------------------------------------------------------------------------
/src/Images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Pinqua/Call-Buddy-Frontend/f8d2500ccede37adccfb6052e7cc85c8b36ec1fc/src/Images/logo.png
--------------------------------------------------------------------------------
/src/Utils/truncate.js:
--------------------------------------------------------------------------------
1 | export function truncate(str, n) {
2 | return str?.length > n ? str.substr(0, n - 1) + "..." : str;
3 | }
4 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | * {
2 | margin: 0;
3 | padding: 0;
4 | box-sizing: border-box;
5 | -webkit-box-sizing: border-box;
6 | scroll-behavior: smooth;
7 | -webkit-tap-highlight-color: transparent;
8 | }
9 | html {
10 | scroll-behavior: smooth;
11 | }
12 |
13 | body {
14 | margin: 0;
15 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
16 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
17 | sans-serif;
18 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
19 | -webkit-font-smoothing: antialiased;
20 | -moz-osx-font-smoothing: grayscale;
21 | min-width: 320px;
22 | font-weight: 400;
23 | -ms-overflow-style: none; /* IE and Edge */
24 | scrollbar-width: none;
25 | }
26 |
27 | body::-webkit-scrollbar {
28 | display: none;
29 | }
30 | code {
31 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
32 | monospace;
33 | }
34 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 | import "./index.css";
4 | import App from "./App";
5 | import "./normalize.css";
6 | import * as serviceWorkerRegistration from "./serviceWorkerRegistration";
7 | import reportWebVitals from "./reportWebVitals";
8 |
9 | ReactDOM.render(
10 |
11 |
12 | ,
13 | document.getElementById("root")
14 | );
15 |
16 | // If you want your app to work offline and load faster, you can change
17 | // unregister() to register() below. Note this comes with some pitfalls.
18 | // Learn more about service workers: https://cra.link/PWA
19 | serviceWorkerRegistration.register();
20 |
21 | // If you want to start measuring performance in your app, pass a function
22 | // to log results (for example: reportWebVitals(console.log))
23 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
24 | reportWebVitals();
25 |
--------------------------------------------------------------------------------
/src/normalize.css:
--------------------------------------------------------------------------------
1 | /*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */
2 |
3 | /* Document
4 | ========================================================================== */
5 |
6 | /**
7 | * 1. Correct the line height in all browsers.
8 | * 2. Prevent adjustments of font size after orientation changes in iOS.
9 | */
10 |
11 | html {
12 | line-height: 1.15; /* 1 */
13 | -webkit-text-size-adjust: 100%; /* 2 */
14 | }
15 |
16 | /* Sections
17 | ========================================================================== */
18 |
19 | /**
20 | * Remove the margin in all browsers.
21 | */
22 |
23 | body {
24 | margin: 0;
25 | }
26 |
27 | /**
28 | * Render the `main` element consistently in IE.
29 | */
30 |
31 | main {
32 | display: block;
33 | }
34 |
35 | /**
36 | * Correct the font size and margin on `h1` elements within `section` and
37 | * `article` contexts in Chrome, Firefox, and Safari.
38 | */
39 |
40 | h1 {
41 | font-size: 2em;
42 | margin: 0.67em 0;
43 | }
44 |
45 | /* Grouping content
46 | ========================================================================== */
47 |
48 | /**
49 | * 1. Add the correct box sizing in Firefox.
50 | * 2. Show the overflow in Edge and IE.
51 | */
52 |
53 | hr {
54 | box-sizing: content-box; /* 1 */
55 | height: 0; /* 1 */
56 | overflow: visible; /* 2 */
57 | }
58 |
59 | /**
60 | * 1. Correct the inheritance and scaling of font size in all browsers.
61 | * 2. Correct the odd `em` font sizing in all browsers.
62 | */
63 |
64 | pre {
65 | font-family: monospace, monospace; /* 1 */
66 | font-size: 1em; /* 2 */
67 | }
68 |
69 | /* Text-level semantics
70 | ========================================================================== */
71 |
72 | /**
73 | * Remove the gray background on active links in IE 10.
74 | */
75 |
76 | a {
77 | background-color: transparent;
78 | }
79 |
80 | /**
81 | * 1. Remove the bottom border in Chrome 57-
82 | * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
83 | */
84 |
85 | abbr[title] {
86 | border-bottom: none; /* 1 */
87 | text-decoration: underline; /* 2 */
88 | text-decoration: underline dotted; /* 2 */
89 | }
90 |
91 | /**
92 | * Add the correct font weight in Chrome, Edge, and Safari.
93 | */
94 |
95 | b,
96 | strong {
97 | font-weight: bolder;
98 | }
99 |
100 | /**
101 | * 1. Correct the inheritance and scaling of font size in all browsers.
102 | * 2. Correct the odd `em` font sizing in all browsers.
103 | */
104 |
105 | code,
106 | kbd,
107 | samp {
108 | font-family: monospace, monospace; /* 1 */
109 | font-size: 1em; /* 2 */
110 | }
111 |
112 | /**
113 | * Add the correct font size in all browsers.
114 | */
115 |
116 | small {
117 | font-size: 80%;
118 | }
119 |
120 | /**
121 | * Prevent `sub` and `sup` elements from affecting the line height in
122 | * all browsers.
123 | */
124 |
125 | sub,
126 | sup {
127 | font-size: 75%;
128 | line-height: 0;
129 | position: relative;
130 | vertical-align: baseline;
131 | }
132 |
133 | sub {
134 | bottom: -0.25em;
135 | }
136 |
137 | sup {
138 | top: -0.5em;
139 | }
140 |
141 | /* Embedded content
142 | ========================================================================== */
143 |
144 | /**
145 | * Remove the border on images inside links in IE 10.
146 | */
147 |
148 | img {
149 | border-style: none;
150 | }
151 |
152 | /* Forms
153 | ========================================================================== */
154 |
155 | /**
156 | * 1. Change the font styles in all browsers.
157 | * 2. Remove the margin in Firefox and Safari.
158 | */
159 |
160 | button,
161 | input,
162 | optgroup,
163 | select,
164 | textarea {
165 | font-family: inherit; /* 1 */
166 | font-size: 100%; /* 1 */
167 | line-height: 1.15; /* 1 */
168 | margin: 0; /* 2 */
169 | }
170 |
171 | /**
172 | * Show the overflow in IE.
173 | * 1. Show the overflow in Edge.
174 | */
175 |
176 | button,
177 | input {
178 | /* 1 */
179 | overflow: visible;
180 | }
181 |
182 | /**
183 | * Remove the inheritance of text transform in Edge, Firefox, and IE.
184 | * 1. Remove the inheritance of text transform in Firefox.
185 | */
186 |
187 | button,
188 | select {
189 | /* 1 */
190 | text-transform: none;
191 | }
192 |
193 | /**
194 | * Correct the inability to style clickable types in iOS and Safari.
195 | */
196 |
197 | button,
198 | [type="button"],
199 | [type="reset"],
200 | [type="submit"] {
201 | -webkit-appearance: button;
202 | }
203 |
204 | /**
205 | * Remove the inner border and padding in Firefox.
206 | */
207 |
208 | button::-moz-focus-inner,
209 | [type="button"]::-moz-focus-inner,
210 | [type="reset"]::-moz-focus-inner,
211 | [type="submit"]::-moz-focus-inner {
212 | border-style: none;
213 | padding: 0;
214 | }
215 |
216 | /**
217 | * Restore the focus styles unset by the previous rule.
218 | */
219 |
220 | button:-moz-focusring,
221 | [type="button"]:-moz-focusring,
222 | [type="reset"]:-moz-focusring,
223 | [type="submit"]:-moz-focusring {
224 | outline: 1px dotted ButtonText;
225 | }
226 |
227 | /**
228 | * Correct the padding in Firefox.
229 | */
230 |
231 | fieldset {
232 | padding: 0.35em 0.75em 0.625em;
233 | }
234 |
235 | /**
236 | * 1. Correct the text wrapping in Edge and IE.
237 | * 2. Correct the color inheritance from `fieldset` elements in IE.
238 | * 3. Remove the padding so developers are not caught out when they zero out
239 | * `fieldset` elements in all browsers.
240 | */
241 |
242 | legend {
243 | box-sizing: border-box; /* 1 */
244 | color: inherit; /* 2 */
245 | display: table; /* 1 */
246 | max-width: 100%; /* 1 */
247 | padding: 0; /* 3 */
248 | white-space: normal; /* 1 */
249 | }
250 |
251 | /**
252 | * Add the correct vertical alignment in Chrome, Firefox, and Opera.
253 | */
254 |
255 | progress {
256 | vertical-align: baseline;
257 | }
258 |
259 | /**
260 | * Remove the default vertical scrollbar in IE 10+.
261 | */
262 |
263 | textarea {
264 | overflow: auto;
265 | }
266 |
267 | /**
268 | * 1. Add the correct box sizing in IE 10.
269 | * 2. Remove the padding in IE 10.
270 | */
271 |
272 | [type="checkbox"],
273 | [type="radio"] {
274 | box-sizing: border-box; /* 1 */
275 | padding: 0; /* 2 */
276 | }
277 |
278 | /**
279 | * Correct the cursor style of increment and decrement buttons in Chrome.
280 | */
281 |
282 | [type="number"]::-webkit-inner-spin-button,
283 | [type="number"]::-webkit-outer-spin-button {
284 | height: auto;
285 | }
286 |
287 | /**
288 | * 1. Correct the odd appearance in Chrome and Safari.
289 | * 2. Correct the outline style in Safari.
290 | */
291 |
292 | [type="search"] {
293 | -webkit-appearance: textfield; /* 1 */
294 | outline-offset: -2px; /* 2 */
295 | }
296 |
297 | /**
298 | * Remove the inner padding in Chrome and Safari on macOS.
299 | */
300 |
301 | [type="search"]::-webkit-search-decoration {
302 | -webkit-appearance: none;
303 | }
304 |
305 | /**
306 | * 1. Correct the inability to style clickable types in iOS and Safari.
307 | * 2. Change font properties to `inherit` in Safari.
308 | */
309 |
310 | ::-webkit-file-upload-button {
311 | -webkit-appearance: button; /* 1 */
312 | font: inherit; /* 2 */
313 | }
314 |
315 | /* Interactive
316 | ========================================================================== */
317 |
318 | /*
319 | * Add the correct display in Edge, IE 10+, and Firefox.
320 | */
321 |
322 | details {
323 | display: block;
324 | }
325 |
326 | /*
327 | * Add the correct display in all browsers.
328 | */
329 |
330 | summary {
331 | display: list-item;
332 | }
333 |
334 | /* Misc
335 | ========================================================================== */
336 |
337 | /**
338 | * Add the correct display in IE 10+.
339 | */
340 |
341 | template {
342 | display: none;
343 | }
344 |
345 | /**
346 | * Add the correct display in IE 10.
347 | */
348 |
349 | [hidden] {
350 | display: none;
351 | }
352 |
--------------------------------------------------------------------------------
/src/reportWebVitals.js:
--------------------------------------------------------------------------------
1 | const reportWebVitals = (onPerfEntry) => {
2 | if (onPerfEntry && onPerfEntry instanceof Function) {
3 | import("web-vitals").then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
4 | getCLS(onPerfEntry);
5 | getFID(onPerfEntry);
6 | getFCP(onPerfEntry);
7 | getLCP(onPerfEntry);
8 | getTTFB(onPerfEntry);
9 | });
10 | }
11 | };
12 |
13 | export default reportWebVitals;
14 |
--------------------------------------------------------------------------------
/src/service-worker.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-restricted-globals */
2 |
3 | // This service worker can be customized!
4 | // See https://developers.google.com/web/tools/workbox/modules
5 | // for the list of available Workbox modules, or add any other
6 | // code you'd like.
7 | // You can also remove this file if you'd prefer not to use a
8 | // service worker, and the Workbox build step will be skipped.
9 |
10 | import { clientsClaim } from "workbox-core";
11 | import { ExpirationPlugin } from "workbox-expiration";
12 | import { precacheAndRoute, createHandlerBoundToURL } from "workbox-precaching";
13 | import { registerRoute } from "workbox-routing";
14 | import { StaleWhileRevalidate } from "workbox-strategies";
15 |
16 | clientsClaim();
17 |
18 | // Precache all of the assets generated by your build process.
19 | // Their URLs are injected into the manifest variable below.
20 | // This variable must be present somewhere in your service worker file,
21 | // even if you decide not to use precaching. See https://cra.link/PWA
22 | precacheAndRoute(self.__WB_MANIFEST);
23 |
24 | // Set up App Shell-style routing, so that all navigation requests
25 | // are fulfilled with your index.html shell. Learn more at
26 | // https://developers.google.com/web/fundamentals/architecture/app-shell
27 | const fileExtensionRegexp = new RegExp("/[^/?]+\\.[^/]+$");
28 | registerRoute(
29 | // Return false to exempt requests from being fulfilled by index.html.
30 | ({ request, url }) => {
31 | // If this isn't a navigation, skip.
32 | if (request.mode !== "navigate") {
33 | return false;
34 | } // If this is a URL that starts with /_, skip.
35 |
36 | if (url.pathname.startsWith("/_")) {
37 | return false;
38 | } // If this looks like a URL for a resource, because it contains // a file extension, skip.
39 |
40 | if (url.pathname.match(fileExtensionRegexp)) {
41 | return false;
42 | } // Return true to signal that we want to use the handler.
43 |
44 | return true;
45 | },
46 | createHandlerBoundToURL(process.env.PUBLIC_URL + "/index.html")
47 | );
48 |
49 | // An example runtime caching route for requests that aren't handled by the
50 | // precache, in this case same-origin .png requests like those from in public/
51 | registerRoute(
52 | // Add in any other file extensions or routing criteria as needed.
53 | ({ url }) =>
54 | url.origin === self.location.origin && url.pathname.endsWith(".png"), // Customize this strategy as needed, e.g., by changing to CacheFirst.
55 | new StaleWhileRevalidate({
56 | cacheName: "images",
57 | plugins: [
58 | // Ensure that once this runtime cache reaches a maximum size the
59 | // least-recently used images are removed.
60 | new ExpirationPlugin({ maxEntries: 50 }),
61 | ],
62 | })
63 | );
64 |
65 | // This allows the web app to trigger skipWaiting via
66 | // registration.waiting.postMessage({type: 'SKIP_WAITING'})
67 | self.addEventListener("message", (event) => {
68 | if (event.data && event.data.type === "SKIP_WAITING") {
69 | self.skipWaiting();
70 | }
71 | });
72 |
73 | // Any other custom service worker logic can go here.
74 |
--------------------------------------------------------------------------------
/src/serviceWorkerRegistration.js:
--------------------------------------------------------------------------------
1 | // This optional code is used to register a service worker.
2 | // register() is not called by default.
3 |
4 | // This lets the app load faster on subsequent visits in production, and gives
5 | // it offline capabilities. However, it also means that developers (and users)
6 | // will only see deployed updates on subsequent visits to a page, after all the
7 | // existing tabs open on the page have been closed, since previously cached
8 | // resources are updated in the background.
9 |
10 | // To learn more about the benefits of this model and instructions on how to
11 | // opt-in, read https://cra.link/PWA
12 |
13 | const isLocalhost = Boolean(
14 | window.location.hostname === "localhost" ||
15 | // [::1] is the IPv6 localhost address.
16 | window.location.hostname === "[::1]" ||
17 | // 127.0.0.0/8 are considered localhost for IPv4.
18 | window.location.hostname.match(
19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
20 | )
21 | );
22 |
23 | export function register(config) {
24 | if (process.env.NODE_ENV === "production" && "serviceWorker" in navigator) {
25 | // The URL constructor is available in all browsers that support SW.
26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
27 | if (publicUrl.origin !== window.location.origin) {
28 | // Our service worker won't work if PUBLIC_URL is on a different origin
29 | // from what our page is served on. This might happen if a CDN is used to
30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374
31 | return;
32 | }
33 |
34 | window.addEventListener("load", () => {
35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
36 |
37 | if (isLocalhost) {
38 | // This is running on localhost. Let's check if a service worker still exists or not.
39 | checkValidServiceWorker(swUrl, config);
40 |
41 | // Add some additional logging to localhost, pointing developers to the
42 | // service worker/PWA documentation.
43 | navigator.serviceWorker.ready.then(() => {
44 | console.log(
45 | "This web app is being served cache-first by a service " +
46 | "worker. To learn more, visit https://cra.link/PWA"
47 | );
48 | });
49 | } else {
50 | // Is not localhost. Just register service worker
51 | registerValidSW(swUrl, config);
52 | }
53 | });
54 | }
55 | }
56 |
57 | function registerValidSW(swUrl, config) {
58 | navigator.serviceWorker
59 | .register(swUrl)
60 | .then((registration) => {
61 | registration.onupdatefound = () => {
62 | const installingWorker = registration.installing;
63 | if (installingWorker == null) {
64 | return;
65 | }
66 | installingWorker.onstatechange = () => {
67 | if (installingWorker.state === "installed") {
68 | if (navigator.serviceWorker.controller) {
69 | // At this point, the updated precached content has been fetched,
70 | // but the previous service worker will still serve the older
71 | // content until all client tabs are closed.
72 | console.log(
73 | "New content is available and will be used when all " +
74 | "tabs for this page are closed. See https://cra.link/PWA."
75 | );
76 |
77 | // Execute callback
78 | if (config && config.onUpdate) {
79 | config.onUpdate(registration);
80 | }
81 | } else {
82 | // At this point, everything has been precached.
83 | // It's the perfect time to display a
84 | // "Content is cached for offline use." message.
85 | console.log("Content is cached for offline use.");
86 |
87 | // Execute callback
88 | if (config && config.onSuccess) {
89 | config.onSuccess(registration);
90 | }
91 | }
92 | }
93 | };
94 | };
95 | })
96 | .catch((error) => {
97 | console.error("Error during service worker registration:", error);
98 | });
99 | }
100 |
101 | function checkValidServiceWorker(swUrl, config) {
102 | // Check if the service worker can be found. If it can't reload the page.
103 | fetch(swUrl, {
104 | headers: { "Service-Worker": "script" },
105 | })
106 | .then((response) => {
107 | // Ensure service worker exists, and that we really are getting a JS file.
108 | const contentType = response.headers.get("content-type");
109 | if (
110 | response.status === 404 ||
111 | (contentType != null && contentType.indexOf("javascript") === -1)
112 | ) {
113 | // No service worker found. Probably a different app. Reload the page.
114 | navigator.serviceWorker.ready.then((registration) => {
115 | registration.unregister().then(() => {
116 | window.location.reload();
117 | });
118 | });
119 | } else {
120 | // Service worker found. Proceed as normal.
121 | registerValidSW(swUrl, config);
122 | }
123 | })
124 | .catch(() => {
125 | console.log(
126 | "No internet connection found. App is running in offline mode."
127 | );
128 | });
129 | }
130 |
131 | export function unregister() {
132 | if ("serviceWorker" in navigator) {
133 | navigator.serviceWorker.ready
134 | .then((registration) => {
135 | registration.unregister();
136 | })
137 | .catch((error) => {
138 | console.error(error.message);
139 | });
140 | }
141 | }
142 |
--------------------------------------------------------------------------------
/src/setupTests.js:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import "@testing-library/jest-dom";
6 |
--------------------------------------------------------------------------------