├── .babelrc ├── .gitignore ├── .prettierrc ├── License.md ├── README.md ├── clients ├── .gitignore ├── README.md ├── package-lock.json ├── package.json ├── public │ ├── favicon.ico │ ├── index.html │ ├── logo.png │ ├── logo192.png │ ├── logo512.png │ ├── manifest.json │ └── robots.txt ├── src │ ├── App.js │ ├── App.test.js │ ├── apis │ │ ├── auth.js │ │ ├── chat.js │ │ └── messages.js │ ├── assets │ │ ├── logo.png │ │ └── no-contacts.jpg │ ├── components │ │ ├── Contacts.jsx │ │ ├── Group.jsx │ │ ├── MessageHistory.jsx │ │ ├── Model.jsx │ │ ├── Profile.jsx │ │ ├── Start.jsx │ │ ├── group │ │ │ └── Search.jsx │ │ ├── profile │ │ │ └── InputEdit.jsx │ │ └── ui │ │ │ ├── Loading.jsx │ │ │ ├── NoContacts.jsx │ │ │ ├── SkeletonLoading.jsx │ │ │ └── Typing.jsx │ ├── index.css │ ├── index.js │ ├── pages │ │ ├── Chat.jsx │ │ ├── Home.jsx │ │ ├── Login.jsx │ │ ├── Regsiter.jsx │ │ └── home.css │ ├── redux │ │ ├── activeUserSlice.js │ │ ├── chatsSlice.js │ │ ├── profileSlice.js │ │ └── searchSlice.js │ ├── reportWebVitals.js │ ├── setupTests.js │ ├── store.js │ └── utils │ │ └── logics.js └── tailwind.config.js ├── package-lock.json ├── package.json └── server ├── .gitignore ├── controllers ├── chatControllers.js ├── messageControllers.js └── user.js ├── index.js ├── middleware └── user.js ├── models ├── chatModel.js ├── messageModel.js └── userModel.js ├── mongoDB └── connection.js ├── package-lock.json ├── package.json └── routes ├── chat.js ├── message.js └── user.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env"] 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | .env 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 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "es5" 4 | } -------------------------------------------------------------------------------- /License.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Shakir Farhan 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 | # Real-Time Chat Website with MERN Stack, Socket.io, Redux Toolkit, and Tailwind CSS 2 | 3 | This is a real-time chat website that allows users to connect with each other and chat in real-time. It was built using the MERN stack (MongoDB, Express.js, React.js, and Node.js), Socket.io, Redux Toolkit, and Tailwind CSS. 4 | 5 | - If you liked it then give this Repository a Star⭐ 6 | - Youtube Demo : Click On Me 7 | 8 | ## Technologies Used 9 | 10 | - MERN stack (MongoDB, Express.js, React.js, and Node.js) 11 | - Socket.io 12 | - Redux Toolkit 13 | - Tailwind CSS 14 | 15 | ## Features 16 | 17 | - Real-time chat: users can send and receive messages in real-time 18 | - User authentication: users can sign up, log in, and log out using JWT and Google Auth 19 | - Group creation: users can create chat rooms and invite others to join 20 | - Notifications: users can receive notifications on new messages 21 | - Emojis: users can send and receive emojis in messages 22 | - Profile page where users can update their avatar and display name. 23 | - Users can create a room to chat with others. 24 | - Search functionality. 25 | - Responsive design: the website is optimized for different screen sizes and devices 26 | 27 | ## Configuration and Setup 28 | In order to run this project locally, simply fork and clone the repository or download as zip and unzip on your machine. 29 | 30 | - Open the project in your prefered code editor. 31 | - Go to terminal -> New terminal (If you are using VS code) 32 | - Split your terminal into two (run the client on one terminal and the server on the other terminal) 33 | 34 | In the first terminal 35 | - cd client and create a .env file in the root of your client directory. 36 | - Supply the following credentials 37 | 38 | ``` 39 | REACT_APP_GOOGLE_CLIENT_ID = 40 | REACT_APP_SERVER_URL='http://localhost:8000' 41 | ``` 42 | 43 | To get your Google ClientID for authentication, go to the [credential Page ](https://console.cloud.google.com/apis/credentials) (if you are new, then [create a new project first](https://console.cloud.google.com/projectcreate) and follow the following steps; 44 | 45 | - Click Create credentials > OAuth client ID. 46 | - Select the Web application type. 47 | - Name your OAuth client and click Create 48 | - Remember to provide your domain and redirect URL so that Google identifies the origin domain to which it can display the consent screen. In development, that is going to be `http://localhost:3000` and `http://localhost:3000/login` 49 | - Copy the Client ID and assign it to the variable `REACT_APP_GOOGLE_CLIENT_ID` in your .env file 50 | 51 | ``` 52 | $ cd client 53 | $ npm install (to install client-side dependencies) 54 | $ npm start (to start the client) 55 | ``` 56 | In the second terminal 57 | - cd server and create a .env file in the root of your server directory. 58 | - Supply the following credentials 59 | 60 | ``` 61 | PORT=8000 62 | URL= 63 | SECRET= 64 | CLIENT_ID= 65 | BASE_URL="http://localhost:3000" 66 | ``` 67 | ``` 68 | $ cd server 69 | $ npm install (to install server-side dependencies) 70 | & npm start (to start the server) 71 | ``` 72 | 73 | 74 | ## Contributing 75 | 76 | Contributions to this project are welcome! If you find a bug or want to add a feature, please submit an issue or a pull request. To contribute, follow these steps: 77 | 78 | 1. Fork the repository 79 | 2. Create a new branch for your feature: `git checkout -b my-new-feature` 80 | 3. Make changes and commit them: `git commit -m 'Add some feature'` 81 | 4. Push your branch to your forked repository: `git push origin my-new-feature` 82 | 5. Create a Pull Request 83 | 84 | -------------------------------------------------------------------------------- /clients/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | .env 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 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /clients/README.md: -------------------------------------------------------------------------------- 1 | # Getting Started with Create React App 2 | 3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 4 | 5 | ## Available Scripts 6 | 7 | In the project directory, you can run: 8 | 9 | ### `npm start` 10 | 11 | Runs the app in the development mode.\ 12 | Open [http://localhost:3000](http://localhost:3000) to view it in your browser. 13 | 14 | The page will reload when you make changes.\ 15 | You may also see any lint errors in the console. 16 | 17 | ### `npm test` 18 | 19 | Launches the test runner in the interactive watch mode.\ 20 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 21 | 22 | ### `npm run build` 23 | 24 | Builds the app for production to the `build` folder.\ 25 | It correctly bundles React in production mode and optimizes the build for the best performance. 26 | 27 | The build is minified and the filenames include the hashes.\ 28 | Your app is ready to be deployed! 29 | 30 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 31 | 32 | ### `npm run eject` 33 | 34 | **Note: this is a one-way operation. Once you `eject`, you can't go back!** 35 | 36 | If you aren't satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 37 | 38 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you're on your own. 39 | 40 | You don't have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn't feel obligated to use this feature. However we understand that this tool wouldn't be useful if you couldn't customize it when you are ready for it. 41 | 42 | ## Learn More 43 | 44 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 45 | 46 | To learn React, check out the [React documentation](https://reactjs.org/). 47 | 48 | ### Code Splitting 49 | 50 | This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting) 51 | 52 | ### Analyzing the Bundle Size 53 | 54 | This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size) 55 | 56 | ### Making a Progressive Web App 57 | 58 | This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app) 59 | 60 | ### Advanced Configuration 61 | 62 | This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration) 63 | 64 | ### Deployment 65 | 66 | This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment) 67 | 68 | ### `npm run build` fails to minify 69 | 70 | This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify) 71 | -------------------------------------------------------------------------------- /clients/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "clients", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@chakra-ui/react": "^2.7.1", 7 | "@emoji-mart/data": "^1.1.2", 8 | "@emoji-mart/react": "^1.1.1", 9 | "@emotion/react": "^11.11.1", 10 | "@emotion/styled": "^11.11.0", 11 | "@mui/material": "^5.11.4", 12 | "@reduxjs/toolkit": "^1.9.1", 13 | "@testing-library/jest-dom": "^5.16.5", 14 | "@testing-library/react": "^13.4.0", 15 | "@testing-library/user-event": "^13.5.0", 16 | "axios": "^1.2.2", 17 | "dotenv": "^16.0.3", 18 | "emoji-mart": "^5.5.2", 19 | "framer-motion": "^10.12.18", 20 | "gapi-script": "^1.2.0", 21 | "react": "^18.2.0", 22 | "react-dom": "^18.2.0", 23 | "react-google-login": "^5.2.2", 24 | "react-icons": "^4.7.1", 25 | "react-loading-skeleton": "^3.2.0", 26 | "react-notification-badge": "^1.5.1", 27 | "react-redux": "^8.0.5", 28 | "react-router": "^6.9.0", 29 | "react-router-dom": "^6.6.2", 30 | "react-scripts": "5.0.1", 31 | "react-scrollable-feed": "^1.3.1", 32 | "react-toastify": "^9.1.1", 33 | "react-transition-group": "^4.4.5", 34 | "socket.io-client": "^4.5.4", 35 | "web-vitals": "^2.1.4" 36 | }, 37 | "scripts": { 38 | "start": "react-scripts start", 39 | "build": "react-scripts build", 40 | "test": "react-scripts test", 41 | "eject": "react-scripts eject" 42 | }, 43 | "eslintConfig": { 44 | "extends": [ 45 | "react-app", 46 | "react-app/jest" 47 | ] 48 | }, 49 | "browserslist": { 50 | "production": [ 51 | ">0.2%", 52 | "not dead", 53 | "not op_mini all" 54 | ], 55 | "development": [ 56 | "last 1 chrome version", 57 | "last 1 firefox version", 58 | "last 1 safari version" 59 | ] 60 | }, 61 | "devDependencies": { 62 | "tailwindcss": "^3.2.4" 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /clients/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sahandmohammadrehzaii/Realtime-Chat/c6a2524907b8e5cf63356c2144af41e51398461b/clients/public/favicon.ico -------------------------------------------------------------------------------- /clients/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 19 | 20 | 21 | 25 | 26 | 35 | Talk Time 36 | 41 | 42 | 43 | 44 |
45 | 46 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /clients/public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sahandmohammadrehzaii/Realtime-Chat/c6a2524907b8e5cf63356c2144af41e51398461b/clients/public/logo.png -------------------------------------------------------------------------------- /clients/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sahandmohammadrehzaii/Realtime-Chat/c6a2524907b8e5cf63356c2144af41e51398461b/clients/public/logo192.png -------------------------------------------------------------------------------- /clients/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sahandmohammadrehzaii/Realtime-Chat/c6a2524907b8e5cf63356c2144af41e51398461b/clients/public/logo512.png -------------------------------------------------------------------------------- /clients/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /clients/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /clients/src/App.js: -------------------------------------------------------------------------------- 1 | import Login from './pages/Login'; 2 | import { BrowserRouter as Router, Routes, Route } from 'react-router-dom'; 3 | import Regsiter from './pages/Regsiter'; 4 | import Home from './pages/Home'; 5 | import Start from './components/Start'; 6 | 7 | function App() { 8 | return ( 9 |
10 | 11 | 12 | } /> 13 | } /> 14 | } /> 15 | } /> 16 | 17 | 18 |
19 | ); 20 | } 21 | 22 | export default App; 23 | -------------------------------------------------------------------------------- /clients/src/App.test.js: -------------------------------------------------------------------------------- 1 | import { render, screen } from '@testing-library/react'; 2 | import App from './App'; 3 | 4 | test('renders learn react link', () => { 5 | render(); 6 | const linkElement = screen.getByText(/learn react/i); 7 | expect(linkElement).toBeInTheDocument(); 8 | }); 9 | -------------------------------------------------------------------------------- /clients/src/apis/auth.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { toast } from 'react-toastify'; 3 | const API = (token) => 4 | axios.create({ 5 | baseURL: process.env.REACT_APP_SERVER_URL, 6 | headers: { Authorization: token }, 7 | }); 8 | let url = process.env.REACT_APP_SERVER_URL; 9 | export const loginUser = async (body) => { 10 | try { 11 | return await axios.post(`${url}/auth/login`, body); 12 | } catch (error) { 13 | console.log('error in loginuser api'); 14 | } 15 | }; 16 | export const googleAuth = async (body) => { 17 | try { 18 | return await axios.post(`${url}/api/google`, body); 19 | } catch (error) { 20 | console.log(error); 21 | } 22 | }; 23 | export const registerUser = async (body) => { 24 | try { 25 | return await axios.post(`${url}/auth/register`, body); 26 | } catch (error) { 27 | console.log('error in register api'); 28 | } 29 | }; 30 | export const validUser = async () => { 31 | try { 32 | const token = localStorage.getItem('userToken'); 33 | 34 | const { data } = await API(token).get(`/auth/valid`, { 35 | headers: { Authorization: token }, 36 | }); 37 | return data; 38 | } catch (error) { 39 | console.log('error in valid user api'); 40 | } 41 | }; 42 | export const searchUsers = async (id) => { 43 | try { 44 | const token = localStorage.getItem('userToken'); 45 | 46 | return await API(token).get(`/api/user?search=${id}`); 47 | } catch (error) { 48 | console.log('error in search users api'); 49 | } 50 | }; 51 | export const updateUser = async (id, body) => { 52 | try { 53 | const token = localStorage.getItem('userToken'); 54 | 55 | const { data } = await API(token).patch(`/api/users/update/${id}`, body); 56 | return data; 57 | } catch (error) { 58 | console.log('error in update user api'); 59 | toast.error('Something Went Wrong.try Again!'); 60 | } 61 | }; 62 | export const checkValid = async () => { 63 | const data = await validUser(); 64 | if (!data?.user) { 65 | window.location.href = '/login'; 66 | } else { 67 | window.location.href = '/chats'; 68 | } 69 | }; 70 | -------------------------------------------------------------------------------- /clients/src/apis/chat.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { toast } from 'react-toastify'; 3 | const API = (token) => 4 | axios.create({ 5 | baseURL: process.env.REACT_APP_SERVER_URL, 6 | headers: { Authorization: token }, 7 | }); 8 | export const acessCreate = async (body) => { 9 | try { 10 | const token = localStorage.getItem('userToken'); 11 | 12 | const { data } = await API(token).post('/api/chat', body); 13 | console.log(data); 14 | return data; 15 | } catch (error) { 16 | console.log('error in access create api'); 17 | } 18 | }; 19 | export const fetchAllChats = async () => { 20 | try { 21 | const token = localStorage.getItem('userToken'); 22 | const { data } = await API(token).get('/api/chat'); 23 | return data; 24 | } catch (error) { 25 | console.log('error in fetch all chats api'); 26 | } 27 | }; 28 | export const createGroup = async (body) => { 29 | try { 30 | const token = localStorage.getItem('userToken'); 31 | const { data } = await API(token).post('/api/chat/group', body); 32 | toast.success(`${data.chatName} Group Created`); 33 | return data; 34 | } catch (error) { 35 | console.log('error in create group api'); 36 | } 37 | }; 38 | export const addToGroup = async (body) => { 39 | try { 40 | const token = localStorage.getItem('userToken'); 41 | const { data } = await API(token).patch('/api/chat/groupAdd', body); 42 | return data; 43 | } catch (error) { 44 | console.log('error in addtogroup api'); 45 | } 46 | }; 47 | export const renameGroup = async (body) => { 48 | try { 49 | const token = localStorage.getItem('userToken'); 50 | const { data } = await API(token).patch('/api/chat/group/rename', body); 51 | return data; 52 | } catch (error) { 53 | console.log('error in rename group api'); 54 | } 55 | }; 56 | export const removeUser = async (body) => { 57 | try { 58 | const token = localStorage.getItem('userToken'); 59 | const { data } = await API(token).patch('/api/chat/groupRemove', body); 60 | return data; 61 | } catch (error) { 62 | console.log('error in remove user api'); 63 | } 64 | }; 65 | -------------------------------------------------------------------------------- /clients/src/apis/messages.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | const API = (token) => 3 | axios.create({ 4 | baseURL: process.env.REACT_APP_SERVER_URL, 5 | headers: { Authorization: token }, 6 | }); 7 | export const sendMessage = async (body) => { 8 | try { 9 | const token = localStorage.getItem('userToken'); 10 | const { data } = await API(token).post('/api/message/', body); 11 | return data; 12 | } catch (error) { 13 | console.log('error in sendmessage api' + error); 14 | } 15 | }; 16 | export const fetchMessages = async (id) => { 17 | try { 18 | const token = localStorage.getItem('userToken'); 19 | 20 | const { data } = await API(token).get(`/api/message/${id}`); 21 | return data; 22 | } catch (error) { 23 | console.log('error in fetch Message API ' + error); 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /clients/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sahandmohammadrehzaii/Realtime-Chat/c6a2524907b8e5cf63356c2144af41e51398461b/clients/src/assets/logo.png -------------------------------------------------------------------------------- /clients/src/assets/no-contacts.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sahandmohammadrehzaii/Realtime-Chat/c6a2524907b8e5cf63356c2144af41e51398461b/clients/src/assets/no-contacts.jpg -------------------------------------------------------------------------------- /clients/src/components/Contacts.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { useSelector, useDispatch } from 'react-redux' 3 | import { setActiveChat, fetchChats } from '../redux/chatsSlice' 4 | import { useEffect } from 'react' 5 | import { getChatName, getChatPhoto, timeSince } from '../utils/logics' 6 | import NoContacts from './ui/NoContacts' 7 | // import SkeletonLoading from './ui/SkeletonLoading' 8 | var aDay = 24 * 60 * 60 * 1000; 9 | function Contacts() { 10 | const { chats, activeChat } = useSelector((state) => state.chats) 11 | const dispatch = useDispatch() 12 | const activeUser = useSelector((state) => state.activeUser) 13 | useEffect(() => { 14 | dispatch(fetchChats()) 15 | }, [dispatch]) 16 | return ( 17 | <> 18 |
19 | { 20 | chats?.length > 0 ? chats?.map((e) => { 21 | return ( 22 |
{ 23 | dispatch(setActiveChat(e)) 24 | }} key={e._id} className={`flex items-center justify-between sm:gap-x-1 md:gap-x-1 mt-5 ${activeChat._id === e._id ? "bg-[#fafafa]" : "bg-[#fff]"} cursor-pointer py-4 px-2`}> 25 |
26 | 27 |
28 |
{getChatName(e, activeUser)}
29 |

{e.latestMessage?.message.length > 30 30 | ? e.latestMessage?.message.slice(0, 30) + "..." 31 | : e.latestMessage?.message 32 | }

33 |
34 |
35 |
36 |

{timeSince(new Date(Date.parse(e.updatedAt) - aDay))}

37 |
38 |
39 | ) 40 | }) : 41 | } 42 |
43 | 44 | 45 | ) 46 | } 47 | 48 | export default Contacts -------------------------------------------------------------------------------- /clients/src/components/Group.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import { BsPlusLg } from "react-icons/bs" 3 | import { Modal, Box } from "@mui/material" 4 | import { searchUsers } from '../apis/auth'; 5 | import { RxCross2 } from "react-icons/rx" 6 | import { useEffect } from 'react'; 7 | import { createGroup } from '../apis/chat'; 8 | import { fetchChats } from '../redux/chatsSlice'; 9 | import { useDispatch } from 'react-redux'; 10 | import Search from './group/Search'; 11 | 12 | const style = { 13 | position: 'absolute', 14 | top: '50%', 15 | left: '50%', 16 | transform: 'translate(-50%, -50%)', 17 | minWidth: 400, 18 | bgcolor: 'background.paper', 19 | 20 | boxShadow: 24, 21 | p: 2 22 | 23 | }; 24 | function Group() { 25 | const dispatch = useDispatch() 26 | const [open, setOpen] = useState(false); 27 | const [chatName, setChatName] = useState("") 28 | const [searchResults, setSearchResults] = useState([]) 29 | const [search, setSearch] = useState("") 30 | const [isLoading, setIsLoading] = useState(false) 31 | const [selectedUser, setSelectedUsers] = useState([]) 32 | const handleOpen = () => setOpen(true); 33 | const handleClose = () => { 34 | setOpen(false) 35 | setSearch("") 36 | setSelectedUsers([]) 37 | } 38 | const handleFormSearch = async (e) => { 39 | setSearch(e.target.value) 40 | } 41 | const handleClick = (e) => { 42 | if (selectedUser.includes(e)) { 43 | return 44 | } 45 | setSelectedUsers([...selectedUser, e]) 46 | } 47 | 48 | const deleteSelected = (ele) => { 49 | setSelectedUsers(selectedUser.filter((e) => e._id !== ele._id)) 50 | } 51 | const handleSubmit = async () => { 52 | if (selectedUser.length >= 2) { 53 | 54 | await createGroup({ 55 | chatName, 56 | users: JSON.stringify(selectedUser.map((e) => e._id)) 57 | }) 58 | dispatch(fetchChats()) 59 | handleClose() 60 | } 61 | } 62 | useEffect(() => { 63 | const searchChange = async () => { 64 | setIsLoading(true) 65 | const { data } = await searchUsers(search) 66 | setSearchResults(data) 67 | setIsLoading(false) 68 | } 69 | searchChange() 70 | }, [search]) 71 | useEffect(() => { 72 | }, []) 73 | return ( 74 | <> 75 | 79 | 80 | 81 | 87 | 88 |
Create A Group
89 | 90 |
e.preventDefault()} className='flex flex-col gap-y-3 mt-3'> 91 | 92 | setChatName(e.target.value)} className="border-[#c4ccd5] border-[1px] text-[13.5px] py-[4px] px-2 w-[100%]" type="text" name="chatName" placeholder="Group Name" required /> 93 | 94 |
95 | 96 | { 97 | selectedUser?.map((e) => { 98 | return ( 99 | 103 | ) 104 | }) 105 | } 106 |
107 | 108 | 109 | 110 | 111 |
112 | 113 |
114 | 115 | 116 | 117 |
118 |
119 | 120 | ) 121 | } 122 | 123 | export default Group -------------------------------------------------------------------------------- /clients/src/components/MessageHistory.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { useSelector } from 'react-redux' 3 | import ScrollableFeed from "react-scrollable-feed" 4 | import { isSameSender, isSameSenderMargin, isSameUser, isLastMessage } from '../utils/logics' 5 | import { Tooltip } from "@chakra-ui/tooltip"; 6 | import { Avatar } from "@chakra-ui/avatar"; 7 | import "../pages/home.css" 8 | function MessageHistory({ messages }) { 9 | const activeUser = useSelector((state) => state.activeUser) 10 | 11 | return ( 12 | <> 13 | 14 | {messages && 15 | messages.map((m, i) => ( 16 | 17 |
18 | {(isSameSender(messages, m, i, activeUser.id) || 19 | isLastMessage(messages, i, activeUser.id)) && ( 20 | 21 | 31 | 32 | 33 | )} 34 | 46 | {m.message} 47 | 48 |
49 | )) 50 | } 51 | 52 |
53 | 54 | ) 55 | } 56 | 57 | export default MessageHistory -------------------------------------------------------------------------------- /clients/src/components/Model.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import Box from '@mui/material/Box'; 3 | import Modal from '@mui/material/Modal'; 4 | import { useDispatch, useSelector } from 'react-redux'; 5 | import { RxCross2 } from "react-icons/rx" 6 | import { useEffect } from 'react'; 7 | import { searchUsers } from '../apis/auth'; 8 | import { addToGroup, removeUser, renameGroup } from '../apis/chat'; 9 | import { fetchChats } from '../redux/chatsSlice'; 10 | import Search from './group/Search'; 11 | import { getChatName, getChatPhoto } from '../utils/logics'; 12 | const style = { 13 | position: 'absolute', 14 | top: '50%', 15 | left: '50%', 16 | transform: 'translate(-50%, -50%)', 17 | width: "fit-content", 18 | bgcolor: 'background.paper', 19 | boxShadow: 24, 20 | p: 2, 21 | }; 22 | function Model(props) { 23 | const [open, setOpen] = useState(false); 24 | const dispatch = useDispatch() 25 | const [searchResults, setSearchResults] = useState([]) 26 | const [name, setName] = useState("") 27 | const [search, setSearch] = useState("") 28 | const [isLoading, setIsLoading] = useState(false) 29 | const [members, setMembers] = useState([]) 30 | const { activeChat } = useSelector((state) => state.chats) 31 | const activeUser = useSelector((state) => state.activeUser) 32 | 33 | const handleOpen = () => { 34 | setOpen(true); 35 | setName(getChatName(activeChat, activeUser)) 36 | }; 37 | const handleClose = () => { 38 | setOpen(false); 39 | setSearch("") 40 | setSearchResults([]) 41 | }; 42 | const handleClick = async (e) => { 43 | if (members.includes(e)) { 44 | return 45 | } 46 | await addToGroup({ userId: e?._id, chatId: activeChat?._id }) 47 | setMembers([...members, e]) 48 | 49 | } 50 | 51 | const updateBtn = async () => { 52 | if (name) { 53 | let data = await renameGroup({ chatId: activeChat._id, chatName: name }) 54 | if (data) { 55 | dispatch(fetchChats()) 56 | setOpen(false) 57 | } 58 | } 59 | setOpen(false) 60 | } 61 | const deleteSelected = async (ele) => { 62 | const res = await removeUser({ chatId: activeChat._id, userId: ele._id }) 63 | if (res._id) { 64 | setMembers(members.filter((e) => e._id !== ele._id)) 65 | 66 | dispatch(fetchChats()) 67 | setOpen(false) 68 | 69 | } 70 | return 71 | } 72 | const leaveGroup = async () => { 73 | const res = await removeUser({ chatId: activeChat._id, userId: activeUser.id }) 74 | if (res._id) { 75 | dispatch(fetchChats()) 76 | setOpen(false) 77 | } 78 | return 79 | } 80 | useEffect(() => { 81 | setMembers(activeChat?.users.map((e) => e)) 82 | }, [activeChat]) 83 | useEffect(() => { 84 | const searchChange = async () => { 85 | setIsLoading(true) 86 | const { data } = await searchUsers(search) 87 | setSearchResults(data) 88 | setIsLoading(false) 89 | } 90 | searchChange() 91 | }, [search]) 92 | return ( 93 | <> 94 | 95 | 96 | 97 | 98 | 102 | { 103 | activeChat?.isGroup ? 104 | 105 | 111 | 112 |
{getChatName(activeChat, activeUser)}
113 |
114 |
Members
115 |
116 | { 117 | members.length > 0 && members?.map((e) => { 118 | return ( 119 | 123 | ) 124 | }) 125 | } 126 |
127 |
128 |
e.preventDefault()}> 129 | setName(e.target.value)} value={name} className="border-[#c4ccd5] border-[1px] text-[13.5px] py-[4px] px-2 w-[100%]" type="text" name="chatName" placeholder="Group Name" required /> 130 | setSearch(e.target.value)} className="border-[#c4ccd5] border-[1px] text-[13.5px] py-[4px] px-2 w-[100%]" type="text" name="users" placeholder="add users" /> 131 |
132 | {/*
*/} 133 | 134 | 135 | 136 |
137 | 138 | 139 | 140 |
141 |
142 |
143 | 144 | : 150 | 151 |
152 | 153 |

{getChatName(activeChat, activeUser)}

154 | 155 |

{!activeChat?.isGroup && activeChat?.users[0]?._id === activeUser.id ? activeChat?.users[1]?.email : activeChat?.users[0]?.email}

156 |
157 | 158 |
{!activeChat?.isGroup && activeChat?.users[0]?._id === activeUser.id ? activeChat?.users[1]?.bio : activeChat?.users[0]?.bio}
159 |
160 |
161 | 162 | 163 |
164 |
165 | } 166 | 167 | 168 | 169 | 170 | 171 | ) 172 | } 173 | 174 | export default Model -------------------------------------------------------------------------------- /clients/src/components/Profile.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import { IoArrowBack } from "react-icons/io5" 3 | import { useDispatch, useSelector } from 'react-redux' 4 | import { setShowProfile } from '../redux/profileSlice' 5 | import { IoMdLogOut } from "react-icons/io" 6 | import InputEdit from './profile/InputEdit' 7 | import { updateUser } from '../apis/auth' 8 | import { toast } from 'react-toastify' 9 | import { setUserNameAndBio } from '../redux/activeUserSlice' 10 | function Profile(props) { 11 | const dispatch = useDispatch() 12 | const { showProfile } = useSelector((state) => state.profile) 13 | const activeUser = useSelector((state) => state.activeUser) 14 | const [formData, setFormData] = useState({ 15 | name: activeUser.name, 16 | bio: activeUser.bio 17 | }) 18 | const logoutUser = () => { 19 | toast.success("Logout Successfull!") 20 | localStorage.removeItem("userToken") 21 | window.location.href = "/login" 22 | } 23 | const handleChange = (e) => { 24 | setFormData({ ...formData, [e.target.name]: e.target.value }) 25 | } 26 | const submit = async () => { 27 | 28 | dispatch(setUserNameAndBio(formData)) 29 | toast.success("Updated!") 30 | await updateUser(activeUser.id, formData) 31 | 32 | } 33 | 34 | return ( 35 | 36 |
37 |
38 |
39 | 43 |
44 |
45 |
46 | 47 |
48 | 49 | 50 |
51 | 52 |
53 |

54 | This is not your username or pin. This name will be visible to your contacts 55 |

56 |
57 | 58 |
59 | 60 |
61 | 62 |
63 | 64 |
Logout
65 |
66 |
67 |
68 | ) 69 | } 70 | 71 | export default Profile -------------------------------------------------------------------------------- /clients/src/components/Start.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react' 2 | import { validUser } from '../apis/auth' 3 | import { useNavigate } from "react-router-dom" 4 | function Start() { 5 | const pageRoute = useNavigate() 6 | useEffect(() => { 7 | const isValid = async () => { 8 | const data = await validUser() 9 | if (!data?.user) { 10 | pageRoute("/login") 11 | } 12 | else { 13 | pageRoute("/chats") 14 | 15 | } 16 | } 17 | isValid() 18 | 19 | }, [pageRoute]) 20 | return ( 21 | 22 | 23 |
24 | 25 |

Please Wait. It Might take some time

26 |
27 | 28 | ) 29 | } 30 | 31 | export default Start -------------------------------------------------------------------------------- /clients/src/components/group/Search.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import SkeletonLoading from '../ui/SkeletonLoading' 3 | import "../../pages/home.css" 4 | function Search({ type, isLoading, searchResults, handleClick, search }) { 5 | 6 | return ( 7 |
8 | 9 | { 10 | isLoading ? : ( 11 | searchResults.length > 0 ? searchResults?.map((e) => { 12 | return ( 13 |
14 |
15 | 16 | 17 |
18 |
{e.name}
19 |
{e.email}
20 |
21 |
22 | 23 |
24 | ) 25 | }) : No results found 26 | ) 27 | 28 | } 29 |
30 | ) 31 | } 32 | 33 | export default Search -------------------------------------------------------------------------------- /clients/src/components/profile/InputEdit.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import { TbEdit } from "react-icons/tb" 3 | import { BsCheck2 } from "react-icons/bs" 4 | function InputEdit({ type, handleChange, input, handleSubmit }) { 5 | const [editable, setEditable] = useState(false) 6 | // const [showPicker, setShowPicker] = useState(false) 7 | const submitButton = () => { 8 | handleSubmit() 9 | setEditable(false) 10 | } 11 | return ( 12 | <> 13 |
14 |

Your name

15 | { 16 | !editable ? 17 | 18 |
19 | 20 |

21 | 22 | {input} 23 |

24 | 25 | 28 |
29 | 30 | :
31 | 32 |
33 | 34 |
35 |
36 | 37 | 38 | 41 |
42 |
43 | 44 | } 45 | 46 | 47 |
48 | 49 | ) 50 | } 51 | 52 | export default InputEdit -------------------------------------------------------------------------------- /clients/src/components/ui/Loading.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | function Loading() { 4 | return ( 5 |
6 | 7 |
8 | ) 9 | } 10 | 11 | export default Loading -------------------------------------------------------------------------------- /clients/src/components/ui/NoContacts.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import nocontacts from "../../assets/no-contacts.jpg" 3 | function NoContacts() { 4 | return ( 5 |
6 | No Contacts 7 |

No Contacts Yet

8 | Search for people 9 |
10 | ) 11 | } 12 | 13 | export default NoContacts -------------------------------------------------------------------------------- /clients/src/components/ui/SkeletonLoading.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Skeleton from 'react-loading-skeleton' 3 | import 'react-loading-skeleton/dist/skeleton.css' 4 | 5 | function SkeletonLoading({ height, count }) { 6 | return ( 7 |
8 | 9 |
10 | ) 11 | } 12 | 13 | export default SkeletonLoading -------------------------------------------------------------------------------- /clients/src/components/ui/Typing.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | function Typing({ className, width, height }) { 4 | return ( 5 |
6 | 7 |
8 | ) 9 | } 10 | 11 | export default Typing -------------------------------------------------------------------------------- /clients/src/index.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /clients/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom/client'; 3 | import App from './App'; 4 | import './index.css'; 5 | import reportWebVitals from './reportWebVitals'; 6 | import { Provider } from 'react-redux'; 7 | import store from './store'; 8 | import { ToastContainer } from 'react-toastify'; 9 | import 'react-toastify/dist/ReactToastify.css'; 10 | const root = ReactDOM.createRoot(document.getElementById('root')); 11 | root.render( 12 | 13 | 14 | 15 | 16 | 17 | 18 | ); 19 | 20 | // If you want to start measuring performance in your app, pass a function 21 | // to log results (for example: reportWebVitals(console.log)) 22 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 23 | reportWebVitals(); 24 | -------------------------------------------------------------------------------- /clients/src/pages/Chat.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import { useDispatch, useSelector } from 'react-redux' 3 | import Model from '../components/Model'; 4 | import { BsEmojiSmile, BsFillEmojiSmileFill } from "react-icons/bs" 5 | import { fetchMessages, sendMessage } from '../apis/messages'; 6 | import { useEffect } from 'react'; 7 | import MessageHistory from '../components/MessageHistory'; 8 | import io from "socket.io-client" 9 | import "./home.css" 10 | import { fetchChats, setNotifications } from '../redux/chatsSlice'; 11 | import Loading from '../components/ui/Loading'; 12 | import data from '@emoji-mart/data' 13 | import Picker from '@emoji-mart/react' 14 | import { getChatName } from '../utils/logics'; 15 | import Typing from '../components/ui/Typing'; 16 | import { validUser } from '../apis/auth'; 17 | const ENDPOINT = process.env.REACT_APP_SERVER_URL 18 | let socket, selectedChatCompare; 19 | 20 | function Chat(props) { 21 | const { activeChat, notifications } = useSelector((state) => state.chats) 22 | const dispatch = useDispatch() 23 | const [message, setMessage] = useState("") 24 | const [messages, setMessages] = useState([]) 25 | const [socketConnected, setSocketConnected] = useState(false) 26 | const [typing, setTyping] = useState(false) 27 | const [isTyping, setIsTyping] = useState(false) 28 | const [loading, setLoading] = useState(false) 29 | const [showPicker, setShowPicker] = useState(false); 30 | const activeUser = useSelector((state) => state.activeUser) 31 | 32 | const keyDownFunction = async (e) => { 33 | if ((e.key === "Enter" || e.type === "click") && (message)) { 34 | setMessage("") 35 | socket.emit("stop typing", activeChat._id) 36 | const data = await sendMessage({ chatId: activeChat._id, message }) 37 | socket.emit("new message", data) 38 | setMessages([...messages, data]) 39 | dispatch(fetchChats()) 40 | } 41 | } 42 | 43 | 44 | useEffect(() => { 45 | socket = io(ENDPOINT) 46 | socket.on("typing", () => setIsTyping(true)) 47 | socket.on("stop typing", () => setIsTyping(false)) 48 | }, []) 49 | 50 | useEffect(() => { 51 | socket.emit("setup", activeUser) 52 | socket.on("connected", () => { 53 | setSocketConnected(true) 54 | }) 55 | }, [messages, activeUser]) 56 | useEffect(() => { 57 | const fetchMessagesFunc = async () => { 58 | if (activeChat) { 59 | setLoading(true) 60 | const data = await fetchMessages(activeChat._id) 61 | setMessages(data) 62 | socket.emit("join room", activeChat._id) 63 | setLoading(false) 64 | 65 | } 66 | return 67 | } 68 | fetchMessagesFunc() 69 | selectedChatCompare = activeChat 70 | 71 | }, [activeChat]) 72 | useEffect(() => { 73 | socket.on("message recieved", (newMessageRecieved) => { 74 | if ((!selectedChatCompare || selectedChatCompare._id) !== newMessageRecieved.chatId._id) { 75 | if (!notifications.includes(newMessageRecieved)) { 76 | dispatch(setNotifications([newMessageRecieved, ...notifications])) 77 | } 78 | } 79 | else { 80 | setMessages([...messages, newMessageRecieved]) 81 | } 82 | dispatch(fetchChats()) 83 | }) 84 | }) 85 | useEffect(() => { 86 | const isValid = async () => { 87 | const data = await validUser() 88 | if (!data?.user) { 89 | window.location.href = "/login" 90 | } 91 | 92 | } 93 | isValid() 94 | }, []) 95 | if (loading) { 96 | return
97 | 98 |
99 | } 100 | return ( 101 | <> 102 | { 103 | activeChat ? 104 |
105 |
106 |
107 |
108 |
{getChatName(activeChat, activeUser)}
109 | {/*

Last seen 5 min ago

*/} 110 |
111 |
112 |
113 | 114 |
115 |
116 |
117 | 118 |
119 | { 120 | isTyping ? 121 | : "" 122 | } 123 | 124 |
125 |
126 |
127 | { 128 | showPicker && setMessage(message + e.native)} /> 129 | } 130 |
131 | 132 |
keyDownFunction(e)} onSubmit={(e) => e.preventDefault()}> 133 | { 134 | setMessage(e.target.value) 135 | if (!socketConnected) return 136 | if (!typing) { 137 | setTyping(true) 138 | socket.emit('typing', activeChat._id) 139 | } 140 | let lastTime = new Date().getTime() 141 | var time = 3000 142 | setTimeout(() => { 143 | var timeNow = new Date().getTime() 144 | var timeDiff = timeNow - lastTime 145 | if (timeDiff >= time && typing) { 146 | socket.emit("stop typing", activeChat._id) 147 | setTyping(false) 148 | } 149 | }, time) 150 | }} className='focus:outline-0 w-[100%] bg-[#f8f9fa]' type="text" name="message" placeholder="Enter message" value={message} /> 151 |
152 | 153 |
154 | 155 |
156 | {/* { 157 | isTyping ?
Loading
: "" 158 | } */} 159 |
160 | 161 |
setShowPicker(!showPicker)}> 162 | 163 | {showPicker ? : } 164 |
165 | 166 |
167 |
168 |
169 |
: 170 |
171 |
172 |
173 | User profile 174 |

Welcome {activeUser.name}

175 |
176 |
177 |
178 | 179 | } 180 | 181 | ) 182 | } 183 | 184 | export default Chat -------------------------------------------------------------------------------- /clients/src/pages/Home.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import { useEffect } from 'react' 3 | import { useDispatch, useSelector } from 'react-redux' 4 | import { searchUsers, validUser } from '../apis/auth' 5 | import { setActiveUser } from '../redux/activeUserSlice' 6 | import { RiNotificationBadgeFill } from "react-icons/ri" 7 | import { BsSearch } from "react-icons/bs" 8 | import { BiNotification } from "react-icons/bi" 9 | import { IoIosArrowDown } from "react-icons/io" 10 | import { setShowNotifications, setShowProfile } from '../redux/profileSlice' 11 | import Chat from './Chat' 12 | import Profile from "../components/Profile" 13 | import { acessCreate } from "../apis/chat.js" 14 | import "./home.css" 15 | import { fetchChats, setNotifications } from '../redux/chatsSlice' 16 | import { getSender } from '../utils/logics' 17 | import { setActiveChat } from '../redux/chatsSlice' 18 | import Group from '../components/Group' 19 | import Contacts from '../components/Contacts' 20 | import { Effect } from "react-notification-badge" 21 | // import NotificationBadge from 'react-notification-badge/lib/components/NotificationBadge'; 22 | import NotificationBadge from 'react-notification-badge'; 23 | import Search from '../components/group/Search' 24 | function Home() { 25 | const dispatch = useDispatch() 26 | const { showProfile, showNotifications } = useSelector((state) => state.profile) 27 | const { notifications } = useSelector((state) => state.chats) 28 | const { activeUser } = useSelector((state) => state) 29 | const [searchResults, setSearchResults] = useState([]) 30 | const [isLoading, setIsLoading] = useState(false) 31 | const [search, setSearch] = useState("") 32 | 33 | const handleSearch = async (e) => { 34 | setSearch(e.target.value) 35 | } 36 | const handleClick = async (e) => { 37 | await acessCreate({ userId: e._id }) 38 | dispatch(fetchChats()) 39 | setSearch("") 40 | } 41 | useEffect(() => { 42 | const searchChange = async () => { 43 | setIsLoading(true) 44 | const { data } = await searchUsers(search) 45 | setSearchResults(data) 46 | setIsLoading(false) 47 | } 48 | searchChange() 49 | }, [search]) 50 | useEffect(() => { 51 | const isValid = async () => { 52 | const data = await validUser() 53 | 54 | const user = { 55 | id: data?.user?._id, 56 | email: data?.user?.email, 57 | profilePic: data?.user?.profilePic, 58 | bio: data?.user?.bio, 59 | name: data?.user?.name 60 | } 61 | dispatch(setActiveUser(user)) 62 | } 63 | isValid() 64 | 65 | }, [dispatch, activeUser]) 66 | 67 | 68 | return ( 69 | <> 70 | 71 |
72 | 73 |
74 | { 75 | !showProfile ? 76 |
77 | 78 |
79 | 85 |
86 | 97 |
98 |
99 | 100 | {!notifications.length && "No new messages"} 101 |
102 | { 103 | notifications.map((e, index) => { 104 | return ( 105 |
{ 106 | dispatch(setActiveChat(e.chatId)) 107 | dispatch(setNotifications(notifications.filter((data) => data !== e))) 108 | 109 | }} key={index} className='text-[12.5px] text-black px-2 cursor-pointer' > 110 | 111 | {e.chatId.isGroup ? `New Message in ${e.chatId.chatName}` : `New Message from ${getSender(activeUser, e.chatId.users)}`} 112 |
113 | 114 | ) 115 | 116 | }) 117 | } 118 |
119 | 123 |
124 |
125 | 126 |
127 | 128 |
129 |
e.preventDefault()}> 130 | 131 | 132 | 133 |
134 | 135 |
136 | 137 |
138 | 139 | 140 |
141 | 142 | 143 |
144 |
145 | 146 | 147 | 148 | 149 | 150 |
151 | 152 | 153 |
: 154 | } 155 | 156 | 157 | 158 | 159 | 160 |
161 |
162 | 163 | 164 | ) 165 | } 166 | 167 | export default Home -------------------------------------------------------------------------------- /clients/src/pages/Login.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { useEffect } from 'react' 3 | import { GoogleLogin } from "react-google-login" 4 | import { gapi } from "gapi-script" 5 | import { googleAuth } from '../apis/auth' 6 | import { useState } from 'react' 7 | import { loginUser } from '../apis/auth' 8 | import { Link, useNavigate } from 'react-router-dom' 9 | import { BsEmojiLaughing, BsEmojiExpressionless } from "react-icons/bs" 10 | import { toast } from 'react-toastify'; 11 | import { validUser } from '../apis/auth' 12 | const defaultData = { 13 | email: "", 14 | password: "" 15 | } 16 | function Login() { 17 | const [formData, setFormData] = useState(defaultData) 18 | const [isLoading, setIsLoading] = useState(false) 19 | const [showPass, setShowPass] = useState(false) 20 | const pageRoute = useNavigate() 21 | const googleSuccess = async (res) => { 22 | if (res?.profileObj) { 23 | console.log(res.profileObj) 24 | setIsLoading(true) 25 | const response = await googleAuth({ tokenId: res.tokenId }) 26 | setIsLoading(false) 27 | 28 | console.log("response :" + res) 29 | if (response.data.token) { 30 | localStorage.setItem("userToken", response.data.token) 31 | pageRoute("/chats") 32 | 33 | } 34 | } 35 | } 36 | const googleFailure = (error) => { 37 | // toast.error("Something went Wrong.Try Again!") 38 | } 39 | const handleOnChange = (e) => { 40 | setFormData({ ...formData, [e.target.name]: e.target.value }) 41 | } 42 | 43 | const formSubmit = async (e) => { 44 | e.preventDefault() 45 | if (formData.email.includes("@") && formData.password.length > 6) { 46 | setIsLoading(true) 47 | const { data } = await loginUser(formData) 48 | if (data?.token) { 49 | localStorage.setItem("userToken", data.token) 50 | toast.success("Succesfully Login!") 51 | setIsLoading(false) 52 | pageRoute("/chats") 53 | } 54 | else { 55 | setIsLoading(false) 56 | toast.error("Invalid Credentials!") 57 | setFormData({ ...formData, password: "" }) 58 | } 59 | } 60 | else { 61 | setIsLoading(false) 62 | toast.warning("Provide valid Credentials!") 63 | setFormData(defaultData) 64 | 65 | } 66 | } 67 | useEffect(() => { 68 | const initClient = () => { 69 | gapi.client.init({ 70 | clientId: process.env.REACT_APP_CLIENT_ID, 71 | scope: '' 72 | }); 73 | }; 74 | gapi.load('client:auth2', initClient); 75 | const isValid = async () => { 76 | const data = await validUser() 77 | if (data?.user) { 78 | window.location.href = "/chats" 79 | } 80 | 81 | } 82 | isValid() 83 | }, []) 84 | return ( 85 | <> 86 | 87 |
88 |
89 | {/* */} 90 |
91 |

Login

92 |

No Account ? Sign up

93 |
94 | {/*

Login to your Account

*/} 95 |
96 |
97 | 98 | 99 |
100 |
101 | 102 | 103 | { 104 | !showPass ? : 105 | } 106 |
107 | 108 | 115 | {/*
*/} 116 |

/

117 | ( 120 | 124 | )} 125 | onSuccess={googleSuccess} 126 | onFailure={googleFailure} 127 | cookiePolicy={'single_host_origin'} 128 | scope="profile email https://www.googleapis.com/auth/user.birthday.read" 129 | /> 130 | 131 | 132 | 133 |
134 | 135 |
136 | 137 | ) 138 | } 139 | 140 | export default Login -------------------------------------------------------------------------------- /clients/src/pages/Regsiter.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Link, useNavigate } from 'react-router-dom' 3 | import { GoogleLogin } from "react-google-login" 4 | import { gapi } from "gapi-script" 5 | import { useEffect } from 'react' 6 | import { googleAuth, registerUser } from '../apis/auth' 7 | import { useState } from 'react' 8 | import { BsEmojiLaughing, BsEmojiExpressionless } from "react-icons/bs" 9 | import { toast } from 'react-toastify'; 10 | import { validUser } from '../apis/auth' 11 | const defaultData = { 12 | firstname: "", 13 | lastname: "", 14 | email: "", 15 | password: "" 16 | } 17 | function Regsiter() { 18 | const [formData, setFormData] = useState(defaultData) 19 | const [isLoading, setIsLoading] = useState(false) 20 | const [showPass, setShowPass] = useState(false) 21 | const pageRoute = useNavigate() 22 | const handleOnChange = (e) => { 23 | setFormData({ ...formData, [e.target.name]: e.target.value }) 24 | } 25 | const handleOnSubmit = async (e) => { 26 | e.preventDefault() 27 | setIsLoading(true) 28 | if (formData.email.includes("@") && formData.password.length > 6) { 29 | const { data } = await registerUser(formData) 30 | if (data?.token) { 31 | localStorage.setItem("userToken", data.token) 32 | toast.success("Succesfully Registered😍") 33 | setIsLoading(false) 34 | pageRoute("/chats") 35 | 36 | } 37 | else { 38 | setIsLoading(false) 39 | toast.error("Invalid Credentials!") 40 | } 41 | } 42 | else { 43 | setIsLoading(false) 44 | toast.warning("Provide valid Credentials!") 45 | setFormData({ ...formData, password: "" }) 46 | } 47 | 48 | } 49 | 50 | const googleSuccess = async (res) => { 51 | if (res?.profileObj) { 52 | setIsLoading(true) 53 | const response = await googleAuth({ tokenId: res.tokenId }) 54 | setIsLoading(false) 55 | if (response.data.token) { 56 | localStorage.setItem("userToken", response.data.token) 57 | pageRoute("/chats") 58 | } 59 | } 60 | } 61 | const googleFailure = (error) => { 62 | toast.error("Something Went Wrong.Try Agian!") 63 | } 64 | 65 | useEffect(() => { 66 | const initClient = () => { 67 | gapi.client.init({ 68 | clientId: process.env.REACT_APP_CLIENT_ID, 69 | scope: '' 70 | }); 71 | }; 72 | gapi.load('client:auth2', initClient); 73 | const isValid = async () => { 74 | const data = await validUser() 75 | if (data?.user) { 76 | window.location.href = "/chats" 77 | } 78 | } 79 | isValid() 80 | }, []) 81 | return ( 82 |
83 |
84 |
85 |

Register

86 |

Have Account ? Sign in

87 |
88 |
89 |
90 | 91 | 92 |
93 |
94 | 95 |
96 |
97 | 98 | 99 | 100 | {/* */} 103 | { 104 | !showPass ? : 105 | } 106 | 107 | 108 |
109 | 116 |

/

117 | ( 120 | 124 | )} 125 | onSuccess={googleSuccess} 126 | onFailure={googleFailure} 127 | cookiePolicy={'single_host_origin'} 128 | /> 129 | 130 |
131 |
132 | ) 133 | } 134 | 135 | export default Regsiter -------------------------------------------------------------------------------- /clients/src/pages/home.css: -------------------------------------------------------------------------------- 1 | .alert-enter { 2 | opacity: 0; 3 | transform: scale(0.9); 4 | } 5 | .alert-enter-active { 6 | opacity: 1; 7 | transform: translateX(0); 8 | transition: opacity 300ms, transform 300ms; 9 | } 10 | .alert-exit { 11 | opacity: 1; 12 | } 13 | .alert-exit-active { 14 | opacity: 0; 15 | transform: scale(0.9); 16 | transition: opacity 300ms, transform 300ms; 17 | } 18 | .scrollbar-hide::-webkit-scrollbar { 19 | display: none; 20 | } 21 | @media screen and (max-width: 1023px) { 22 | .chat-page { 23 | min-width: 600px; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /clients/src/redux/activeUserSlice.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from '@reduxjs/toolkit'; 2 | 3 | const initialState = { 4 | id: '', 5 | email: '', 6 | profilePic: '', 7 | bio: '', 8 | name: '', 9 | }; 10 | 11 | const activeUserSlice = createSlice({ 12 | name: 'activeUser', 13 | initialState, 14 | reducers: { 15 | setActiveUser: (state, { payload }) => { 16 | state.id = payload.id; 17 | state.email = payload.email; 18 | state.profilePic = payload.profilePic; 19 | state.bio = payload.bio; 20 | state.name = payload.name; 21 | }, 22 | setUserNameAndBio: (state, { payload }) => { 23 | state.name = payload.name; 24 | state.bio = payload.bio; 25 | }, 26 | }, 27 | }); 28 | export const { setActiveUser, setUserNameAndBio } = activeUserSlice.actions; 29 | export default activeUserSlice.reducer; 30 | -------------------------------------------------------------------------------- /clients/src/redux/chatsSlice.js: -------------------------------------------------------------------------------- 1 | import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'; 2 | import { toast } from 'react-toastify'; 3 | import { fetchAllChats } from '../apis/chat'; 4 | const initialState = { 5 | chats: [], 6 | activeChat: '', 7 | isLoading: false, 8 | notifications: [], 9 | }; 10 | export const fetchChats = createAsyncThunk('redux/chats', async () => { 11 | try { 12 | const data = await fetchAllChats(); 13 | return data; 14 | } catch (error) { 15 | toast.error('Something Went Wrong!Try Again'); 16 | } 17 | }); 18 | const chatsSlice = createSlice({ 19 | name: 'chats', 20 | initialState, 21 | reducers: { 22 | setActiveChat: (state, { payload }) => { 23 | state.activeChat = payload; 24 | }, 25 | setNotifications: (state, { payload }) => { 26 | state.notifications = payload; 27 | }, 28 | }, 29 | extraReducers: { 30 | [fetchChats.pending]: (state) => { 31 | state.isLoading = true; 32 | }, 33 | [fetchChats.fulfilled]: (state, { payload }) => { 34 | state.chats = payload; 35 | state.isLoading = false; 36 | }, 37 | [fetchChats.rejected]: (state) => { 38 | state.isLoading = false; 39 | }, 40 | }, 41 | }); 42 | export const { setActiveChat, setNotifications } = chatsSlice.actions; 43 | export default chatsSlice.reducer; 44 | -------------------------------------------------------------------------------- /clients/src/redux/profileSlice.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from "@reduxjs/toolkit"; 2 | const initialState = { 3 | showProfile: false, 4 | showNotifications: false, 5 | }; 6 | const profileSlice = createSlice({ 7 | name: "profile", 8 | initialState, 9 | reducers: { 10 | setShowProfile: (state, { payload }) => { 11 | state.showProfile = payload; 12 | }, 13 | setShowNotifications: (state, { payload }) => { 14 | state.showNotifications = payload; 15 | }, 16 | }, 17 | }); 18 | export const { setShowProfile, setShowNotifications } = profileSlice.actions; 19 | export default profileSlice.reducer; 20 | -------------------------------------------------------------------------------- /clients/src/redux/searchSlice.js: -------------------------------------------------------------------------------- 1 | import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'; 2 | import { toast } from 'react-toastify'; 3 | import { searchUsers } from '../apis/auth'; 4 | const initialState = { 5 | searchResults: [], 6 | isLoading: false, 7 | isError: false, 8 | }; 9 | export const searchUserThunk = createAsyncThunk( 10 | 'redux/searchUser', 11 | async (search) => { 12 | try { 13 | const { data } = await searchUsers(search); 14 | return data; 15 | } catch (error) { 16 | toast.error('Something Went Wrong.Try Again!'); 17 | } 18 | } 19 | ); 20 | const searchSlice = createSlice({ 21 | name: 'search', 22 | initialState, 23 | reducers: {}, 24 | extraReducers: { 25 | [searchUserThunk.pending]: (state) => { 26 | state.isLoading = true; 27 | }, 28 | [searchUserThunk.fulfilled]: (state, { payload }) => { 29 | state.searchResults = payload; 30 | state.isLoading = false; 31 | }, 32 | [searchUserThunk.rejected]: (state) => { 33 | state.isError = true; 34 | }, 35 | }, 36 | }); 37 | export default searchSlice.reducer; 38 | -------------------------------------------------------------------------------- /clients/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 | -------------------------------------------------------------------------------- /clients/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 | -------------------------------------------------------------------------------- /clients/src/store.js: -------------------------------------------------------------------------------- 1 | import { configureStore } from "@reduxjs/toolkit"; 2 | import activeUserSlice from "./redux/activeUserSlice"; 3 | import chatsSlice from "./redux/chatsSlice"; 4 | import profileSlice from "./redux/profileSlice"; 5 | import searchSlice from "./redux/searchSlice"; 6 | const store = configureStore({ 7 | reducer: { 8 | activeUser: activeUserSlice, 9 | profile: profileSlice, 10 | search: searchSlice, 11 | chats: chatsSlice, 12 | }, 13 | }); 14 | export default store; 15 | -------------------------------------------------------------------------------- /clients/src/utils/logics.js: -------------------------------------------------------------------------------- 1 | export const isSameSenderMargin = (messages, m, i, userId) => { 2 | if ( 3 | i < messages.length - 1 && 4 | messages[i + 1].sender._id === m.sender._id && 5 | messages[i].sender._id !== userId 6 | ) 7 | return 33; 8 | else if ( 9 | (i < messages.length - 1 && 10 | messages[i + 1].sender._id !== m.sender._id && 11 | messages[i].sender._id !== userId) || 12 | (i === messages.length - 1 && messages[i].sender._id !== userId) 13 | ) 14 | return 0; 15 | else return 'auto'; 16 | }; 17 | export function timeSince(date) { 18 | var seconds = Math.floor((new Date() - date) / 1000); 19 | 20 | var interval = seconds / 31536000; 21 | 22 | if (interval > 1) { 23 | return Math.floor(interval) + ' year ago'; 24 | } 25 | interval = seconds / 2592000; 26 | if (interval > 1) { 27 | return Math.floor(interval) + ' month ago'; 28 | } 29 | interval = seconds / 86400; 30 | if (interval > 1) { 31 | return Math.floor(interval) + ' day ago'; 32 | } 33 | interval = seconds / 3600; 34 | if (interval > 1) { 35 | return Math.floor(interval) + ' hour ago'; 36 | } 37 | interval = seconds / 60; 38 | if (interval > 1) { 39 | return Math.floor(interval) + ' minute ago'; 40 | } 41 | return Math.floor(seconds) + ' seconda ago'; 42 | } 43 | export const isSameSender = (messages, m, i, userId) => { 44 | return ( 45 | i < messages.length - 1 && 46 | (messages[i + 1].sender._id !== m.sender._id || 47 | messages[i + 1].sender._id === undefined) && 48 | messages[i].sender._id !== userId 49 | ); 50 | }; 51 | export const isLastMessage = (messages, i, userId) => { 52 | return ( 53 | i === messages.length - 1 && 54 | messages[messages.length - 1].sender._id !== userId && 55 | messages[messages.length - 1].sender._id 56 | ); 57 | }; 58 | export const isSameUser = (messages, m, i) => { 59 | return i > 0 && messages[i - 1].sender._id === m.sender._id; 60 | }; 61 | export const getSender = (activeUser, users) => { 62 | return activeUser.id === users[0]._id ? users[1].name : users[0].name; 63 | }; 64 | export const getChatName = (activeChat, activeUser) => { 65 | return activeChat?.isGroup 66 | ? activeChat?.chatName 67 | : activeChat?.users[0]?._id === activeUser.id 68 | ? activeChat?.users[1]?.name 69 | : activeChat?.users[0]?.name; 70 | }; 71 | export const getChatPhoto = (activeChat, activeUser) => { 72 | return activeChat?.isGroup 73 | ? activeChat.photo 74 | : activeChat?.users[0]?._id === activeUser?.id 75 | ? activeChat?.users[1]?.profilePic 76 | : activeChat?.users[0]?.profilePic; 77 | }; 78 | -------------------------------------------------------------------------------- /clients/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: ["./src/**/*.{js,jsx,ts,tsx}"], 4 | theme: { 5 | extend: {}, 6 | }, 7 | plugins: [], 8 | }; 9 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chat-website", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@ampproject/remapping": { 8 | "version": "2.2.1", 9 | "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", 10 | "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", 11 | "dev": true, 12 | "requires": { 13 | "@jridgewell/gen-mapping": "^0.3.0", 14 | "@jridgewell/trace-mapping": "^0.3.9" 15 | } 16 | }, 17 | "@babel/cli": { 18 | "version": "7.22.9", 19 | "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.22.9.tgz", 20 | "integrity": "sha512-nb2O7AThqRo7/E53EGiuAkMaRbb7J5Qp3RvN+dmua1U+kydm0oznkhqbTEG15yk26G/C3yL6OdZjzgl+DMXVVA==", 21 | "dev": true, 22 | "requires": { 23 | "@jridgewell/trace-mapping": "^0.3.17", 24 | "@nicolo-ribaudo/chokidar-2": "2.1.8-no-fsevents.3", 25 | "chokidar": "^3.4.0", 26 | "commander": "^4.0.1", 27 | "convert-source-map": "^1.1.0", 28 | "fs-readdir-recursive": "^1.1.0", 29 | "glob": "^7.2.0", 30 | "make-dir": "^2.1.0", 31 | "slash": "^2.0.0" 32 | } 33 | }, 34 | "@babel/code-frame": { 35 | "version": "7.22.5", 36 | "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.5.tgz", 37 | "integrity": "sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ==", 38 | "dev": true, 39 | "requires": { 40 | "@babel/highlight": "^7.22.5" 41 | } 42 | }, 43 | "@babel/compat-data": { 44 | "version": "7.22.9", 45 | "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.22.9.tgz", 46 | "integrity": "sha512-5UamI7xkUcJ3i9qVDS+KFDEK8/7oJ55/sJMB1Ge7IEapr7KfdfV/HErR+koZwOfd+SgtFKOKRhRakdg++DcJpQ==", 47 | "dev": true 48 | }, 49 | "@babel/core": { 50 | "version": "7.22.9", 51 | "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.22.9.tgz", 52 | "integrity": "sha512-G2EgeufBcYw27U4hhoIwFcgc1XU7TlXJ3mv04oOv1WCuo900U/anZSPzEqNjwdjgffkk2Gs0AN0dW1CKVLcG7w==", 53 | "dev": true, 54 | "requires": { 55 | "@ampproject/remapping": "^2.2.0", 56 | "@babel/code-frame": "^7.22.5", 57 | "@babel/generator": "^7.22.9", 58 | "@babel/helper-compilation-targets": "^7.22.9", 59 | "@babel/helper-module-transforms": "^7.22.9", 60 | "@babel/helpers": "^7.22.6", 61 | "@babel/parser": "^7.22.7", 62 | "@babel/template": "^7.22.5", 63 | "@babel/traverse": "^7.22.8", 64 | "@babel/types": "^7.22.5", 65 | "convert-source-map": "^1.7.0", 66 | "debug": "^4.1.0", 67 | "gensync": "^1.0.0-beta.2", 68 | "json5": "^2.2.2", 69 | "semver": "^6.3.1" 70 | }, 71 | "dependencies": { 72 | "semver": { 73 | "version": "6.3.1", 74 | "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", 75 | "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", 76 | "dev": true 77 | } 78 | } 79 | }, 80 | "@babel/generator": { 81 | "version": "7.22.9", 82 | "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.9.tgz", 83 | "integrity": "sha512-KtLMbmicyuK2Ak/FTCJVbDnkN1SlT8/kceFTiuDiiRUUSMnHMidxSCdG4ndkTOHHpoomWe/4xkvHkEOncwjYIw==", 84 | "dev": true, 85 | "requires": { 86 | "@babel/types": "^7.22.5", 87 | "@jridgewell/gen-mapping": "^0.3.2", 88 | "@jridgewell/trace-mapping": "^0.3.17", 89 | "jsesc": "^2.5.1" 90 | } 91 | }, 92 | "@babel/helper-annotate-as-pure": { 93 | "version": "7.22.5", 94 | "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz", 95 | "integrity": "sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==", 96 | "dev": true, 97 | "requires": { 98 | "@babel/types": "^7.22.5" 99 | } 100 | }, 101 | "@babel/helper-builder-binary-assignment-operator-visitor": { 102 | "version": "7.22.5", 103 | "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.5.tgz", 104 | "integrity": "sha512-m1EP3lVOPptR+2DwD125gziZNcmoNSHGmJROKoy87loWUQyJaVXDgpmruWqDARZSmtYQ+Dl25okU8+qhVzuykw==", 105 | "dev": true, 106 | "requires": { 107 | "@babel/types": "^7.22.5" 108 | } 109 | }, 110 | "@babel/helper-compilation-targets": { 111 | "version": "7.22.9", 112 | "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.9.tgz", 113 | "integrity": "sha512-7qYrNM6HjpnPHJbopxmb8hSPoZ0gsX8IvUS32JGVoy+pU9e5N0nLr1VjJoR6kA4d9dmGLxNYOjeB8sUDal2WMw==", 114 | "dev": true, 115 | "requires": { 116 | "@babel/compat-data": "^7.22.9", 117 | "@babel/helper-validator-option": "^7.22.5", 118 | "browserslist": "^4.21.9", 119 | "lru-cache": "^5.1.1", 120 | "semver": "^6.3.1" 121 | }, 122 | "dependencies": { 123 | "semver": { 124 | "version": "6.3.1", 125 | "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", 126 | "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", 127 | "dev": true 128 | } 129 | } 130 | }, 131 | "@babel/helper-create-class-features-plugin": { 132 | "version": "7.22.9", 133 | "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.9.tgz", 134 | "integrity": "sha512-Pwyi89uO4YrGKxL/eNJ8lfEH55DnRloGPOseaA8NFNL6jAUnn+KccaISiFazCj5IolPPDjGSdzQzXVzODVRqUQ==", 135 | "dev": true, 136 | "requires": { 137 | "@babel/helper-annotate-as-pure": "^7.22.5", 138 | "@babel/helper-environment-visitor": "^7.22.5", 139 | "@babel/helper-function-name": "^7.22.5", 140 | "@babel/helper-member-expression-to-functions": "^7.22.5", 141 | "@babel/helper-optimise-call-expression": "^7.22.5", 142 | "@babel/helper-replace-supers": "^7.22.9", 143 | "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", 144 | "@babel/helper-split-export-declaration": "^7.22.6", 145 | "semver": "^6.3.1" 146 | }, 147 | "dependencies": { 148 | "semver": { 149 | "version": "6.3.1", 150 | "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", 151 | "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", 152 | "dev": true 153 | } 154 | } 155 | }, 156 | "@babel/helper-create-regexp-features-plugin": { 157 | "version": "7.22.9", 158 | "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.9.tgz", 159 | "integrity": "sha512-+svjVa/tFwsNSG4NEy1h85+HQ5imbT92Q5/bgtS7P0GTQlP8WuFdqsiABmQouhiFGyV66oGxZFpeYHza1rNsKw==", 160 | "dev": true, 161 | "requires": { 162 | "@babel/helper-annotate-as-pure": "^7.22.5", 163 | "regexpu-core": "^5.3.1", 164 | "semver": "^6.3.1" 165 | }, 166 | "dependencies": { 167 | "semver": { 168 | "version": "6.3.1", 169 | "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", 170 | "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", 171 | "dev": true 172 | } 173 | } 174 | }, 175 | "@babel/helper-define-polyfill-provider": { 176 | "version": "0.4.1", 177 | "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.1.tgz", 178 | "integrity": "sha512-kX4oXixDxG197yhX+J3Wp+NpL2wuCFjWQAr6yX2jtCnflK9ulMI51ULFGIrWiX1jGfvAxdHp+XQCcP2bZGPs9A==", 179 | "dev": true, 180 | "requires": { 181 | "@babel/helper-compilation-targets": "^7.22.6", 182 | "@babel/helper-plugin-utils": "^7.22.5", 183 | "debug": "^4.1.1", 184 | "lodash.debounce": "^4.0.8", 185 | "resolve": "^1.14.2" 186 | } 187 | }, 188 | "@babel/helper-environment-visitor": { 189 | "version": "7.22.5", 190 | "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz", 191 | "integrity": "sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q==", 192 | "dev": true 193 | }, 194 | "@babel/helper-function-name": { 195 | "version": "7.22.5", 196 | "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz", 197 | "integrity": "sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==", 198 | "dev": true, 199 | "requires": { 200 | "@babel/template": "^7.22.5", 201 | "@babel/types": "^7.22.5" 202 | } 203 | }, 204 | "@babel/helper-hoist-variables": { 205 | "version": "7.22.5", 206 | "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", 207 | "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", 208 | "dev": true, 209 | "requires": { 210 | "@babel/types": "^7.22.5" 211 | } 212 | }, 213 | "@babel/helper-member-expression-to-functions": { 214 | "version": "7.22.5", 215 | "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.22.5.tgz", 216 | "integrity": "sha512-aBiH1NKMG0H2cGZqspNvsaBe6wNGjbJjuLy29aU+eDZjSbbN53BaxlpB02xm9v34pLTZ1nIQPFYn2qMZoa5BQQ==", 217 | "dev": true, 218 | "requires": { 219 | "@babel/types": "^7.22.5" 220 | } 221 | }, 222 | "@babel/helper-module-imports": { 223 | "version": "7.22.5", 224 | "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.5.tgz", 225 | "integrity": "sha512-8Dl6+HD/cKifutF5qGd/8ZJi84QeAKh+CEe1sBzz8UayBBGg1dAIJrdHOcOM5b2MpzWL2yuotJTtGjETq0qjXg==", 226 | "dev": true, 227 | "requires": { 228 | "@babel/types": "^7.22.5" 229 | } 230 | }, 231 | "@babel/helper-module-transforms": { 232 | "version": "7.22.9", 233 | "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.22.9.tgz", 234 | "integrity": "sha512-t+WA2Xn5K+rTeGtC8jCsdAH52bjggG5TKRuRrAGNM/mjIbO4GxvlLMFOEz9wXY5I2XQ60PMFsAG2WIcG82dQMQ==", 235 | "dev": true, 236 | "requires": { 237 | "@babel/helper-environment-visitor": "^7.22.5", 238 | "@babel/helper-module-imports": "^7.22.5", 239 | "@babel/helper-simple-access": "^7.22.5", 240 | "@babel/helper-split-export-declaration": "^7.22.6", 241 | "@babel/helper-validator-identifier": "^7.22.5" 242 | } 243 | }, 244 | "@babel/helper-optimise-call-expression": { 245 | "version": "7.22.5", 246 | "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz", 247 | "integrity": "sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==", 248 | "dev": true, 249 | "requires": { 250 | "@babel/types": "^7.22.5" 251 | } 252 | }, 253 | "@babel/helper-plugin-utils": { 254 | "version": "7.22.5", 255 | "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", 256 | "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==", 257 | "dev": true 258 | }, 259 | "@babel/helper-remap-async-to-generator": { 260 | "version": "7.22.9", 261 | "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.9.tgz", 262 | "integrity": "sha512-8WWC4oR4Px+tr+Fp0X3RHDVfINGpF3ad1HIbrc8A77epiR6eMMc6jsgozkzT2uDiOOdoS9cLIQ+XD2XvI2WSmQ==", 263 | "dev": true, 264 | "requires": { 265 | "@babel/helper-annotate-as-pure": "^7.22.5", 266 | "@babel/helper-environment-visitor": "^7.22.5", 267 | "@babel/helper-wrap-function": "^7.22.9" 268 | } 269 | }, 270 | "@babel/helper-replace-supers": { 271 | "version": "7.22.9", 272 | "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.22.9.tgz", 273 | "integrity": "sha512-LJIKvvpgPOPUThdYqcX6IXRuIcTkcAub0IaDRGCZH0p5GPUp7PhRU9QVgFcDDd51BaPkk77ZjqFwh6DZTAEmGg==", 274 | "dev": true, 275 | "requires": { 276 | "@babel/helper-environment-visitor": "^7.22.5", 277 | "@babel/helper-member-expression-to-functions": "^7.22.5", 278 | "@babel/helper-optimise-call-expression": "^7.22.5" 279 | } 280 | }, 281 | "@babel/helper-simple-access": { 282 | "version": "7.22.5", 283 | "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", 284 | "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", 285 | "dev": true, 286 | "requires": { 287 | "@babel/types": "^7.22.5" 288 | } 289 | }, 290 | "@babel/helper-skip-transparent-expression-wrappers": { 291 | "version": "7.22.5", 292 | "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz", 293 | "integrity": "sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q==", 294 | "dev": true, 295 | "requires": { 296 | "@babel/types": "^7.22.5" 297 | } 298 | }, 299 | "@babel/helper-split-export-declaration": { 300 | "version": "7.22.6", 301 | "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", 302 | "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", 303 | "dev": true, 304 | "requires": { 305 | "@babel/types": "^7.22.5" 306 | } 307 | }, 308 | "@babel/helper-string-parser": { 309 | "version": "7.22.5", 310 | "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", 311 | "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", 312 | "dev": true 313 | }, 314 | "@babel/helper-validator-identifier": { 315 | "version": "7.22.5", 316 | "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz", 317 | "integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==", 318 | "dev": true 319 | }, 320 | "@babel/helper-validator-option": { 321 | "version": "7.22.5", 322 | "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.5.tgz", 323 | "integrity": "sha512-R3oB6xlIVKUnxNUxbmgq7pKjxpru24zlimpE8WK47fACIlM0II/Hm1RS8IaOI7NgCr6LNS+jl5l75m20npAziw==", 324 | "dev": true 325 | }, 326 | "@babel/helper-wrap-function": { 327 | "version": "7.22.9", 328 | "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.22.9.tgz", 329 | "integrity": "sha512-sZ+QzfauuUEfxSEjKFmi3qDSHgLsTPK/pEpoD/qonZKOtTPTLbf59oabPQ4rKekt9lFcj/hTZaOhWwFYrgjk+Q==", 330 | "dev": true, 331 | "requires": { 332 | "@babel/helper-function-name": "^7.22.5", 333 | "@babel/template": "^7.22.5", 334 | "@babel/types": "^7.22.5" 335 | } 336 | }, 337 | "@babel/helpers": { 338 | "version": "7.22.6", 339 | "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.22.6.tgz", 340 | "integrity": "sha512-YjDs6y/fVOYFV8hAf1rxd1QvR9wJe1pDBZ2AREKq/SDayfPzgk0PBnVuTCE5X1acEpMMNOVUqoe+OwiZGJ+OaA==", 341 | "dev": true, 342 | "requires": { 343 | "@babel/template": "^7.22.5", 344 | "@babel/traverse": "^7.22.6", 345 | "@babel/types": "^7.22.5" 346 | } 347 | }, 348 | "@babel/highlight": { 349 | "version": "7.22.5", 350 | "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.5.tgz", 351 | "integrity": "sha512-BSKlD1hgnedS5XRnGOljZawtag7H1yPfQp0tdNJCHoH6AZ+Pcm9VvkrK59/Yy593Ypg0zMxH2BxD1VPYUQ7UIw==", 352 | "dev": true, 353 | "requires": { 354 | "@babel/helper-validator-identifier": "^7.22.5", 355 | "chalk": "^2.0.0", 356 | "js-tokens": "^4.0.0" 357 | } 358 | }, 359 | "@babel/parser": { 360 | "version": "7.22.7", 361 | "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.7.tgz", 362 | "integrity": "sha512-7NF8pOkHP5o2vpmGgNGcfAeCvOYhGLyA3Z4eBQkT1RJlWu47n63bCs93QfJ2hIAFCil7L5P2IWhs1oToVgrL0Q==", 363 | "dev": true 364 | }, 365 | "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { 366 | "version": "7.22.5", 367 | "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.22.5.tgz", 368 | "integrity": "sha512-NP1M5Rf+u2Gw9qfSO4ihjcTGW5zXTi36ITLd4/EoAcEhIZ0yjMqmftDNl3QC19CX7olhrjpyU454g/2W7X0jvQ==", 369 | "dev": true, 370 | "requires": { 371 | "@babel/helper-plugin-utils": "^7.22.5" 372 | } 373 | }, 374 | "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { 375 | "version": "7.22.5", 376 | "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.22.5.tgz", 377 | "integrity": "sha512-31Bb65aZaUwqCbWMnZPduIZxCBngHFlzyN6Dq6KAJjtx+lx6ohKHubc61OomYi7XwVD4Ol0XCVz4h+pYFR048g==", 378 | "dev": true, 379 | "requires": { 380 | "@babel/helper-plugin-utils": "^7.22.5", 381 | "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", 382 | "@babel/plugin-transform-optional-chaining": "^7.22.5" 383 | } 384 | }, 385 | "@babel/plugin-proposal-private-property-in-object": { 386 | "version": "7.21.0-placeholder-for-preset-env.2", 387 | "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", 388 | "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", 389 | "dev": true 390 | }, 391 | "@babel/plugin-proposal-unicode-property-regex": { 392 | "version": "7.18.6", 393 | "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz", 394 | "integrity": "sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==", 395 | "dev": true, 396 | "requires": { 397 | "@babel/helper-create-regexp-features-plugin": "^7.18.6", 398 | "@babel/helper-plugin-utils": "^7.18.6" 399 | } 400 | }, 401 | "@babel/plugin-syntax-async-generators": { 402 | "version": "7.8.4", 403 | "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", 404 | "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", 405 | "dev": true, 406 | "requires": { 407 | "@babel/helper-plugin-utils": "^7.8.0" 408 | } 409 | }, 410 | "@babel/plugin-syntax-class-properties": { 411 | "version": "7.12.13", 412 | "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", 413 | "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", 414 | "dev": true, 415 | "requires": { 416 | "@babel/helper-plugin-utils": "^7.12.13" 417 | } 418 | }, 419 | "@babel/plugin-syntax-class-static-block": { 420 | "version": "7.14.5", 421 | "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", 422 | "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", 423 | "dev": true, 424 | "requires": { 425 | "@babel/helper-plugin-utils": "^7.14.5" 426 | } 427 | }, 428 | "@babel/plugin-syntax-dynamic-import": { 429 | "version": "7.8.3", 430 | "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", 431 | "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", 432 | "dev": true, 433 | "requires": { 434 | "@babel/helper-plugin-utils": "^7.8.0" 435 | } 436 | }, 437 | "@babel/plugin-syntax-export-namespace-from": { 438 | "version": "7.8.3", 439 | "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", 440 | "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", 441 | "dev": true, 442 | "requires": { 443 | "@babel/helper-plugin-utils": "^7.8.3" 444 | } 445 | }, 446 | "@babel/plugin-syntax-import-assertions": { 447 | "version": "7.22.5", 448 | "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.22.5.tgz", 449 | "integrity": "sha512-rdV97N7KqsRzeNGoWUOK6yUsWarLjE5Su/Snk9IYPU9CwkWHs4t+rTGOvffTR8XGkJMTAdLfO0xVnXm8wugIJg==", 450 | "dev": true, 451 | "requires": { 452 | "@babel/helper-plugin-utils": "^7.22.5" 453 | } 454 | }, 455 | "@babel/plugin-syntax-import-attributes": { 456 | "version": "7.22.5", 457 | "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.22.5.tgz", 458 | "integrity": "sha512-KwvoWDeNKPETmozyFE0P2rOLqh39EoQHNjqizrI5B8Vt0ZNS7M56s7dAiAqbYfiAYOuIzIh96z3iR2ktgu3tEg==", 459 | "dev": true, 460 | "requires": { 461 | "@babel/helper-plugin-utils": "^7.22.5" 462 | } 463 | }, 464 | "@babel/plugin-syntax-import-meta": { 465 | "version": "7.10.4", 466 | "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", 467 | "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", 468 | "dev": true, 469 | "requires": { 470 | "@babel/helper-plugin-utils": "^7.10.4" 471 | } 472 | }, 473 | "@babel/plugin-syntax-json-strings": { 474 | "version": "7.8.3", 475 | "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", 476 | "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", 477 | "dev": true, 478 | "requires": { 479 | "@babel/helper-plugin-utils": "^7.8.0" 480 | } 481 | }, 482 | "@babel/plugin-syntax-jsx": { 483 | "version": "7.22.5", 484 | "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.22.5.tgz", 485 | "integrity": "sha512-gvyP4hZrgrs/wWMaocvxZ44Hw0b3W8Pe+cMxc8V1ULQ07oh8VNbIRaoD1LRZVTvD+0nieDKjfgKg89sD7rrKrg==", 486 | "dev": true, 487 | "requires": { 488 | "@babel/helper-plugin-utils": "^7.22.5" 489 | } 490 | }, 491 | "@babel/plugin-syntax-logical-assignment-operators": { 492 | "version": "7.10.4", 493 | "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", 494 | "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", 495 | "dev": true, 496 | "requires": { 497 | "@babel/helper-plugin-utils": "^7.10.4" 498 | } 499 | }, 500 | "@babel/plugin-syntax-nullish-coalescing-operator": { 501 | "version": "7.8.3", 502 | "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", 503 | "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", 504 | "dev": true, 505 | "requires": { 506 | "@babel/helper-plugin-utils": "^7.8.0" 507 | } 508 | }, 509 | "@babel/plugin-syntax-numeric-separator": { 510 | "version": "7.10.4", 511 | "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", 512 | "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", 513 | "dev": true, 514 | "requires": { 515 | "@babel/helper-plugin-utils": "^7.10.4" 516 | } 517 | }, 518 | "@babel/plugin-syntax-object-rest-spread": { 519 | "version": "7.8.3", 520 | "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", 521 | "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", 522 | "dev": true, 523 | "requires": { 524 | "@babel/helper-plugin-utils": "^7.8.0" 525 | } 526 | }, 527 | "@babel/plugin-syntax-optional-catch-binding": { 528 | "version": "7.8.3", 529 | "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", 530 | "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", 531 | "dev": true, 532 | "requires": { 533 | "@babel/helper-plugin-utils": "^7.8.0" 534 | } 535 | }, 536 | "@babel/plugin-syntax-optional-chaining": { 537 | "version": "7.8.3", 538 | "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", 539 | "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", 540 | "dev": true, 541 | "requires": { 542 | "@babel/helper-plugin-utils": "^7.8.0" 543 | } 544 | }, 545 | "@babel/plugin-syntax-private-property-in-object": { 546 | "version": "7.14.5", 547 | "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", 548 | "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", 549 | "dev": true, 550 | "requires": { 551 | "@babel/helper-plugin-utils": "^7.14.5" 552 | } 553 | }, 554 | "@babel/plugin-syntax-top-level-await": { 555 | "version": "7.14.5", 556 | "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", 557 | "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", 558 | "dev": true, 559 | "requires": { 560 | "@babel/helper-plugin-utils": "^7.14.5" 561 | } 562 | }, 563 | "@babel/plugin-syntax-unicode-sets-regex": { 564 | "version": "7.18.6", 565 | "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", 566 | "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", 567 | "dev": true, 568 | "requires": { 569 | "@babel/helper-create-regexp-features-plugin": "^7.18.6", 570 | "@babel/helper-plugin-utils": "^7.18.6" 571 | } 572 | }, 573 | "@babel/plugin-transform-arrow-functions": { 574 | "version": "7.22.5", 575 | "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.22.5.tgz", 576 | "integrity": "sha512-26lTNXoVRdAnsaDXPpvCNUq+OVWEVC6bx7Vvz9rC53F2bagUWW4u4ii2+h8Fejfh7RYqPxn+libeFBBck9muEw==", 577 | "dev": true, 578 | "requires": { 579 | "@babel/helper-plugin-utils": "^7.22.5" 580 | } 581 | }, 582 | "@babel/plugin-transform-async-generator-functions": { 583 | "version": "7.22.7", 584 | "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.22.7.tgz", 585 | "integrity": "sha512-7HmE7pk/Fmke45TODvxvkxRMV9RazV+ZZzhOL9AG8G29TLrr3jkjwF7uJfxZ30EoXpO+LJkq4oA8NjO2DTnEDg==", 586 | "dev": true, 587 | "requires": { 588 | "@babel/helper-environment-visitor": "^7.22.5", 589 | "@babel/helper-plugin-utils": "^7.22.5", 590 | "@babel/helper-remap-async-to-generator": "^7.22.5", 591 | "@babel/plugin-syntax-async-generators": "^7.8.4" 592 | } 593 | }, 594 | "@babel/plugin-transform-async-to-generator": { 595 | "version": "7.22.5", 596 | "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.22.5.tgz", 597 | "integrity": "sha512-b1A8D8ZzE/VhNDoV1MSJTnpKkCG5bJo+19R4o4oy03zM7ws8yEMK755j61Dc3EyvdysbqH5BOOTquJ7ZX9C6vQ==", 598 | "dev": true, 599 | "requires": { 600 | "@babel/helper-module-imports": "^7.22.5", 601 | "@babel/helper-plugin-utils": "^7.22.5", 602 | "@babel/helper-remap-async-to-generator": "^7.22.5" 603 | } 604 | }, 605 | "@babel/plugin-transform-block-scoped-functions": { 606 | "version": "7.22.5", 607 | "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.22.5.tgz", 608 | "integrity": "sha512-tdXZ2UdknEKQWKJP1KMNmuF5Lx3MymtMN/pvA+p/VEkhK8jVcQ1fzSy8KM9qRYhAf2/lV33hoMPKI/xaI9sADA==", 609 | "dev": true, 610 | "requires": { 611 | "@babel/helper-plugin-utils": "^7.22.5" 612 | } 613 | }, 614 | "@babel/plugin-transform-block-scoping": { 615 | "version": "7.22.5", 616 | "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.22.5.tgz", 617 | "integrity": "sha512-EcACl1i5fSQ6bt+YGuU/XGCeZKStLmyVGytWkpyhCLeQVA0eu6Wtiw92V+I1T/hnezUv7j74dA/Ro69gWcU+hg==", 618 | "dev": true, 619 | "requires": { 620 | "@babel/helper-plugin-utils": "^7.22.5" 621 | } 622 | }, 623 | "@babel/plugin-transform-class-properties": { 624 | "version": "7.22.5", 625 | "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.22.5.tgz", 626 | "integrity": "sha512-nDkQ0NfkOhPTq8YCLiWNxp1+f9fCobEjCb0n8WdbNUBc4IB5V7P1QnX9IjpSoquKrXF5SKojHleVNs2vGeHCHQ==", 627 | "dev": true, 628 | "requires": { 629 | "@babel/helper-create-class-features-plugin": "^7.22.5", 630 | "@babel/helper-plugin-utils": "^7.22.5" 631 | } 632 | }, 633 | "@babel/plugin-transform-class-static-block": { 634 | "version": "7.22.5", 635 | "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.22.5.tgz", 636 | "integrity": "sha512-SPToJ5eYZLxlnp1UzdARpOGeC2GbHvr9d/UV0EukuVx8atktg194oe+C5BqQ8jRTkgLRVOPYeXRSBg1IlMoVRA==", 637 | "dev": true, 638 | "requires": { 639 | "@babel/helper-create-class-features-plugin": "^7.22.5", 640 | "@babel/helper-plugin-utils": "^7.22.5", 641 | "@babel/plugin-syntax-class-static-block": "^7.14.5" 642 | } 643 | }, 644 | "@babel/plugin-transform-classes": { 645 | "version": "7.22.6", 646 | "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.22.6.tgz", 647 | "integrity": "sha512-58EgM6nuPNG6Py4Z3zSuu0xWu2VfodiMi72Jt5Kj2FECmaYk1RrTXA45z6KBFsu9tRgwQDwIiY4FXTt+YsSFAQ==", 648 | "dev": true, 649 | "requires": { 650 | "@babel/helper-annotate-as-pure": "^7.22.5", 651 | "@babel/helper-compilation-targets": "^7.22.6", 652 | "@babel/helper-environment-visitor": "^7.22.5", 653 | "@babel/helper-function-name": "^7.22.5", 654 | "@babel/helper-optimise-call-expression": "^7.22.5", 655 | "@babel/helper-plugin-utils": "^7.22.5", 656 | "@babel/helper-replace-supers": "^7.22.5", 657 | "@babel/helper-split-export-declaration": "^7.22.6", 658 | "globals": "^11.1.0" 659 | } 660 | }, 661 | "@babel/plugin-transform-computed-properties": { 662 | "version": "7.22.5", 663 | "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.22.5.tgz", 664 | "integrity": "sha512-4GHWBgRf0krxPX+AaPtgBAlTgTeZmqDynokHOX7aqqAB4tHs3U2Y02zH6ETFdLZGcg9UQSD1WCmkVrE9ErHeOg==", 665 | "dev": true, 666 | "requires": { 667 | "@babel/helper-plugin-utils": "^7.22.5", 668 | "@babel/template": "^7.22.5" 669 | } 670 | }, 671 | "@babel/plugin-transform-destructuring": { 672 | "version": "7.22.5", 673 | "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.22.5.tgz", 674 | "integrity": "sha512-GfqcFuGW8vnEqTUBM7UtPd5A4q797LTvvwKxXTgRsFjoqaJiEg9deBG6kWeQYkVEL569NpnmpC0Pkr/8BLKGnQ==", 675 | "dev": true, 676 | "requires": { 677 | "@babel/helper-plugin-utils": "^7.22.5" 678 | } 679 | }, 680 | "@babel/plugin-transform-dotall-regex": { 681 | "version": "7.22.5", 682 | "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.22.5.tgz", 683 | "integrity": "sha512-5/Yk9QxCQCl+sOIB1WelKnVRxTJDSAIxtJLL2/pqL14ZVlbH0fUQUZa/T5/UnQtBNgghR7mfB8ERBKyKPCi7Vw==", 684 | "dev": true, 685 | "requires": { 686 | "@babel/helper-create-regexp-features-plugin": "^7.22.5", 687 | "@babel/helper-plugin-utils": "^7.22.5" 688 | } 689 | }, 690 | "@babel/plugin-transform-duplicate-keys": { 691 | "version": "7.22.5", 692 | "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.22.5.tgz", 693 | "integrity": "sha512-dEnYD+9BBgld5VBXHnF/DbYGp3fqGMsyxKbtD1mDyIA7AkTSpKXFhCVuj/oQVOoALfBs77DudA0BE4d5mcpmqw==", 694 | "dev": true, 695 | "requires": { 696 | "@babel/helper-plugin-utils": "^7.22.5" 697 | } 698 | }, 699 | "@babel/plugin-transform-dynamic-import": { 700 | "version": "7.22.5", 701 | "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.22.5.tgz", 702 | "integrity": "sha512-0MC3ppTB1AMxd8fXjSrbPa7LT9hrImt+/fcj+Pg5YMD7UQyWp/02+JWpdnCymmsXwIx5Z+sYn1bwCn4ZJNvhqQ==", 703 | "dev": true, 704 | "requires": { 705 | "@babel/helper-plugin-utils": "^7.22.5", 706 | "@babel/plugin-syntax-dynamic-import": "^7.8.3" 707 | } 708 | }, 709 | "@babel/plugin-transform-exponentiation-operator": { 710 | "version": "7.22.5", 711 | "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.22.5.tgz", 712 | "integrity": "sha512-vIpJFNM/FjZ4rh1myqIya9jXwrwwgFRHPjT3DkUA9ZLHuzox8jiXkOLvwm1H+PQIP3CqfC++WPKeuDi0Sjdj1g==", 713 | "dev": true, 714 | "requires": { 715 | "@babel/helper-builder-binary-assignment-operator-visitor": "^7.22.5", 716 | "@babel/helper-plugin-utils": "^7.22.5" 717 | } 718 | }, 719 | "@babel/plugin-transform-export-namespace-from": { 720 | "version": "7.22.5", 721 | "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.22.5.tgz", 722 | "integrity": "sha512-X4hhm7FRnPgd4nDA4b/5V280xCx6oL7Oob5+9qVS5C13Zq4bh1qq7LU0GgRU6b5dBWBvhGaXYVB4AcN6+ol6vg==", 723 | "dev": true, 724 | "requires": { 725 | "@babel/helper-plugin-utils": "^7.22.5", 726 | "@babel/plugin-syntax-export-namespace-from": "^7.8.3" 727 | } 728 | }, 729 | "@babel/plugin-transform-for-of": { 730 | "version": "7.22.5", 731 | "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.22.5.tgz", 732 | "integrity": "sha512-3kxQjX1dU9uudwSshyLeEipvrLjBCVthCgeTp6CzE/9JYrlAIaeekVxRpCWsDDfYTfRZRoCeZatCQvwo+wvK8A==", 733 | "dev": true, 734 | "requires": { 735 | "@babel/helper-plugin-utils": "^7.22.5" 736 | } 737 | }, 738 | "@babel/plugin-transform-function-name": { 739 | "version": "7.22.5", 740 | "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.22.5.tgz", 741 | "integrity": "sha512-UIzQNMS0p0HHiQm3oelztj+ECwFnj+ZRV4KnguvlsD2of1whUeM6o7wGNj6oLwcDoAXQ8gEqfgC24D+VdIcevg==", 742 | "dev": true, 743 | "requires": { 744 | "@babel/helper-compilation-targets": "^7.22.5", 745 | "@babel/helper-function-name": "^7.22.5", 746 | "@babel/helper-plugin-utils": "^7.22.5" 747 | } 748 | }, 749 | "@babel/plugin-transform-json-strings": { 750 | "version": "7.22.5", 751 | "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.22.5.tgz", 752 | "integrity": "sha512-DuCRB7fu8MyTLbEQd1ew3R85nx/88yMoqo2uPSjevMj3yoN7CDM8jkgrY0wmVxfJZyJ/B9fE1iq7EQppWQmR5A==", 753 | "dev": true, 754 | "requires": { 755 | "@babel/helper-plugin-utils": "^7.22.5", 756 | "@babel/plugin-syntax-json-strings": "^7.8.3" 757 | } 758 | }, 759 | "@babel/plugin-transform-literals": { 760 | "version": "7.22.5", 761 | "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.22.5.tgz", 762 | "integrity": "sha512-fTLj4D79M+mepcw3dgFBTIDYpbcB9Sm0bpm4ppXPaO+U+PKFFyV9MGRvS0gvGw62sd10kT5lRMKXAADb9pWy8g==", 763 | "dev": true, 764 | "requires": { 765 | "@babel/helper-plugin-utils": "^7.22.5" 766 | } 767 | }, 768 | "@babel/plugin-transform-logical-assignment-operators": { 769 | "version": "7.22.5", 770 | "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.22.5.tgz", 771 | "integrity": "sha512-MQQOUW1KL8X0cDWfbwYP+TbVbZm16QmQXJQ+vndPtH/BoO0lOKpVoEDMI7+PskYxH+IiE0tS8xZye0qr1lGzSA==", 772 | "dev": true, 773 | "requires": { 774 | "@babel/helper-plugin-utils": "^7.22.5", 775 | "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" 776 | } 777 | }, 778 | "@babel/plugin-transform-member-expression-literals": { 779 | "version": "7.22.5", 780 | "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.22.5.tgz", 781 | "integrity": "sha512-RZEdkNtzzYCFl9SE9ATaUMTj2hqMb4StarOJLrZRbqqU4HSBE7UlBw9WBWQiDzrJZJdUWiMTVDI6Gv/8DPvfew==", 782 | "dev": true, 783 | "requires": { 784 | "@babel/helper-plugin-utils": "^7.22.5" 785 | } 786 | }, 787 | "@babel/plugin-transform-modules-amd": { 788 | "version": "7.22.5", 789 | "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.22.5.tgz", 790 | "integrity": "sha512-R+PTfLTcYEmb1+kK7FNkhQ1gP4KgjpSO6HfH9+f8/yfp2Nt3ggBjiVpRwmwTlfqZLafYKJACy36yDXlEmI9HjQ==", 791 | "dev": true, 792 | "requires": { 793 | "@babel/helper-module-transforms": "^7.22.5", 794 | "@babel/helper-plugin-utils": "^7.22.5" 795 | } 796 | }, 797 | "@babel/plugin-transform-modules-commonjs": { 798 | "version": "7.22.5", 799 | "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.22.5.tgz", 800 | "integrity": "sha512-B4pzOXj+ONRmuaQTg05b3y/4DuFz3WcCNAXPLb2Q0GT0TrGKGxNKV4jwsXts+StaM0LQczZbOpj8o1DLPDJIiA==", 801 | "dev": true, 802 | "requires": { 803 | "@babel/helper-module-transforms": "^7.22.5", 804 | "@babel/helper-plugin-utils": "^7.22.5", 805 | "@babel/helper-simple-access": "^7.22.5" 806 | } 807 | }, 808 | "@babel/plugin-transform-modules-systemjs": { 809 | "version": "7.22.5", 810 | "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.22.5.tgz", 811 | "integrity": "sha512-emtEpoaTMsOs6Tzz+nbmcePl6AKVtS1yC4YNAeMun9U8YCsgadPNxnOPQ8GhHFB2qdx+LZu9LgoC0Lthuu05DQ==", 812 | "dev": true, 813 | "requires": { 814 | "@babel/helper-hoist-variables": "^7.22.5", 815 | "@babel/helper-module-transforms": "^7.22.5", 816 | "@babel/helper-plugin-utils": "^7.22.5", 817 | "@babel/helper-validator-identifier": "^7.22.5" 818 | } 819 | }, 820 | "@babel/plugin-transform-modules-umd": { 821 | "version": "7.22.5", 822 | "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.22.5.tgz", 823 | "integrity": "sha512-+S6kzefN/E1vkSsKx8kmQuqeQsvCKCd1fraCM7zXm4SFoggI099Tr4G8U81+5gtMdUeMQ4ipdQffbKLX0/7dBQ==", 824 | "dev": true, 825 | "requires": { 826 | "@babel/helper-module-transforms": "^7.22.5", 827 | "@babel/helper-plugin-utils": "^7.22.5" 828 | } 829 | }, 830 | "@babel/plugin-transform-named-capturing-groups-regex": { 831 | "version": "7.22.5", 832 | "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.22.5.tgz", 833 | "integrity": "sha512-YgLLKmS3aUBhHaxp5hi1WJTgOUb/NCuDHzGT9z9WTt3YG+CPRhJs6nprbStx6DnWM4dh6gt7SU3sZodbZ08adQ==", 834 | "dev": true, 835 | "requires": { 836 | "@babel/helper-create-regexp-features-plugin": "^7.22.5", 837 | "@babel/helper-plugin-utils": "^7.22.5" 838 | } 839 | }, 840 | "@babel/plugin-transform-new-target": { 841 | "version": "7.22.5", 842 | "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.22.5.tgz", 843 | "integrity": "sha512-AsF7K0Fx/cNKVyk3a+DW0JLo+Ua598/NxMRvxDnkpCIGFh43+h/v2xyhRUYf6oD8gE4QtL83C7zZVghMjHd+iw==", 844 | "dev": true, 845 | "requires": { 846 | "@babel/helper-plugin-utils": "^7.22.5" 847 | } 848 | }, 849 | "@babel/plugin-transform-nullish-coalescing-operator": { 850 | "version": "7.22.5", 851 | "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.22.5.tgz", 852 | "integrity": "sha512-6CF8g6z1dNYZ/VXok5uYkkBBICHZPiGEl7oDnAx2Mt1hlHVHOSIKWJaXHjQJA5VB43KZnXZDIexMchY4y2PGdA==", 853 | "dev": true, 854 | "requires": { 855 | "@babel/helper-plugin-utils": "^7.22.5", 856 | "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" 857 | } 858 | }, 859 | "@babel/plugin-transform-numeric-separator": { 860 | "version": "7.22.5", 861 | "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.22.5.tgz", 862 | "integrity": "sha512-NbslED1/6M+sXiwwtcAB/nieypGw02Ejf4KtDeMkCEpP6gWFMX1wI9WKYua+4oBneCCEmulOkRpwywypVZzs/g==", 863 | "dev": true, 864 | "requires": { 865 | "@babel/helper-plugin-utils": "^7.22.5", 866 | "@babel/plugin-syntax-numeric-separator": "^7.10.4" 867 | } 868 | }, 869 | "@babel/plugin-transform-object-rest-spread": { 870 | "version": "7.22.5", 871 | "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.22.5.tgz", 872 | "integrity": "sha512-Kk3lyDmEslH9DnvCDA1s1kkd3YWQITiBOHngOtDL9Pt6BZjzqb6hiOlb8VfjiiQJ2unmegBqZu0rx5RxJb5vmQ==", 873 | "dev": true, 874 | "requires": { 875 | "@babel/compat-data": "^7.22.5", 876 | "@babel/helper-compilation-targets": "^7.22.5", 877 | "@babel/helper-plugin-utils": "^7.22.5", 878 | "@babel/plugin-syntax-object-rest-spread": "^7.8.3", 879 | "@babel/plugin-transform-parameters": "^7.22.5" 880 | } 881 | }, 882 | "@babel/plugin-transform-object-super": { 883 | "version": "7.22.5", 884 | "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.22.5.tgz", 885 | "integrity": "sha512-klXqyaT9trSjIUrcsYIfETAzmOEZL3cBYqOYLJxBHfMFFggmXOv+NYSX/Jbs9mzMVESw/WycLFPRx8ba/b2Ipw==", 886 | "dev": true, 887 | "requires": { 888 | "@babel/helper-plugin-utils": "^7.22.5", 889 | "@babel/helper-replace-supers": "^7.22.5" 890 | } 891 | }, 892 | "@babel/plugin-transform-optional-catch-binding": { 893 | "version": "7.22.5", 894 | "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.22.5.tgz", 895 | "integrity": "sha512-pH8orJahy+hzZje5b8e2QIlBWQvGpelS76C63Z+jhZKsmzfNaPQ+LaW6dcJ9bxTpo1mtXbgHwy765Ro3jftmUg==", 896 | "dev": true, 897 | "requires": { 898 | "@babel/helper-plugin-utils": "^7.22.5", 899 | "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" 900 | } 901 | }, 902 | "@babel/plugin-transform-optional-chaining": { 903 | "version": "7.22.6", 904 | "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.22.6.tgz", 905 | "integrity": "sha512-Vd5HiWml0mDVtcLHIoEU5sw6HOUW/Zk0acLs/SAeuLzkGNOPc9DB4nkUajemhCmTIz3eiaKREZn2hQQqF79YTg==", 906 | "dev": true, 907 | "requires": { 908 | "@babel/helper-plugin-utils": "^7.22.5", 909 | "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", 910 | "@babel/plugin-syntax-optional-chaining": "^7.8.3" 911 | } 912 | }, 913 | "@babel/plugin-transform-parameters": { 914 | "version": "7.22.5", 915 | "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.22.5.tgz", 916 | "integrity": "sha512-AVkFUBurORBREOmHRKo06FjHYgjrabpdqRSwq6+C7R5iTCZOsM4QbcB27St0a4U6fffyAOqh3s/qEfybAhfivg==", 917 | "dev": true, 918 | "requires": { 919 | "@babel/helper-plugin-utils": "^7.22.5" 920 | } 921 | }, 922 | "@babel/plugin-transform-private-methods": { 923 | "version": "7.22.5", 924 | "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.22.5.tgz", 925 | "integrity": "sha512-PPjh4gyrQnGe97JTalgRGMuU4icsZFnWkzicB/fUtzlKUqvsWBKEpPPfr5a2JiyirZkHxnAqkQMO5Z5B2kK3fA==", 926 | "dev": true, 927 | "requires": { 928 | "@babel/helper-create-class-features-plugin": "^7.22.5", 929 | "@babel/helper-plugin-utils": "^7.22.5" 930 | } 931 | }, 932 | "@babel/plugin-transform-private-property-in-object": { 933 | "version": "7.22.5", 934 | "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.22.5.tgz", 935 | "integrity": "sha512-/9xnaTTJcVoBtSSmrVyhtSvO3kbqS2ODoh2juEU72c3aYonNF0OMGiaz2gjukyKM2wBBYJP38S4JiE0Wfb5VMQ==", 936 | "dev": true, 937 | "requires": { 938 | "@babel/helper-annotate-as-pure": "^7.22.5", 939 | "@babel/helper-create-class-features-plugin": "^7.22.5", 940 | "@babel/helper-plugin-utils": "^7.22.5", 941 | "@babel/plugin-syntax-private-property-in-object": "^7.14.5" 942 | } 943 | }, 944 | "@babel/plugin-transform-property-literals": { 945 | "version": "7.22.5", 946 | "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.22.5.tgz", 947 | "integrity": "sha512-TiOArgddK3mK/x1Qwf5hay2pxI6wCZnvQqrFSqbtg1GLl2JcNMitVH/YnqjP+M31pLUeTfzY1HAXFDnUBV30rQ==", 948 | "dev": true, 949 | "requires": { 950 | "@babel/helper-plugin-utils": "^7.22.5" 951 | } 952 | }, 953 | "@babel/plugin-transform-react-display-name": { 954 | "version": "7.22.5", 955 | "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.22.5.tgz", 956 | "integrity": "sha512-PVk3WPYudRF5z4GKMEYUrLjPl38fJSKNaEOkFuoprioowGuWN6w2RKznuFNSlJx7pzzXXStPUnNSOEO0jL5EVw==", 957 | "dev": true, 958 | "requires": { 959 | "@babel/helper-plugin-utils": "^7.22.5" 960 | } 961 | }, 962 | "@babel/plugin-transform-react-jsx": { 963 | "version": "7.22.5", 964 | "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.22.5.tgz", 965 | "integrity": "sha512-rog5gZaVbUip5iWDMTYbVM15XQq+RkUKhET/IHR6oizR+JEoN6CAfTTuHcK4vwUyzca30qqHqEpzBOnaRMWYMA==", 966 | "dev": true, 967 | "requires": { 968 | "@babel/helper-annotate-as-pure": "^7.22.5", 969 | "@babel/helper-module-imports": "^7.22.5", 970 | "@babel/helper-plugin-utils": "^7.22.5", 971 | "@babel/plugin-syntax-jsx": "^7.22.5", 972 | "@babel/types": "^7.22.5" 973 | } 974 | }, 975 | "@babel/plugin-transform-react-jsx-development": { 976 | "version": "7.22.5", 977 | "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.22.5.tgz", 978 | "integrity": "sha512-bDhuzwWMuInwCYeDeMzyi7TaBgRQei6DqxhbyniL7/VG4RSS7HtSL2QbY4eESy1KJqlWt8g3xeEBGPuo+XqC8A==", 979 | "dev": true, 980 | "requires": { 981 | "@babel/plugin-transform-react-jsx": "^7.22.5" 982 | } 983 | }, 984 | "@babel/plugin-transform-react-pure-annotations": { 985 | "version": "7.22.5", 986 | "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.22.5.tgz", 987 | "integrity": "sha512-gP4k85wx09q+brArVinTXhWiyzLl9UpmGva0+mWyKxk6JZequ05x3eUcIUE+FyttPKJFRRVtAvQaJ6YF9h1ZpA==", 988 | "dev": true, 989 | "requires": { 990 | "@babel/helper-annotate-as-pure": "^7.22.5", 991 | "@babel/helper-plugin-utils": "^7.22.5" 992 | } 993 | }, 994 | "@babel/plugin-transform-regenerator": { 995 | "version": "7.22.5", 996 | "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.22.5.tgz", 997 | "integrity": "sha512-rR7KePOE7gfEtNTh9Qw+iO3Q/e4DEsoQ+hdvM6QUDH7JRJ5qxq5AA52ZzBWbI5i9lfNuvySgOGP8ZN7LAmaiPw==", 998 | "dev": true, 999 | "requires": { 1000 | "@babel/helper-plugin-utils": "^7.22.5", 1001 | "regenerator-transform": "^0.15.1" 1002 | } 1003 | }, 1004 | "@babel/plugin-transform-reserved-words": { 1005 | "version": "7.22.5", 1006 | "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.22.5.tgz", 1007 | "integrity": "sha512-DTtGKFRQUDm8svigJzZHzb/2xatPc6TzNvAIJ5GqOKDsGFYgAskjRulbR/vGsPKq3OPqtexnz327qYpP57RFyA==", 1008 | "dev": true, 1009 | "requires": { 1010 | "@babel/helper-plugin-utils": "^7.22.5" 1011 | } 1012 | }, 1013 | "@babel/plugin-transform-shorthand-properties": { 1014 | "version": "7.22.5", 1015 | "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.22.5.tgz", 1016 | "integrity": "sha512-vM4fq9IXHscXVKzDv5itkO1X52SmdFBFcMIBZ2FRn2nqVYqw6dBexUgMvAjHW+KXpPPViD/Yo3GrDEBaRC0QYA==", 1017 | "dev": true, 1018 | "requires": { 1019 | "@babel/helper-plugin-utils": "^7.22.5" 1020 | } 1021 | }, 1022 | "@babel/plugin-transform-spread": { 1023 | "version": "7.22.5", 1024 | "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.22.5.tgz", 1025 | "integrity": "sha512-5ZzDQIGyvN4w8+dMmpohL6MBo+l2G7tfC/O2Dg7/hjpgeWvUx8FzfeOKxGog9IimPa4YekaQ9PlDqTLOljkcxg==", 1026 | "dev": true, 1027 | "requires": { 1028 | "@babel/helper-plugin-utils": "^7.22.5", 1029 | "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5" 1030 | } 1031 | }, 1032 | "@babel/plugin-transform-sticky-regex": { 1033 | "version": "7.22.5", 1034 | "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.22.5.tgz", 1035 | "integrity": "sha512-zf7LuNpHG0iEeiyCNwX4j3gDg1jgt1k3ZdXBKbZSoA3BbGQGvMiSvfbZRR3Dr3aeJe3ooWFZxOOG3IRStYp2Bw==", 1036 | "dev": true, 1037 | "requires": { 1038 | "@babel/helper-plugin-utils": "^7.22.5" 1039 | } 1040 | }, 1041 | "@babel/plugin-transform-template-literals": { 1042 | "version": "7.22.5", 1043 | "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.22.5.tgz", 1044 | "integrity": "sha512-5ciOehRNf+EyUeewo8NkbQiUs4d6ZxiHo6BcBcnFlgiJfu16q0bQUw9Jvo0b0gBKFG1SMhDSjeKXSYuJLeFSMA==", 1045 | "dev": true, 1046 | "requires": { 1047 | "@babel/helper-plugin-utils": "^7.22.5" 1048 | } 1049 | }, 1050 | "@babel/plugin-transform-typeof-symbol": { 1051 | "version": "7.22.5", 1052 | "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.22.5.tgz", 1053 | "integrity": "sha512-bYkI5lMzL4kPii4HHEEChkD0rkc+nvnlR6+o/qdqR6zrm0Sv/nodmyLhlq2DO0YKLUNd2VePmPRjJXSBh9OIdA==", 1054 | "dev": true, 1055 | "requires": { 1056 | "@babel/helper-plugin-utils": "^7.22.5" 1057 | } 1058 | }, 1059 | "@babel/plugin-transform-unicode-escapes": { 1060 | "version": "7.22.5", 1061 | "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.22.5.tgz", 1062 | "integrity": "sha512-biEmVg1IYB/raUO5wT1tgfacCef15Fbzhkx493D3urBI++6hpJ+RFG4SrWMn0NEZLfvilqKf3QDrRVZHo08FYg==", 1063 | "dev": true, 1064 | "requires": { 1065 | "@babel/helper-plugin-utils": "^7.22.5" 1066 | } 1067 | }, 1068 | "@babel/plugin-transform-unicode-property-regex": { 1069 | "version": "7.22.5", 1070 | "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.22.5.tgz", 1071 | "integrity": "sha512-HCCIb+CbJIAE6sXn5CjFQXMwkCClcOfPCzTlilJ8cUatfzwHlWQkbtV0zD338u9dZskwvuOYTuuaMaA8J5EI5A==", 1072 | "dev": true, 1073 | "requires": { 1074 | "@babel/helper-create-regexp-features-plugin": "^7.22.5", 1075 | "@babel/helper-plugin-utils": "^7.22.5" 1076 | } 1077 | }, 1078 | "@babel/plugin-transform-unicode-regex": { 1079 | "version": "7.22.5", 1080 | "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.22.5.tgz", 1081 | "integrity": "sha512-028laaOKptN5vHJf9/Arr/HiJekMd41hOEZYvNsrsXqJ7YPYuX2bQxh31fkZzGmq3YqHRJzYFFAVYvKfMPKqyg==", 1082 | "dev": true, 1083 | "requires": { 1084 | "@babel/helper-create-regexp-features-plugin": "^7.22.5", 1085 | "@babel/helper-plugin-utils": "^7.22.5" 1086 | } 1087 | }, 1088 | "@babel/plugin-transform-unicode-sets-regex": { 1089 | "version": "7.22.5", 1090 | "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.22.5.tgz", 1091 | "integrity": "sha512-lhMfi4FC15j13eKrh3DnYHjpGj6UKQHtNKTbtc1igvAhRy4+kLhV07OpLcsN0VgDEw/MjAvJO4BdMJsHwMhzCg==", 1092 | "dev": true, 1093 | "requires": { 1094 | "@babel/helper-create-regexp-features-plugin": "^7.22.5", 1095 | "@babel/helper-plugin-utils": "^7.22.5" 1096 | } 1097 | }, 1098 | "@babel/preset-env": { 1099 | "version": "7.22.9", 1100 | "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.22.9.tgz", 1101 | "integrity": "sha512-wNi5H/Emkhll/bqPjsjQorSykrlfY5OWakd6AulLvMEytpKasMVUpVy8RL4qBIBs5Ac6/5i0/Rv0b/Fg6Eag/g==", 1102 | "dev": true, 1103 | "requires": { 1104 | "@babel/compat-data": "^7.22.9", 1105 | "@babel/helper-compilation-targets": "^7.22.9", 1106 | "@babel/helper-plugin-utils": "^7.22.5", 1107 | "@babel/helper-validator-option": "^7.22.5", 1108 | "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.22.5", 1109 | "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.22.5", 1110 | "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", 1111 | "@babel/plugin-syntax-async-generators": "^7.8.4", 1112 | "@babel/plugin-syntax-class-properties": "^7.12.13", 1113 | "@babel/plugin-syntax-class-static-block": "^7.14.5", 1114 | "@babel/plugin-syntax-dynamic-import": "^7.8.3", 1115 | "@babel/plugin-syntax-export-namespace-from": "^7.8.3", 1116 | "@babel/plugin-syntax-import-assertions": "^7.22.5", 1117 | "@babel/plugin-syntax-import-attributes": "^7.22.5", 1118 | "@babel/plugin-syntax-import-meta": "^7.10.4", 1119 | "@babel/plugin-syntax-json-strings": "^7.8.3", 1120 | "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", 1121 | "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", 1122 | "@babel/plugin-syntax-numeric-separator": "^7.10.4", 1123 | "@babel/plugin-syntax-object-rest-spread": "^7.8.3", 1124 | "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", 1125 | "@babel/plugin-syntax-optional-chaining": "^7.8.3", 1126 | "@babel/plugin-syntax-private-property-in-object": "^7.14.5", 1127 | "@babel/plugin-syntax-top-level-await": "^7.14.5", 1128 | "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", 1129 | "@babel/plugin-transform-arrow-functions": "^7.22.5", 1130 | "@babel/plugin-transform-async-generator-functions": "^7.22.7", 1131 | "@babel/plugin-transform-async-to-generator": "^7.22.5", 1132 | "@babel/plugin-transform-block-scoped-functions": "^7.22.5", 1133 | "@babel/plugin-transform-block-scoping": "^7.22.5", 1134 | "@babel/plugin-transform-class-properties": "^7.22.5", 1135 | "@babel/plugin-transform-class-static-block": "^7.22.5", 1136 | "@babel/plugin-transform-classes": "^7.22.6", 1137 | "@babel/plugin-transform-computed-properties": "^7.22.5", 1138 | "@babel/plugin-transform-destructuring": "^7.22.5", 1139 | "@babel/plugin-transform-dotall-regex": "^7.22.5", 1140 | "@babel/plugin-transform-duplicate-keys": "^7.22.5", 1141 | "@babel/plugin-transform-dynamic-import": "^7.22.5", 1142 | "@babel/plugin-transform-exponentiation-operator": "^7.22.5", 1143 | "@babel/plugin-transform-export-namespace-from": "^7.22.5", 1144 | "@babel/plugin-transform-for-of": "^7.22.5", 1145 | "@babel/plugin-transform-function-name": "^7.22.5", 1146 | "@babel/plugin-transform-json-strings": "^7.22.5", 1147 | "@babel/plugin-transform-literals": "^7.22.5", 1148 | "@babel/plugin-transform-logical-assignment-operators": "^7.22.5", 1149 | "@babel/plugin-transform-member-expression-literals": "^7.22.5", 1150 | "@babel/plugin-transform-modules-amd": "^7.22.5", 1151 | "@babel/plugin-transform-modules-commonjs": "^7.22.5", 1152 | "@babel/plugin-transform-modules-systemjs": "^7.22.5", 1153 | "@babel/plugin-transform-modules-umd": "^7.22.5", 1154 | "@babel/plugin-transform-named-capturing-groups-regex": "^7.22.5", 1155 | "@babel/plugin-transform-new-target": "^7.22.5", 1156 | "@babel/plugin-transform-nullish-coalescing-operator": "^7.22.5", 1157 | "@babel/plugin-transform-numeric-separator": "^7.22.5", 1158 | "@babel/plugin-transform-object-rest-spread": "^7.22.5", 1159 | "@babel/plugin-transform-object-super": "^7.22.5", 1160 | "@babel/plugin-transform-optional-catch-binding": "^7.22.5", 1161 | "@babel/plugin-transform-optional-chaining": "^7.22.6", 1162 | "@babel/plugin-transform-parameters": "^7.22.5", 1163 | "@babel/plugin-transform-private-methods": "^7.22.5", 1164 | "@babel/plugin-transform-private-property-in-object": "^7.22.5", 1165 | "@babel/plugin-transform-property-literals": "^7.22.5", 1166 | "@babel/plugin-transform-regenerator": "^7.22.5", 1167 | "@babel/plugin-transform-reserved-words": "^7.22.5", 1168 | "@babel/plugin-transform-shorthand-properties": "^7.22.5", 1169 | "@babel/plugin-transform-spread": "^7.22.5", 1170 | "@babel/plugin-transform-sticky-regex": "^7.22.5", 1171 | "@babel/plugin-transform-template-literals": "^7.22.5", 1172 | "@babel/plugin-transform-typeof-symbol": "^7.22.5", 1173 | "@babel/plugin-transform-unicode-escapes": "^7.22.5", 1174 | "@babel/plugin-transform-unicode-property-regex": "^7.22.5", 1175 | "@babel/plugin-transform-unicode-regex": "^7.22.5", 1176 | "@babel/plugin-transform-unicode-sets-regex": "^7.22.5", 1177 | "@babel/preset-modules": "^0.1.5", 1178 | "@babel/types": "^7.22.5", 1179 | "babel-plugin-polyfill-corejs2": "^0.4.4", 1180 | "babel-plugin-polyfill-corejs3": "^0.8.2", 1181 | "babel-plugin-polyfill-regenerator": "^0.5.1", 1182 | "core-js-compat": "^3.31.0", 1183 | "semver": "^6.3.1" 1184 | }, 1185 | "dependencies": { 1186 | "semver": { 1187 | "version": "6.3.1", 1188 | "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", 1189 | "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", 1190 | "dev": true 1191 | } 1192 | } 1193 | }, 1194 | "@babel/preset-modules": { 1195 | "version": "0.1.5", 1196 | "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.5.tgz", 1197 | "integrity": "sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA==", 1198 | "dev": true, 1199 | "requires": { 1200 | "@babel/helper-plugin-utils": "^7.0.0", 1201 | "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", 1202 | "@babel/plugin-transform-dotall-regex": "^7.4.4", 1203 | "@babel/types": "^7.4.4", 1204 | "esutils": "^2.0.2" 1205 | } 1206 | }, 1207 | "@babel/preset-react": { 1208 | "version": "7.22.5", 1209 | "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.22.5.tgz", 1210 | "integrity": "sha512-M+Is3WikOpEJHgR385HbuCITPTaPRaNkibTEa9oiofmJvIsrceb4yp9RL9Kb+TE8LznmeyZqpP+Lopwcx59xPQ==", 1211 | "dev": true, 1212 | "requires": { 1213 | "@babel/helper-plugin-utils": "^7.22.5", 1214 | "@babel/helper-validator-option": "^7.22.5", 1215 | "@babel/plugin-transform-react-display-name": "^7.22.5", 1216 | "@babel/plugin-transform-react-jsx": "^7.22.5", 1217 | "@babel/plugin-transform-react-jsx-development": "^7.22.5", 1218 | "@babel/plugin-transform-react-pure-annotations": "^7.22.5" 1219 | } 1220 | }, 1221 | "@babel/regjsgen": { 1222 | "version": "0.8.0", 1223 | "resolved": "https://registry.npmjs.org/@babel/regjsgen/-/regjsgen-0.8.0.tgz", 1224 | "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==", 1225 | "dev": true 1226 | }, 1227 | "@babel/runtime": { 1228 | "version": "7.22.6", 1229 | "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.6.tgz", 1230 | "integrity": "sha512-wDb5pWm4WDdF6LFUde3Jl8WzPA+3ZbxYqkC6xAXuD3irdEHN1k0NfTRrJD8ZD378SJ61miMLCqIOXYhd8x+AJQ==", 1231 | "dev": true, 1232 | "requires": { 1233 | "regenerator-runtime": "^0.13.11" 1234 | } 1235 | }, 1236 | "@babel/template": { 1237 | "version": "7.22.5", 1238 | "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.5.tgz", 1239 | "integrity": "sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw==", 1240 | "dev": true, 1241 | "requires": { 1242 | "@babel/code-frame": "^7.22.5", 1243 | "@babel/parser": "^7.22.5", 1244 | "@babel/types": "^7.22.5" 1245 | } 1246 | }, 1247 | "@babel/traverse": { 1248 | "version": "7.22.8", 1249 | "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.8.tgz", 1250 | "integrity": "sha512-y6LPR+wpM2I3qJrsheCTwhIinzkETbplIgPBbwvqPKc+uljeA5gP+3nP8irdYt1mjQaDnlIcG+dw8OjAco4GXw==", 1251 | "dev": true, 1252 | "requires": { 1253 | "@babel/code-frame": "^7.22.5", 1254 | "@babel/generator": "^7.22.7", 1255 | "@babel/helper-environment-visitor": "^7.22.5", 1256 | "@babel/helper-function-name": "^7.22.5", 1257 | "@babel/helper-hoist-variables": "^7.22.5", 1258 | "@babel/helper-split-export-declaration": "^7.22.6", 1259 | "@babel/parser": "^7.22.7", 1260 | "@babel/types": "^7.22.5", 1261 | "debug": "^4.1.0", 1262 | "globals": "^11.1.0" 1263 | } 1264 | }, 1265 | "@babel/types": { 1266 | "version": "7.22.5", 1267 | "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.5.tgz", 1268 | "integrity": "sha512-zo3MIHGOkPOfoRXitsgHLjEXmlDaD/5KU1Uzuc9GNiZPhSqVxVRtxuPaSBZDsYZ9qV88AjtMtWW7ww98loJ9KA==", 1269 | "dev": true, 1270 | "requires": { 1271 | "@babel/helper-string-parser": "^7.22.5", 1272 | "@babel/helper-validator-identifier": "^7.22.5", 1273 | "to-fast-properties": "^2.0.0" 1274 | } 1275 | }, 1276 | "@jridgewell/gen-mapping": { 1277 | "version": "0.3.3", 1278 | "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", 1279 | "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", 1280 | "dev": true, 1281 | "requires": { 1282 | "@jridgewell/set-array": "^1.0.1", 1283 | "@jridgewell/sourcemap-codec": "^1.4.10", 1284 | "@jridgewell/trace-mapping": "^0.3.9" 1285 | } 1286 | }, 1287 | "@jridgewell/resolve-uri": { 1288 | "version": "3.1.0", 1289 | "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", 1290 | "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", 1291 | "dev": true 1292 | }, 1293 | "@jridgewell/set-array": { 1294 | "version": "1.1.2", 1295 | "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", 1296 | "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", 1297 | "dev": true 1298 | }, 1299 | "@jridgewell/sourcemap-codec": { 1300 | "version": "1.4.14", 1301 | "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", 1302 | "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", 1303 | "dev": true 1304 | }, 1305 | "@jridgewell/trace-mapping": { 1306 | "version": "0.3.18", 1307 | "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz", 1308 | "integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==", 1309 | "dev": true, 1310 | "requires": { 1311 | "@jridgewell/resolve-uri": "3.1.0", 1312 | "@jridgewell/sourcemap-codec": "1.4.14" 1313 | } 1314 | }, 1315 | "@nicolo-ribaudo/chokidar-2": { 1316 | "version": "2.1.8-no-fsevents.3", 1317 | "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/chokidar-2/-/chokidar-2-2.1.8-no-fsevents.3.tgz", 1318 | "integrity": "sha512-s88O1aVtXftvp5bCPB7WnmXc5IwOZZ7YPuwNPt+GtOOXpPvad1LfbmjYv+qII7zP6RU2QGnqve27dnLycEnyEQ==", 1319 | "dev": true, 1320 | "optional": true 1321 | }, 1322 | "@types/json-schema": { 1323 | "version": "7.0.12", 1324 | "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz", 1325 | "integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==", 1326 | "dev": true 1327 | }, 1328 | "ajv": { 1329 | "version": "8.12.0", 1330 | "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", 1331 | "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", 1332 | "dev": true, 1333 | "requires": { 1334 | "fast-deep-equal": "^3.1.1", 1335 | "json-schema-traverse": "^1.0.0", 1336 | "require-from-string": "^2.0.2", 1337 | "uri-js": "^4.2.2" 1338 | } 1339 | }, 1340 | "ajv-formats": { 1341 | "version": "2.1.1", 1342 | "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", 1343 | "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", 1344 | "dev": true, 1345 | "requires": { 1346 | "ajv": "^8.0.0" 1347 | } 1348 | }, 1349 | "ajv-keywords": { 1350 | "version": "5.1.0", 1351 | "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", 1352 | "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", 1353 | "dev": true, 1354 | "requires": { 1355 | "fast-deep-equal": "^3.1.3" 1356 | } 1357 | }, 1358 | "ansi-styles": { 1359 | "version": "3.2.1", 1360 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", 1361 | "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", 1362 | "dev": true, 1363 | "requires": { 1364 | "color-convert": "^1.9.0" 1365 | } 1366 | }, 1367 | "anymatch": { 1368 | "version": "3.1.3", 1369 | "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", 1370 | "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", 1371 | "dev": true, 1372 | "optional": true, 1373 | "requires": { 1374 | "normalize-path": "^3.0.0", 1375 | "picomatch": "^2.0.4" 1376 | } 1377 | }, 1378 | "babel-loader": { 1379 | "version": "9.1.3", 1380 | "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.1.3.tgz", 1381 | "integrity": "sha512-xG3ST4DglodGf8qSwv0MdeWLhrDsw/32QMdTO5T1ZIp9gQur0HkCyFs7Awskr10JKXFXwpAhiCuYX5oGXnRGbw==", 1382 | "dev": true, 1383 | "requires": { 1384 | "find-cache-dir": "^4.0.0", 1385 | "schema-utils": "^4.0.0" 1386 | } 1387 | }, 1388 | "babel-plugin-polyfill-corejs2": { 1389 | "version": "0.4.4", 1390 | "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.4.tgz", 1391 | "integrity": "sha512-9WeK9snM1BfxB38goUEv2FLnA6ja07UMfazFHzCXUb3NyDZAwfXvQiURQ6guTTMeHcOsdknULm1PDhs4uWtKyA==", 1392 | "dev": true, 1393 | "requires": { 1394 | "@babel/compat-data": "^7.22.6", 1395 | "@babel/helper-define-polyfill-provider": "^0.4.1", 1396 | "@nicolo-ribaudo/semver-v6": "^6.3.3" 1397 | }, 1398 | "dependencies": { 1399 | "@nicolo-ribaudo/semver-v6": { 1400 | "version": "6.3.3", 1401 | "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/semver-v6/-/semver-v6-6.3.3.tgz", 1402 | "integrity": "sha512-3Yc1fUTs69MG/uZbJlLSI3JISMn2UV2rg+1D/vROUqZyh3l6iYHCs7GMp+M40ZD7yOdDbYjJcU1oTJhrc+dGKg==", 1403 | "dev": true 1404 | } 1405 | } 1406 | }, 1407 | "babel-plugin-polyfill-corejs3": { 1408 | "version": "0.8.2", 1409 | "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.2.tgz", 1410 | "integrity": "sha512-Cid+Jv1BrY9ReW9lIfNlNpsI53N+FN7gE+f73zLAUbr9C52W4gKLWSByx47pfDJsEysojKArqOtOKZSVIIUTuQ==", 1411 | "dev": true, 1412 | "requires": { 1413 | "@babel/helper-define-polyfill-provider": "^0.4.1", 1414 | "core-js-compat": "^3.31.0" 1415 | } 1416 | }, 1417 | "babel-plugin-polyfill-regenerator": { 1418 | "version": "0.5.1", 1419 | "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.1.tgz", 1420 | "integrity": "sha512-L8OyySuI6OSQ5hFy9O+7zFjyr4WhAfRjLIOkhQGYl+emwJkd/S4XXT1JpfrgR1jrQ1NcGiOh+yAdGlF8pnC3Jw==", 1421 | "dev": true, 1422 | "requires": { 1423 | "@babel/helper-define-polyfill-provider": "^0.4.1" 1424 | } 1425 | }, 1426 | "balanced-match": { 1427 | "version": "1.0.2", 1428 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 1429 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", 1430 | "dev": true 1431 | }, 1432 | "binary-extensions": { 1433 | "version": "2.2.0", 1434 | "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", 1435 | "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", 1436 | "dev": true, 1437 | "optional": true 1438 | }, 1439 | "brace-expansion": { 1440 | "version": "1.1.11", 1441 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 1442 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 1443 | "dev": true, 1444 | "requires": { 1445 | "balanced-match": "^1.0.0", 1446 | "concat-map": "0.0.1" 1447 | } 1448 | }, 1449 | "braces": { 1450 | "version": "3.0.2", 1451 | "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", 1452 | "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", 1453 | "dev": true, 1454 | "optional": true, 1455 | "requires": { 1456 | "fill-range": "^7.0.1" 1457 | } 1458 | }, 1459 | "browserslist": { 1460 | "version": "4.21.9", 1461 | "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.9.tgz", 1462 | "integrity": "sha512-M0MFoZzbUrRU4KNfCrDLnvyE7gub+peetoTid3TBIqtunaDJyXlwhakT+/VkvSXcfIzFfK/nkCs4nmyTmxdNSg==", 1463 | "dev": true, 1464 | "requires": { 1465 | "caniuse-lite": "^1.0.30001503", 1466 | "electron-to-chromium": "^1.4.431", 1467 | "node-releases": "^2.0.12", 1468 | "update-browserslist-db": "^1.0.11" 1469 | } 1470 | }, 1471 | "caniuse-lite": { 1472 | "version": "1.0.30001515", 1473 | "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001515.tgz", 1474 | "integrity": "sha512-eEFDwUOZbE24sb+Ecsx3+OvNETqjWIdabMy52oOkIgcUtAsQifjUG9q4U9dgTHJM2mfk4uEPxc0+xuFdJ629QA==", 1475 | "dev": true 1476 | }, 1477 | "chalk": { 1478 | "version": "2.4.2", 1479 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", 1480 | "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", 1481 | "dev": true, 1482 | "requires": { 1483 | "ansi-styles": "^3.2.1", 1484 | "escape-string-regexp": "^1.0.5", 1485 | "supports-color": "^5.3.0" 1486 | } 1487 | }, 1488 | "chokidar": { 1489 | "version": "3.5.3", 1490 | "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", 1491 | "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", 1492 | "dev": true, 1493 | "optional": true, 1494 | "requires": { 1495 | "anymatch": "~3.1.2", 1496 | "braces": "~3.0.2", 1497 | "fsevents": "~2.3.2", 1498 | "glob-parent": "~5.1.2", 1499 | "is-binary-path": "~2.1.0", 1500 | "is-glob": "~4.0.1", 1501 | "normalize-path": "~3.0.0", 1502 | "readdirp": "~3.6.0" 1503 | } 1504 | }, 1505 | "color-convert": { 1506 | "version": "1.9.3", 1507 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", 1508 | "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", 1509 | "dev": true, 1510 | "requires": { 1511 | "color-name": "1.1.3" 1512 | } 1513 | }, 1514 | "color-name": { 1515 | "version": "1.1.3", 1516 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", 1517 | "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", 1518 | "dev": true 1519 | }, 1520 | "commander": { 1521 | "version": "4.1.1", 1522 | "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", 1523 | "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", 1524 | "dev": true 1525 | }, 1526 | "common-path-prefix": { 1527 | "version": "3.0.0", 1528 | "resolved": "https://registry.npmjs.org/common-path-prefix/-/common-path-prefix-3.0.0.tgz", 1529 | "integrity": "sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==", 1530 | "dev": true 1531 | }, 1532 | "concat-map": { 1533 | "version": "0.0.1", 1534 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 1535 | "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", 1536 | "dev": true 1537 | }, 1538 | "convert-source-map": { 1539 | "version": "1.9.0", 1540 | "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", 1541 | "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", 1542 | "dev": true 1543 | }, 1544 | "core-js-compat": { 1545 | "version": "3.31.1", 1546 | "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.31.1.tgz", 1547 | "integrity": "sha512-wIDWd2s5/5aJSdpOJHfSibxNODxoGoWOBHt8JSPB41NOE94M7kuTPZCYLOlTtuoXTsBPKobpJ6T+y0SSy5L9SA==", 1548 | "dev": true, 1549 | "requires": { 1550 | "browserslist": "^4.21.9" 1551 | } 1552 | }, 1553 | "debug": { 1554 | "version": "4.3.4", 1555 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", 1556 | "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", 1557 | "dev": true, 1558 | "requires": { 1559 | "ms": "2.1.2" 1560 | } 1561 | }, 1562 | "electron-to-chromium": { 1563 | "version": "1.4.460", 1564 | "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.460.tgz", 1565 | "integrity": "sha512-kKiHnbrHME7z8E6AYaw0ehyxY5+hdaRmeUbjBO22LZMdqTYCO29EvF0T1cQ3pJ1RN5fyMcHl1Lmcsdt9WWJpJQ==", 1566 | "dev": true 1567 | }, 1568 | "escalade": { 1569 | "version": "3.1.1", 1570 | "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", 1571 | "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", 1572 | "dev": true 1573 | }, 1574 | "escape-string-regexp": { 1575 | "version": "1.0.5", 1576 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 1577 | "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", 1578 | "dev": true 1579 | }, 1580 | "esutils": { 1581 | "version": "2.0.3", 1582 | "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", 1583 | "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", 1584 | "dev": true 1585 | }, 1586 | "fast-deep-equal": { 1587 | "version": "3.1.3", 1588 | "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", 1589 | "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", 1590 | "dev": true 1591 | }, 1592 | "fill-range": { 1593 | "version": "7.0.1", 1594 | "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", 1595 | "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", 1596 | "dev": true, 1597 | "optional": true, 1598 | "requires": { 1599 | "to-regex-range": "^5.0.1" 1600 | } 1601 | }, 1602 | "find-cache-dir": { 1603 | "version": "4.0.0", 1604 | "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-4.0.0.tgz", 1605 | "integrity": "sha512-9ZonPT4ZAK4a+1pUPVPZJapbi7O5qbbJPdYw/NOQWZZbVLdDTYM3A4R9z/DpAM08IDaFGsvPgiGZ82WEwUDWjg==", 1606 | "dev": true, 1607 | "requires": { 1608 | "common-path-prefix": "^3.0.0", 1609 | "pkg-dir": "^7.0.0" 1610 | } 1611 | }, 1612 | "find-up": { 1613 | "version": "6.3.0", 1614 | "resolved": "https://registry.npmjs.org/find-up/-/find-up-6.3.0.tgz", 1615 | "integrity": "sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==", 1616 | "dev": true, 1617 | "requires": { 1618 | "locate-path": "^7.1.0", 1619 | "path-exists": "^5.0.0" 1620 | } 1621 | }, 1622 | "fs-readdir-recursive": { 1623 | "version": "1.1.0", 1624 | "resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz", 1625 | "integrity": "sha512-GNanXlVr2pf02+sPN40XN8HG+ePaNcvM0q5mZBd668Obwb0yD5GiUbZOFgwn8kGMY6I3mdyDJzieUy3PTYyTRA==", 1626 | "dev": true 1627 | }, 1628 | "fs.realpath": { 1629 | "version": "1.0.0", 1630 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 1631 | "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", 1632 | "dev": true 1633 | }, 1634 | "fsevents": { 1635 | "version": "2.3.2", 1636 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", 1637 | "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", 1638 | "dev": true, 1639 | "optional": true 1640 | }, 1641 | "function-bind": { 1642 | "version": "1.1.1", 1643 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", 1644 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", 1645 | "dev": true 1646 | }, 1647 | "gensync": { 1648 | "version": "1.0.0-beta.2", 1649 | "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", 1650 | "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", 1651 | "dev": true 1652 | }, 1653 | "glob": { 1654 | "version": "7.2.3", 1655 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", 1656 | "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", 1657 | "dev": true, 1658 | "requires": { 1659 | "fs.realpath": "^1.0.0", 1660 | "inflight": "^1.0.4", 1661 | "inherits": "2", 1662 | "minimatch": "^3.1.1", 1663 | "once": "^1.3.0", 1664 | "path-is-absolute": "^1.0.0" 1665 | } 1666 | }, 1667 | "glob-parent": { 1668 | "version": "5.1.2", 1669 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", 1670 | "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", 1671 | "dev": true, 1672 | "optional": true, 1673 | "requires": { 1674 | "is-glob": "^4.0.1" 1675 | } 1676 | }, 1677 | "globals": { 1678 | "version": "11.12.0", 1679 | "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", 1680 | "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", 1681 | "dev": true 1682 | }, 1683 | "has": { 1684 | "version": "1.0.3", 1685 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", 1686 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", 1687 | "dev": true, 1688 | "requires": { 1689 | "function-bind": "^1.1.1" 1690 | } 1691 | }, 1692 | "has-flag": { 1693 | "version": "3.0.0", 1694 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", 1695 | "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", 1696 | "dev": true 1697 | }, 1698 | "inflight": { 1699 | "version": "1.0.6", 1700 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 1701 | "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", 1702 | "dev": true, 1703 | "requires": { 1704 | "once": "^1.3.0", 1705 | "wrappy": "1" 1706 | } 1707 | }, 1708 | "inherits": { 1709 | "version": "2.0.4", 1710 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 1711 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", 1712 | "dev": true 1713 | }, 1714 | "is-binary-path": { 1715 | "version": "2.1.0", 1716 | "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", 1717 | "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", 1718 | "dev": true, 1719 | "optional": true, 1720 | "requires": { 1721 | "binary-extensions": "^2.0.0" 1722 | } 1723 | }, 1724 | "is-core-module": { 1725 | "version": "2.12.1", 1726 | "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.1.tgz", 1727 | "integrity": "sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==", 1728 | "dev": true, 1729 | "requires": { 1730 | "has": "^1.0.3" 1731 | } 1732 | }, 1733 | "is-extglob": { 1734 | "version": "2.1.1", 1735 | "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", 1736 | "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", 1737 | "dev": true, 1738 | "optional": true 1739 | }, 1740 | "is-glob": { 1741 | "version": "4.0.3", 1742 | "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", 1743 | "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", 1744 | "dev": true, 1745 | "optional": true, 1746 | "requires": { 1747 | "is-extglob": "^2.1.1" 1748 | } 1749 | }, 1750 | "is-number": { 1751 | "version": "7.0.0", 1752 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", 1753 | "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", 1754 | "dev": true, 1755 | "optional": true 1756 | }, 1757 | "js-tokens": { 1758 | "version": "4.0.0", 1759 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", 1760 | "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", 1761 | "dev": true 1762 | }, 1763 | "jsesc": { 1764 | "version": "2.5.2", 1765 | "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", 1766 | "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", 1767 | "dev": true 1768 | }, 1769 | "json-schema-traverse": { 1770 | "version": "1.0.0", 1771 | "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", 1772 | "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", 1773 | "dev": true 1774 | }, 1775 | "json5": { 1776 | "version": "2.2.3", 1777 | "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", 1778 | "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", 1779 | "dev": true 1780 | }, 1781 | "locate-path": { 1782 | "version": "7.2.0", 1783 | "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz", 1784 | "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==", 1785 | "dev": true, 1786 | "requires": { 1787 | "p-locate": "^6.0.0" 1788 | } 1789 | }, 1790 | "lodash.debounce": { 1791 | "version": "4.0.8", 1792 | "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", 1793 | "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", 1794 | "dev": true 1795 | }, 1796 | "lru-cache": { 1797 | "version": "5.1.1", 1798 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", 1799 | "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", 1800 | "dev": true, 1801 | "requires": { 1802 | "yallist": "^3.0.2" 1803 | } 1804 | }, 1805 | "make-dir": { 1806 | "version": "2.1.0", 1807 | "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", 1808 | "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", 1809 | "dev": true, 1810 | "requires": { 1811 | "pify": "^4.0.1", 1812 | "semver": "^5.6.0" 1813 | } 1814 | }, 1815 | "minimatch": { 1816 | "version": "3.1.2", 1817 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", 1818 | "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", 1819 | "dev": true, 1820 | "requires": { 1821 | "brace-expansion": "^1.1.7" 1822 | } 1823 | }, 1824 | "ms": { 1825 | "version": "2.1.2", 1826 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 1827 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", 1828 | "dev": true 1829 | }, 1830 | "node-releases": { 1831 | "version": "2.0.13", 1832 | "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", 1833 | "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==", 1834 | "dev": true 1835 | }, 1836 | "normalize-path": { 1837 | "version": "3.0.0", 1838 | "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", 1839 | "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", 1840 | "dev": true, 1841 | "optional": true 1842 | }, 1843 | "once": { 1844 | "version": "1.4.0", 1845 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 1846 | "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", 1847 | "dev": true, 1848 | "requires": { 1849 | "wrappy": "1" 1850 | } 1851 | }, 1852 | "p-limit": { 1853 | "version": "4.0.0", 1854 | "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", 1855 | "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", 1856 | "dev": true, 1857 | "requires": { 1858 | "yocto-queue": "^1.0.0" 1859 | } 1860 | }, 1861 | "p-locate": { 1862 | "version": "6.0.0", 1863 | "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", 1864 | "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", 1865 | "dev": true, 1866 | "requires": { 1867 | "p-limit": "^4.0.0" 1868 | } 1869 | }, 1870 | "path-exists": { 1871 | "version": "5.0.0", 1872 | "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", 1873 | "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", 1874 | "dev": true 1875 | }, 1876 | "path-is-absolute": { 1877 | "version": "1.0.1", 1878 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 1879 | "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", 1880 | "dev": true 1881 | }, 1882 | "path-parse": { 1883 | "version": "1.0.7", 1884 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", 1885 | "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", 1886 | "dev": true 1887 | }, 1888 | "picocolors": { 1889 | "version": "1.0.0", 1890 | "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", 1891 | "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", 1892 | "dev": true 1893 | }, 1894 | "picomatch": { 1895 | "version": "2.3.1", 1896 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", 1897 | "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", 1898 | "dev": true, 1899 | "optional": true 1900 | }, 1901 | "pify": { 1902 | "version": "4.0.1", 1903 | "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", 1904 | "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", 1905 | "dev": true 1906 | }, 1907 | "pkg-dir": { 1908 | "version": "7.0.0", 1909 | "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-7.0.0.tgz", 1910 | "integrity": "sha512-Ie9z/WINcxxLp27BKOCHGde4ITq9UklYKDzVo1nhk5sqGEXU3FpkwP5GM2voTGJkGd9B3Otl+Q4uwSOeSUtOBA==", 1911 | "dev": true, 1912 | "requires": { 1913 | "find-up": "^6.3.0" 1914 | } 1915 | }, 1916 | "prettier": { 1917 | "version": "3.0.0", 1918 | "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.0.tgz", 1919 | "integrity": "sha512-zBf5eHpwHOGPC47h0zrPyNn+eAEIdEzfywMoYn2XPi0P44Zp0tSq64rq0xAREh4auw2cJZHo9QUob+NqCQky4g==", 1920 | "dev": true 1921 | }, 1922 | "punycode": { 1923 | "version": "2.3.0", 1924 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", 1925 | "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", 1926 | "dev": true 1927 | }, 1928 | "readdirp": { 1929 | "version": "3.6.0", 1930 | "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", 1931 | "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", 1932 | "dev": true, 1933 | "optional": true, 1934 | "requires": { 1935 | "picomatch": "^2.2.1" 1936 | } 1937 | }, 1938 | "regenerate": { 1939 | "version": "1.4.2", 1940 | "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", 1941 | "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", 1942 | "dev": true 1943 | }, 1944 | "regenerate-unicode-properties": { 1945 | "version": "10.1.0", 1946 | "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.0.tgz", 1947 | "integrity": "sha512-d1VudCLoIGitcU/hEg2QqvyGZQmdC0Lf8BqdOMXGFSvJP4bNV1+XqbPQeHHLD51Jh4QJJ225dlIFvY4Ly6MXmQ==", 1948 | "dev": true, 1949 | "requires": { 1950 | "regenerate": "^1.4.2" 1951 | } 1952 | }, 1953 | "regenerator-runtime": { 1954 | "version": "0.13.11", 1955 | "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", 1956 | "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", 1957 | "dev": true 1958 | }, 1959 | "regenerator-transform": { 1960 | "version": "0.15.1", 1961 | "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.1.tgz", 1962 | "integrity": "sha512-knzmNAcuyxV+gQCufkYcvOqX/qIIfHLv0u5x79kRxuGojfYVky1f15TzZEu2Avte8QGepvUNTnLskf8E6X6Vyg==", 1963 | "dev": true, 1964 | "requires": { 1965 | "@babel/runtime": "^7.8.4" 1966 | } 1967 | }, 1968 | "regexpu-core": { 1969 | "version": "5.3.2", 1970 | "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.3.2.tgz", 1971 | "integrity": "sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ==", 1972 | "dev": true, 1973 | "requires": { 1974 | "@babel/regjsgen": "^0.8.0", 1975 | "regenerate": "^1.4.2", 1976 | "regenerate-unicode-properties": "^10.1.0", 1977 | "regjsparser": "^0.9.1", 1978 | "unicode-match-property-ecmascript": "^2.0.0", 1979 | "unicode-match-property-value-ecmascript": "^2.1.0" 1980 | } 1981 | }, 1982 | "regjsparser": { 1983 | "version": "0.9.1", 1984 | "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz", 1985 | "integrity": "sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==", 1986 | "dev": true, 1987 | "requires": { 1988 | "jsesc": "~0.5.0" 1989 | }, 1990 | "dependencies": { 1991 | "jsesc": { 1992 | "version": "0.5.0", 1993 | "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", 1994 | "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", 1995 | "dev": true 1996 | } 1997 | } 1998 | }, 1999 | "require-from-string": { 2000 | "version": "2.0.2", 2001 | "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", 2002 | "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", 2003 | "dev": true 2004 | }, 2005 | "resolve": { 2006 | "version": "1.22.2", 2007 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", 2008 | "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==", 2009 | "dev": true, 2010 | "requires": { 2011 | "is-core-module": "^2.11.0", 2012 | "path-parse": "^1.0.7", 2013 | "supports-preserve-symlinks-flag": "^1.0.0" 2014 | } 2015 | }, 2016 | "schema-utils": { 2017 | "version": "4.2.0", 2018 | "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz", 2019 | "integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==", 2020 | "dev": true, 2021 | "requires": { 2022 | "@types/json-schema": "^7.0.9", 2023 | "ajv": "^8.9.0", 2024 | "ajv-formats": "^2.1.1", 2025 | "ajv-keywords": "^5.1.0" 2026 | } 2027 | }, 2028 | "semver": { 2029 | "version": "5.7.2", 2030 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", 2031 | "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", 2032 | "dev": true 2033 | }, 2034 | "slash": { 2035 | "version": "2.0.0", 2036 | "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", 2037 | "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", 2038 | "dev": true 2039 | }, 2040 | "supports-color": { 2041 | "version": "5.5.0", 2042 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", 2043 | "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", 2044 | "dev": true, 2045 | "requires": { 2046 | "has-flag": "^3.0.0" 2047 | } 2048 | }, 2049 | "supports-preserve-symlinks-flag": { 2050 | "version": "1.0.0", 2051 | "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", 2052 | "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", 2053 | "dev": true 2054 | }, 2055 | "to-fast-properties": { 2056 | "version": "2.0.0", 2057 | "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", 2058 | "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", 2059 | "dev": true 2060 | }, 2061 | "to-regex-range": { 2062 | "version": "5.0.1", 2063 | "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", 2064 | "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", 2065 | "dev": true, 2066 | "optional": true, 2067 | "requires": { 2068 | "is-number": "^7.0.0" 2069 | } 2070 | }, 2071 | "unicode-canonical-property-names-ecmascript": { 2072 | "version": "2.0.0", 2073 | "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", 2074 | "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", 2075 | "dev": true 2076 | }, 2077 | "unicode-match-property-ecmascript": { 2078 | "version": "2.0.0", 2079 | "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", 2080 | "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", 2081 | "dev": true, 2082 | "requires": { 2083 | "unicode-canonical-property-names-ecmascript": "^2.0.0", 2084 | "unicode-property-aliases-ecmascript": "^2.0.0" 2085 | } 2086 | }, 2087 | "unicode-match-property-value-ecmascript": { 2088 | "version": "2.1.0", 2089 | "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz", 2090 | "integrity": "sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==", 2091 | "dev": true 2092 | }, 2093 | "unicode-property-aliases-ecmascript": { 2094 | "version": "2.1.0", 2095 | "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", 2096 | "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", 2097 | "dev": true 2098 | }, 2099 | "update-browserslist-db": { 2100 | "version": "1.0.11", 2101 | "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz", 2102 | "integrity": "sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==", 2103 | "dev": true, 2104 | "requires": { 2105 | "escalade": "^3.1.1", 2106 | "picocolors": "^1.0.0" 2107 | } 2108 | }, 2109 | "uri-js": { 2110 | "version": "4.4.1", 2111 | "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", 2112 | "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", 2113 | "dev": true, 2114 | "requires": { 2115 | "punycode": "^2.1.0" 2116 | } 2117 | }, 2118 | "wrappy": { 2119 | "version": "1.0.2", 2120 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 2121 | "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", 2122 | "dev": true 2123 | }, 2124 | "yallist": { 2125 | "version": "3.1.1", 2126 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", 2127 | "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", 2128 | "dev": true 2129 | }, 2130 | "yocto-queue": { 2131 | "version": "1.0.0", 2132 | "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", 2133 | "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==", 2134 | "dev": true 2135 | } 2136 | } 2137 | } 2138 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chat-website", 3 | "version": "1.0.0", 4 | "description": "This is a real-time chat website that allows users to connect with each other and chat in real-time. It was built using the MERN stack (MongoDB, Express.js, React.js, and Node.js), Socket.io, Redux Toolkit, and Tailwind CSS.", 5 | "main": "index.js", 6 | "dependencies": {}, 7 | "devDependencies": { 8 | "@babel/cli": "^7.22.9", 9 | "@babel/core": "^7.22.9", 10 | "@babel/preset-env": "^7.22.9", 11 | "@babel/preset-react": "^7.22.5", 12 | "babel-loader": "^9.1.3", 13 | "prettier": "^3.0.0" 14 | }, 15 | "scripts": { 16 | "test": "echo \"Error: no test specified\" && exit 1" 17 | }, 18 | "repository": { 19 | "type": "git", 20 | "url": "git+https://github.com/ShakirFarhan/Realtime-Chat.git" 21 | }, 22 | "author": "Shakir Farhan", 23 | "license": "ISC", 24 | "bugs": { 25 | "url": "https://github.com/ShakirFarhan/Realtime-Chat/issues" 26 | }, 27 | "homepage": "https://github.com/ShakirFarhan/Realtime-Chat#readme" 28 | } 29 | -------------------------------------------------------------------------------- /server/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /.env -------------------------------------------------------------------------------- /server/controllers/chatControllers.js: -------------------------------------------------------------------------------- 1 | import Chat from '../models/chatModel.js'; 2 | import user from '../models/userModel.js'; 3 | 4 | export const accessChats = async (req, res) => { 5 | const { userId } = req.body; 6 | if (!userId) res.send({ message: "Provide User's Id" }); 7 | let chatExists = await Chat.find({ 8 | isGroup: false, 9 | $and: [ 10 | { users: { $elemMatch: { $eq: userId } } }, 11 | { users: { $elemMatch: { $eq: req.rootUserId } } }, 12 | ], 13 | }) 14 | .populate('users', '-password') 15 | .populate('latestMessage'); 16 | chatExists = await user.populate(chatExists, { 17 | path: 'latestMessage.sender', 18 | select: 'name email profilePic', 19 | }); 20 | if (chatExists.length > 0) { 21 | res.status(200).send(chatExists[0]); 22 | } else { 23 | let data = { 24 | chatName: 'sender', 25 | users: [userId, req.rootUserId], 26 | isGroup: false, 27 | }; 28 | try { 29 | const newChat = await Chat.create(data); 30 | const chat = await Chat.find({ _id: newChat._id }).populate( 31 | 'users', 32 | '-password' 33 | ); 34 | res.status(200).json(chat); 35 | } catch (error) { 36 | res.status(500).send(error); 37 | } 38 | } 39 | }; 40 | export const fetchAllChats = async (req, res) => { 41 | try { 42 | const chats = await Chat.find({ 43 | users: { $elemMatch: { $eq: req.rootUserId } }, 44 | }) 45 | .populate('users') 46 | .populate('latestMessage') 47 | .populate('groupAdmin') 48 | .sort({ updatedAt: -1 }); 49 | const finalChats = await user.populate(chats, { 50 | path: 'latestMessage.sender', 51 | select: 'name email profilePic', 52 | }); 53 | res.status(200).json(finalChats); 54 | } catch (error) { 55 | res.status(500).send(error); 56 | console.log(error); 57 | } 58 | }; 59 | export const creatGroup = async (req, res) => { 60 | const { chatName, users } = req.body; 61 | if (!chatName || !users) { 62 | res.status(400).json({ message: 'Please fill the fields' }); 63 | } 64 | const parsedUsers = JSON.parse(users); 65 | if (parsedUsers.length < 2) 66 | res.send(400).send('Group should contain more than 2 users'); 67 | parsedUsers.push(req.rootUser); 68 | try { 69 | const chat = await Chat.create({ 70 | chatName: chatName, 71 | users: parsedUsers, 72 | isGroup: true, 73 | groupAdmin: req.rootUserId, 74 | }); 75 | const createdChat = await Chat.findOne({ _id: chat._id }) 76 | .populate('users', '-password') 77 | .populate('groupAdmin', '-password'); 78 | // res.status(200).json(createdChat); 79 | res.send(createdChat); 80 | } catch (error) { 81 | res.sendStatus(500); 82 | } 83 | }; 84 | export const renameGroup = async (req, res) => { 85 | const { chatId, chatName } = req.body; 86 | if (!chatId || !chatName) 87 | res.status(400).send('Provide Chat id and Chat name'); 88 | try { 89 | const chat = await Chat.findByIdAndUpdate(chatId, { 90 | $set: { chatName }, 91 | }) 92 | .populate('users', '-password') 93 | .populate('groupAdmin', '-password'); 94 | if (!chat) res.status(404); 95 | res.status(200).send(chat); 96 | } catch (error) { 97 | res.status(500).send(error); 98 | console.log(error); 99 | } 100 | }; 101 | export const addToGroup = async (req, res) => { 102 | const { userId, chatId } = req.body; 103 | const existing = await Chat.findOne({ _id: chatId }); 104 | if (!existing.users.includes(userId)) { 105 | const chat = await Chat.findByIdAndUpdate(chatId, { 106 | $push: { users: userId }, 107 | }) 108 | .populate('groupAdmin', '-password') 109 | .populate('users', '-password'); 110 | if (!chat) res.status(404); 111 | res.status(200).send(chat); 112 | } else { 113 | res.status(409).send('user already exists'); 114 | } 115 | }; 116 | export const removeFromGroup = async (req, res) => { 117 | const { userId, chatId } = req.body; 118 | const existing = await Chat.findOne({ _id: chatId }); 119 | if (existing.users.includes(userId)) { 120 | Chat.findByIdAndUpdate(chatId, { 121 | $pull: { users: userId }, 122 | }) 123 | .populate('groupAdmin', '-password') 124 | .populate('users', '-password') 125 | .then((e) => res.status(200).send(e)) 126 | .catch((e) => res.status(404)); 127 | } else { 128 | res.status(409).send('user doesnt exists'); 129 | } 130 | }; 131 | export const removeContact = async (req, res) => {}; 132 | -------------------------------------------------------------------------------- /server/controllers/messageControllers.js: -------------------------------------------------------------------------------- 1 | import Message from '../models/messageModel.js'; 2 | import user from '../models/userModel.js'; 3 | import Chat from '../models/chatModel.js'; 4 | export const sendMessage = async (req, res) => { 5 | const { chatId, message } = req.body; 6 | try { 7 | let msg = await Message.create({ sender: req.rootUserId, message, chatId }); 8 | msg = await ( 9 | await msg.populate('sender', 'name profilePic email') 10 | ).populate({ 11 | path: 'chatId', 12 | select: 'chatName isGroup users', 13 | model: 'Chat', 14 | populate: { 15 | path: 'users', 16 | select: 'name email profilePic', 17 | model: 'User', 18 | }, 19 | }); 20 | await Chat.findByIdAndUpdate(chatId, { 21 | latestMessage: msg, 22 | }); 23 | res.status(200).send(msg); 24 | } catch (error) { 25 | console.log(error); 26 | res.status(500).json({ error: error }); 27 | } 28 | }; 29 | export const getMessages = async (req, res) => { 30 | const { chatId } = req.params; 31 | try { 32 | let messages = await Message.find({ chatId }) 33 | .populate({ 34 | path: 'sender', 35 | model: 'User', 36 | select: 'name profilePic email', 37 | }) 38 | .populate({ 39 | path: 'chatId', 40 | model: 'Chat', 41 | }); 42 | 43 | res.status(200).json(messages); 44 | } catch (error) { 45 | res.sendStatus(500).json({ error: error }); 46 | console.log(error); 47 | } 48 | }; 49 | -------------------------------------------------------------------------------- /server/controllers/user.js: -------------------------------------------------------------------------------- 1 | import user from '../models/userModel.js'; 2 | import bcrypt from 'bcryptjs'; 3 | import { OAuth2Client } from 'google-auth-library'; 4 | export const register = async (req, res) => { 5 | const { firstname, lastname, email, password } = req.body; 6 | try { 7 | const existingUser = await user.findOne({ email }); 8 | if (existingUser) 9 | return res.status(400).json({ error: 'User already Exits' }); 10 | const fullname = firstname + ' ' + lastname; 11 | const newuser = new user({ email, password, name: fullname }); 12 | const token = await newuser.generateAuthToken(); 13 | await newuser.save(); 14 | res.json({ message: 'success', token: token }); 15 | } catch (error) { 16 | console.log('Error in register ' + error); 17 | res.status(500).send(error); 18 | } 19 | }; 20 | export const login = async (req, res) => { 21 | const { email, password } = req.body; 22 | try { 23 | const valid = await user.findOne({ email }); 24 | if (!valid) res.status(200).json({ message: 'User dont exist' }); 25 | const validPassword = await bcrypt.compare(password, valid.password); 26 | if (!validPassword) { 27 | res.status(200).json({ message: 'Invalid Credentials' }); 28 | } else { 29 | const token = await valid.generateAuthToken(); 30 | await valid.save(); 31 | res.cookie('userToken', token, { 32 | httpOnly: true, 33 | maxAge: 24 * 60 * 60 * 1000, 34 | }); 35 | res.status(200).json({ token: token, status: 200 }); 36 | } 37 | } catch (error) { 38 | res.status(500).json({ error: error }); 39 | } 40 | }; 41 | export const validUser = async (req, res) => { 42 | try { 43 | const validuser = await user 44 | .findOne({ _id: req.rootUserId }) 45 | .select('-password'); 46 | if (!validuser) res.json({ message: 'user is not valid' }); 47 | res.status(201).json({ 48 | user: validuser, 49 | token: req.token, 50 | }); 51 | } catch (error) { 52 | res.status(500).json({ error: error }); 53 | console.log(error); 54 | } 55 | }; 56 | export const googleAuth = async (req, res) => { 57 | try { 58 | const { tokenId } = req.body; 59 | const client = new OAuth2Client(process.env.CLIENT_ID); 60 | const verify = await client.verifyIdToken({ 61 | idToken: tokenId, 62 | audience: process.env.CLIENT_ID, 63 | }); 64 | const { email_verified, email, name, picture } = verify.payload; 65 | if (!email_verified) res.json({ message: 'Email Not Verified' }); 66 | const userExist = await user.findOne({ email }).select('-password'); 67 | if (userExist) { 68 | res.cookie('userToken', tokenId, { 69 | httpOnly: true, 70 | maxAge: 24 * 60 * 60 * 1000, 71 | }); 72 | res.status(200).json({ token: tokenId, user: userExist }); 73 | } else { 74 | const password = email + process.env.CLIENT_ID; 75 | const newUser = await user({ 76 | name: name, 77 | profilePic: picture, 78 | password, 79 | email, 80 | }); 81 | await newUser.save(); 82 | res.cookie('userToken', tokenId, { 83 | httpOnly: true, 84 | maxAge: 24 * 60 * 60 * 1000, 85 | }); 86 | res 87 | .status(200) 88 | .json({ message: 'User registered Successfully', token: tokenId }); 89 | } 90 | } catch (error) { 91 | res.status(500).json({ error: error }); 92 | console.log('error in googleAuth backend' + error); 93 | } 94 | }; 95 | 96 | export const logout = (req, res) => { 97 | req.rootUser.tokens = req.rootUser.tokens.filter((e) => e.token != req.token); 98 | }; 99 | export const searchUsers = async (req, res) => { 100 | // const { search } = req.query; 101 | const search = req.query.search 102 | ? { 103 | $or: [ 104 | { name: { $regex: req.query.search, $options: 'i' } }, 105 | { email: { $regex: req.query.search, $options: 'i' } }, 106 | ], 107 | } 108 | : {}; 109 | 110 | const users = await user.find(search).find({ _id: { $ne: req.rootUserId } }); 111 | res.status(200).send(users); 112 | }; 113 | export const getUserById = async (req, res) => { 114 | const { id } = req.params; 115 | try { 116 | const selectedUser = await user.findOne({ _id: id }).select('-password'); 117 | res.status(200).json(selectedUser); 118 | } catch (error) { 119 | res.status(500).json({ error: error }); 120 | } 121 | }; 122 | export const updateInfo = async (req, res) => { 123 | const { id } = req.params; 124 | const { bio, name } = req.body; 125 | const updatedUser = await user.findByIdAndUpdate(id, { name, bio }); 126 | return updatedUser; 127 | }; 128 | -------------------------------------------------------------------------------- /server/index.js: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import dotenv from 'dotenv/config'; 3 | import mongoDBConnect from './mongoDB/connection.js'; 4 | import mongoose from 'mongoose'; 5 | import bodyParser from 'body-parser'; 6 | import cors from 'cors'; 7 | import userRoutes from './routes/user.js'; 8 | import chatRoutes from './routes/chat.js'; 9 | import messageRoutes from './routes/message.js'; 10 | import * as Server from 'socket.io'; 11 | 12 | const app = express(); 13 | const corsConfig = { 14 | origin: process.env.BASE_URL, 15 | credentials: true, 16 | }; 17 | const PORT=process.env.PORT || 8000 18 | 19 | app.use(bodyParser.json()); 20 | app.use(bodyParser.urlencoded({ extended: true })); 21 | app.use(cors(corsConfig)); 22 | app.use('/', userRoutes); 23 | app.use('/api/chat', chatRoutes); 24 | app.use('/api/message', messageRoutes); 25 | mongoose.set('strictQuery', false); 26 | mongoDBConnect(); 27 | const server = app.listen(PORT, () => { 28 | console.log(`Server Listening at PORT - ${PORT}`); 29 | }); 30 | const io = new Server.Server(server, { 31 | pingTimeout: 60000, 32 | cors: { 33 | origin: 'http://localhost:3000', 34 | }, 35 | }); 36 | io.on('connection', (socket) => { 37 | socket.on('setup', (userData) => { 38 | socket.join(userData.id); 39 | socket.emit('connected'); 40 | }); 41 | socket.on('join room', (room) => { 42 | socket.join(room); 43 | }); 44 | socket.on('typing', (room) => socket.in(room).emit('typing')); 45 | socket.on('stop typing', (room) => socket.in(room).emit('stop typing')); 46 | 47 | socket.on('new message', (newMessageRecieve) => { 48 | var chat = newMessageRecieve.chatId; 49 | if (!chat.users) console.log('chats.users is not defined'); 50 | chat.users.forEach((user) => { 51 | if (user._id == newMessageRecieve.sender._id) return; 52 | socket.in(user._id).emit('message recieved', newMessageRecieve); 53 | }); 54 | }); 55 | }); 56 | -------------------------------------------------------------------------------- /server/middleware/user.js: -------------------------------------------------------------------------------- 1 | import jwt from 'jsonwebtoken'; 2 | import user from '../models/userModel.js'; 3 | 4 | export const Auth = async (req, res, next) => { 5 | try { 6 | let token = req.headers.authorization.split(' ')[0]; //when using browser this line 7 | // let token = req.headers.authorization.split(' ')[1]; //when using postman this line 8 | if (token.length < 500) { 9 | const verifiedUser = jwt.verify(token, process.env.SECRET); 10 | const rootUser = await user 11 | .findOne({ _id: verifiedUser.id }) 12 | .select('-password'); 13 | req.token = token; 14 | req.rootUser = rootUser; 15 | req.rootUserId = rootUser._id; 16 | } else { 17 | let data = jwt.decode(token); 18 | req.rootUserEmail = data.email; 19 | const googleUser = await user 20 | .findOne({ email: req.rootUserEmail }) 21 | .select('-password'); 22 | req.rootUser = googleUser; 23 | req.token = token; 24 | req.rootUserId = googleUser._id; 25 | } 26 | next(); 27 | } catch (error) { 28 | // console.log(error); 29 | res.json({ error: 'Invalid Token' }); 30 | } 31 | }; 32 | -------------------------------------------------------------------------------- /server/models/chatModel.js: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose'; 2 | const chatSchema = mongoose.Schema( 3 | { 4 | photo: { 5 | type: String, 6 | default: 'https://cdn-icons-png.flaticon.com/512/9790/9790561.png', 7 | }, 8 | chatName: { 9 | type: String, 10 | }, 11 | isGroup: { 12 | type: Boolean, 13 | default: false, 14 | }, 15 | users: [ 16 | { 17 | type: mongoose.Schema.Types.ObjectId, 18 | ref: 'User', 19 | }, 20 | ], 21 | latestMessage: { 22 | type: mongoose.Schema.Types.ObjectId, 23 | ref: 'Message', 24 | }, 25 | groupAdmin: { 26 | type: mongoose.Schema.Types.ObjectId, 27 | ref: 'User', 28 | }, 29 | }, 30 | { 31 | timestamps: true, 32 | } 33 | ); 34 | const chatModel = mongoose.model('Chat', chatSchema); 35 | export default chatModel; 36 | -------------------------------------------------------------------------------- /server/models/messageModel.js: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | const messageSchema = mongoose.Schema( 3 | { 4 | sender: { 5 | type: mongoose.Schema.Types.ObjectId, 6 | ref: "User", 7 | }, 8 | message: { 9 | type: String, 10 | trim: true, 11 | }, 12 | chatId: { 13 | type: mongoose.Schema.Types.ObjectId, 14 | ref: "Chat", 15 | }, 16 | }, 17 | { 18 | timestamps: true, 19 | } 20 | ); 21 | const messageModel = mongoose.model("Message", messageSchema); 22 | export default messageModel; 23 | -------------------------------------------------------------------------------- /server/models/userModel.js: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose'; 2 | import bcrypt from 'bcryptjs'; 3 | import jwt from 'jsonwebtoken'; 4 | const userSchema = mongoose.Schema( 5 | { 6 | name: { 7 | type: String, 8 | required: true, 9 | }, 10 | email: { 11 | type: String, 12 | required: true, 13 | }, 14 | password: { 15 | type: String, 16 | required: true, 17 | }, 18 | bio: { 19 | type: String, 20 | default: 'Available', 21 | }, 22 | profilePic: { 23 | type: String, 24 | default: 25 | 'https://icon-library.com/images/anonymous-avatar-icon/anonymous-avatar-icon-25.jpg', 26 | }, 27 | contacts: [ 28 | { 29 | type: mongoose.Schema.Types.ObjectId, 30 | ref: 'User', 31 | }, 32 | ], 33 | }, 34 | { 35 | timestamps: true, 36 | } 37 | ); 38 | userSchema.pre('save', async function (next) { 39 | if (this.isModified('password')) { 40 | this.password = await bcrypt.hash(this.password, 12); 41 | } 42 | next(); 43 | }); 44 | userSchema.methods.generateAuthToken = async function () { 45 | try { 46 | let token = jwt.sign( 47 | { id: this._id, email: this.email }, 48 | process.env.SECRET, 49 | { 50 | expiresIn: '24h', 51 | } 52 | ); 53 | 54 | return token; 55 | } catch (error) { 56 | console.log('error while generating token'); 57 | } 58 | }; 59 | 60 | const userModel = mongoose.model('User', userSchema); 61 | export default userModel; 62 | -------------------------------------------------------------------------------- /server/mongoDB/connection.js: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | const mongoDBConnect = () => { 3 | try { 4 | mongoose.connect(process.env.URL, { 5 | useUnifiedTopology: true, 6 | useNewUrlParser: true, 7 | }); 8 | console.log("MongoDB - Connected"); 9 | } catch (error) { 10 | console.log("Error - MongoDB Connection " + error); 11 | } 12 | }; 13 | export default mongoDBConnect; 14 | -------------------------------------------------------------------------------- /server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "server", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "type": "module", 7 | "scripts": { 8 | "test": "echo \"Error: no test specified\" && exit 1", 9 | "start":"nodemon index.js" 10 | }, 11 | "author": "", 12 | "license": "ISC", 13 | "dependencies": { 14 | "bcryptjs": "^2.4.3", 15 | "body-parser": "^1.20.1", 16 | "cookie-parser": "^1.4.6", 17 | "cors": "^2.8.5", 18 | "dotenv": "^16.0.3", 19 | "express": "^4.18.2", 20 | "google-auth-library": "^8.7.0", 21 | "jsonwebtoken": "^9.0.0", 22 | "jwt-check-expiration": "^1.0.5", 23 | "jwt-decode": "^3.1.2", 24 | "mongoose": "^6.8.2", 25 | "nodemon": "^3.0.1", 26 | "socket.io": "^4.6.1" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /server/routes/chat.js: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import { Auth } from '../middleware/user.js'; 3 | const router = express.Router(); 4 | 5 | import { 6 | accessChats, 7 | fetchAllChats, 8 | creatGroup, 9 | renameGroup, 10 | addToGroup, 11 | removeFromGroup, 12 | } from '../controllers/chatControllers.js'; 13 | router.post('/', Auth, accessChats); 14 | router.get('/', Auth, fetchAllChats); 15 | router.post('/group', Auth, creatGroup); 16 | router.patch('/group/rename', Auth, renameGroup); 17 | router.patch('/groupAdd', Auth, addToGroup); 18 | router.patch('/groupRemove', Auth, removeFromGroup); 19 | router.delete('/removeuser', Auth); 20 | 21 | export default router; 22 | -------------------------------------------------------------------------------- /server/routes/message.js: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | const router = express.Router(); 3 | import { sendMessage, getMessages } from "../controllers/messageControllers.js"; 4 | import { Auth } from "../middleware/user.js"; 5 | router.post("/", Auth, sendMessage); 6 | router.get("/:chatId", Auth, getMessages); 7 | export default router; 8 | -------------------------------------------------------------------------------- /server/routes/user.js: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import { 3 | register, 4 | login, 5 | validUser, 6 | googleAuth, 7 | logout, 8 | searchUsers, 9 | updateInfo, 10 | getUserById, 11 | } from '../controllers/user.js'; 12 | import { Auth } from '../middleware/user.js'; 13 | const router = express.Router(); 14 | router.post('/auth/register', register); 15 | router.post('/auth/login', login); 16 | router.get('/auth/valid', Auth, validUser); 17 | router.get('/auth/logout', Auth, logout); 18 | router.post('/api/google', googleAuth); 19 | router.get('/api/user?', Auth, searchUsers); 20 | router.get('/api/users/:id', Auth, getUserById); 21 | router.patch('/api/users/update/:id', Auth, updateInfo); 22 | export default router; 23 | --------------------------------------------------------------------------------