From ba5de2565a08ebb9eb208908f9602c3d9aff29ee Mon Sep 17 00:00:00 2001 From: Anumoy Nandy Date: Wed, 1 May 2024 01:53:42 +0530 Subject: [PATCH 1/6] implemented real time movement of user --- ts-frontend/package-lock.json | 2 +- ts-frontend/src/context/QLocationContext.tsx | 14 ++++++- ts-frontend/src/hooks/ws/index.ts | 7 ---- ts-frontend/src/pages/Map.tsx | 39 +++++++++++--------- 4 files changed, 35 insertions(+), 27 deletions(-) diff --git a/ts-frontend/package-lock.json b/ts-frontend/package-lock.json index d3e86d9..f5c0e7d 100644 --- a/ts-frontend/package-lock.json +++ b/ts-frontend/package-lock.json @@ -4449,4 +4449,4 @@ } } } -} \ No newline at end of file +} diff --git a/ts-frontend/src/context/QLocationContext.tsx b/ts-frontend/src/context/QLocationContext.tsx index 31e9f53..6e90330 100644 --- a/ts-frontend/src/context/QLocationContext.tsx +++ b/ts-frontend/src/context/QLocationContext.tsx @@ -17,6 +17,8 @@ interface Location { interface QLocationsContextType { qLocations: Location[]; setQLocations: React.Dispatch>; + locationsUserIdSet: Set; + setLocationsUserIdSet: React.Dispatch>>; } const QLocationsContext = createContext( @@ -35,9 +37,19 @@ export const QLocationsProvider: FC<{ children: ReactNode }> = ({ children, }) => { const [qLocations, setQLocations] = useState([]); + const [locationsUserIdSet, setLocationsUserIdSet] = useState>( + new Set(), + ); return ( - + {children} ); diff --git a/ts-frontend/src/hooks/ws/index.ts b/ts-frontend/src/hooks/ws/index.ts index 35ff115..be81ebc 100644 --- a/ts-frontend/src/hooks/ws/index.ts +++ b/ts-frontend/src/hooks/ws/index.ts @@ -13,13 +13,6 @@ const useSocket = (url: string) => { const { locations, setLocations } = useLocations(); const { locationsUserIdSet, setLocationsUserIdSet } = useLocations(); - // const notifyA = (message: string): void => { - // toast.success(message); - // }; - // const notifyB = (message: string): void => { - // toast.error(message); - // }; - useEffect(() => { if (socket) { socket.onopen = () => { diff --git a/ts-frontend/src/pages/Map.tsx b/ts-frontend/src/pages/Map.tsx index 87d3346..1def94d 100644 --- a/ts-frontend/src/pages/Map.tsx +++ b/ts-frontend/src/pages/Map.tsx @@ -68,7 +68,8 @@ export const Map = () => { isWsConnected, } = useSocket(BASE_WS_URI); - const [currentUserPosition] = useState(getRandomPosition()); + const [currentUserPosition, setCurrentUserPosition] = + useState(getRandomPosition()); if (!apiKey) throw new Error("GOOGLE_API_KEY environment variable is not set"); @@ -108,7 +109,7 @@ export const Map = () => { const updateCurrentLocation = (position: Location) => { fetch(`${BASE_API_URI}/api/setLocation`, { - method: "POST", + method: "PUT", headers: { "Content-Type": "application/json", Authorization: `Bearer ${localStorage.getItem("jwt")}`, @@ -129,20 +130,14 @@ export const Map = () => { const socketCommSENDLOC = () => { if (socket) { - setTimeout(() => { - // setCurrentUserPosition(getRandomPosition()); - // const position = getRandomPosition(); - // setCurrentUserPosition(position); - sendMessage({ - type: "SEND_LOCATION", - payload: { - userId: currentUserUUID, - roomId: "202A", - position: currentUserPosition, - }, - }); - updateCurrentLocation(currentUserPosition); - }, 5000); + sendMessage({ + type: "SEND_LOCATION", + payload: { + userId: currentUserUUID, + roomId: "202A", + position: currentUserPosition, + }, + }); } }; @@ -170,6 +165,14 @@ export const Map = () => { } }, []); + useEffect(() => { + setInterval(() => { + setCurrentUserPosition(getRandomPosition()); + updateCurrentLocation(currentUserPosition); + socketCommSENDLOC(); + }, 10000); + }, []); + const handleSocketConnection = () => { // first close if already soc conn exist @@ -228,8 +231,8 @@ export const Map = () => { }, body: JSON.stringify({ userId: currentUserUUID, - latitude: 22.40456, - longitude: 88.126, + latitude: currentUserPosition.lat, + longitude: currentUserPosition.lng, thresholdDistance: sliderValue * 1000, age, gender, From eb3bab8801bc7d993389d925afd4f43ac1ff4e28 Mon Sep 17 00:00:00 2001 From: Manas Pratim Biwas Date: Wed, 1 May 2024 05:15:30 +0530 Subject: [PATCH 2/6] [fix]: font style across modals - fix font style in add/remove profile photo modal - fix font style in logout modal --- ts-frontend/src/styles/LogoutModal.css | 4 +++- ts-frontend/src/styles/ProfilePic.css | 7 ++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/ts-frontend/src/styles/LogoutModal.css b/ts-frontend/src/styles/LogoutModal.css index afa5418..35074fd 100644 --- a/ts-frontend/src/styles/LogoutModal.css +++ b/ts-frontend/src/styles/LogoutModal.css @@ -42,13 +42,15 @@ font-weight: 500; font-size: 18px; text-align: center; + font-family: "Black Ops One"; } .modalContent { padding: 10px; - font-size: 14px; color: #2c3e50; text-align: center; + font-size: 1rem; + font-family: "Courier Prime"; } .modalActions { diff --git a/ts-frontend/src/styles/ProfilePic.css b/ts-frontend/src/styles/ProfilePic.css index ae5e242..b05536b 100644 --- a/ts-frontend/src/styles/ProfilePic.css +++ b/ts-frontend/src/styles/ProfilePic.css @@ -87,6 +87,11 @@ border-radius: 20px; } +.changePic h2 { + font-size: 1.2rem; + font-family: "Black Ops One"; +} + .changePic div { padding: 15px 80px; } @@ -96,7 +101,7 @@ background: none; border: none; font-weight: bolder; - + font-family: "Courier Prime"; font-size: 15px; } From 655d9638faf55365329832866c4a9bdb98224e0d Mon Sep 17 00:00:00 2001 From: Manas Pratim Biwas Date: Wed, 1 May 2024 05:32:53 +0530 Subject: [PATCH 3/6] [fix]: friends page instructions & toast font - fix instruuctions to select age, gender and college in friendsPage - fix toast font to Black Ops One to match the UI --- ts-frontend/src/index.css | 12 ++++++++++++ ts-frontend/src/pages/Client.tsx | 1 + ts-frontend/src/pages/FriendsPage.tsx | 6 +++--- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/ts-frontend/src/index.css b/ts-frontend/src/index.css index 1f33146..dad4241 100644 --- a/ts-frontend/src/index.css +++ b/ts-frontend/src/index.css @@ -5,3 +5,15 @@ body { height: 100%; overflow: scroll; } + +.Toastify__toast--success { + font-family: 'Black Ops One'; +} + +.Toastify__toast--warning { + font-family: 'Black Ops One'; +} + +.Toastify__toast--error { + font-family: 'Black Ops One'; +} diff --git a/ts-frontend/src/pages/Client.tsx b/ts-frontend/src/pages/Client.tsx index 8cff66a..18b69e1 100644 --- a/ts-frontend/src/pages/Client.tsx +++ b/ts-frontend/src/pages/Client.tsx @@ -7,6 +7,7 @@ function Client() { useEffect(() => { if (localStorage.getItem("user") || localStorage.getItem("jwt")) { + console.log("You are authorized!"); } else { navigate("/auth"); } diff --git a/ts-frontend/src/pages/FriendsPage.tsx b/ts-frontend/src/pages/FriendsPage.tsx index c7190d0..9220180 100644 --- a/ts-frontend/src/pages/FriendsPage.tsx +++ b/ts-frontend/src/pages/FriendsPage.tsx @@ -472,7 +472,7 @@ function FriendsPage() { }} disabled={checkAgeValidation(prevAge)} helperText={ - !checkAgeValidation(age) && !canEdit + !canEdit ? "Please select your age" : "" } @@ -500,7 +500,7 @@ function FriendsPage() { value={gender} onChange={handleGenderChange} helperText={ - !checkGenderValidation(gender) && !canEdit + !canEdit ? "Please select your gender" : "" } @@ -532,7 +532,7 @@ function FriendsPage() { value={college} onChange={handleCollegeChange} helperText={ - !checkCollegeValidation(college) && !canEdit + !canEdit ? "Please select your college" : "" } From 34d7e2f8ea8bf112c19646ec435503a11aea105d Mon Sep 17 00:00:00 2001 From: Manas Pratim Biwas Date: Wed, 1 May 2024 05:35:41 +0530 Subject: [PATCH 4/6] [feat]: simulate real-time client movement --- ts-frontend/src/index.css | 6 +- ts-frontend/src/pages/FriendsPage.tsx | 18 +-- ts-frontend/src/pages/Map.tsx | 203 ++++++++++++++++---------- 3 files changed, 129 insertions(+), 98 deletions(-) diff --git a/ts-frontend/src/index.css b/ts-frontend/src/index.css index dad4241..b30becb 100644 --- a/ts-frontend/src/index.css +++ b/ts-frontend/src/index.css @@ -7,13 +7,13 @@ body { } .Toastify__toast--success { - font-family: 'Black Ops One'; + font-family: "Black Ops One"; } .Toastify__toast--warning { - font-family: 'Black Ops One'; + font-family: "Black Ops One"; } .Toastify__toast--error { - font-family: 'Black Ops One'; + font-family: "Black Ops One"; } diff --git a/ts-frontend/src/pages/FriendsPage.tsx b/ts-frontend/src/pages/FriendsPage.tsx index 9220180..377cf47 100644 --- a/ts-frontend/src/pages/FriendsPage.tsx +++ b/ts-frontend/src/pages/FriendsPage.tsx @@ -471,11 +471,7 @@ function FriendsPage() { shrink: true, }} disabled={checkAgeValidation(prevAge)} - helperText={ - !canEdit - ? "Please select your age" - : "" - } + helperText={!canEdit ? "Please select your age" : ""} value={age} onChange={handleAgeChange} /> @@ -499,11 +495,7 @@ function FriendsPage() { disabled={checkGenderValidation(prevGender)} value={gender} onChange={handleGenderChange} - helperText={ - !canEdit - ? "Please select your gender" - : "" - } + helperText={!canEdit ? "Please select your gender" : ""} > {genders.map((option) => ( @@ -531,11 +523,7 @@ function FriendsPage() { disabled={checkCollegeValidation(prevCollege)} value={college} onChange={handleCollegeChange} - helperText={ - !canEdit - ? "Please select your college" - : "" - } + helperText={!canEdit ? "Please select your college" : ""} > {colleges.map((option) => ( diff --git a/ts-frontend/src/pages/Map.tsx b/ts-frontend/src/pages/Map.tsx index 1def94d..b734a6f 100644 --- a/ts-frontend/src/pages/Map.tsx +++ b/ts-frontend/src/pages/Map.tsx @@ -71,6 +71,15 @@ export const Map = () => { const [currentUserPosition, setCurrentUserPosition] = useState(getRandomPosition()); + const [curruser, setcurrUser] = useState(); + const [age, setAge] = useState(50); + const [gender, setGender] = useState("Non Binary"); + const [college, setCollege] = useState("Calcutta University"); + const [sliderValue, setSliderValue] = useState(60); + const [isMinimize, setIsMinimize] = useState(false); + + const { qLocations, setQLocations } = useQLocations(); + if (!apiKey) throw new Error("GOOGLE_API_KEY environment variable is not set"); @@ -107,27 +116,6 @@ export const Map = () => { } }; - const updateCurrentLocation = (position: Location) => { - fetch(`${BASE_API_URI}/api/setLocation`, { - method: "PUT", - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${localStorage.getItem("jwt")}`, - }, - body: JSON.stringify({ - lat: position.lat, - lng: position.lng, - }), - }) - .then((res) => res.json()) - .then((data) => { - if (data.success) { - console.log("Successfully updated current location ..."); - } - }) - .catch((err) => console.log(err)); - }; - const socketCommSENDLOC = () => { if (socket) { sendMessage({ @@ -141,65 +129,29 @@ export const Map = () => { } }; - useEffect(() => { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - let intervalId: any; - if (socket) { - socket.onopen = () => { - console.log("WebSocket connection established from client side"); - socketCommJOINROOM(); - intervalId = setInterval(() => { - socketCommSENDLOC(); - }, 3000); - }; - } - - return () => { - clearInterval(intervalId); - }; - }, [socket]); - - useEffect(() => { - if (!localStorage.getItem("user")) { - navigate("/auth"); + const updateCurrentLocation = (position: Location) => { + if (position.lat != undefined && position.lng != undefined) { + fetch(`${BASE_API_URI}/api/setLocation`, { + method: "PUT", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${localStorage.getItem("jwt")}`, + }, + body: JSON.stringify({ + lat: position.lat, + lng: position.lng, + }), + }) + .then((res) => res.json()) + .then((data) => { + if (data.success) { + console.log("Successfully updated current location ..."); + } + }) + .catch((err) => console.log(err)); } - }, []); - - useEffect(() => { - setInterval(() => { - setCurrentUserPosition(getRandomPosition()); - updateCurrentLocation(currentUserPosition); - socketCommSENDLOC(); - }, 10000); - }, []); - - const handleSocketConnection = () => { - // first close if already soc conn exist - - closeConnection("202A", currentUserUUID); - - // open a new soc conn if doesn't exist - openConnection(); }; - const handleSocketDisconnection = () => { - // close if already soc conn exist - closeConnection("202A", currentUserUUID); - }; - - const handleCloseDialogBox = () => { - setClickedIndex(-1); - }; - - const [curruser, setcurrUser] = useState(); - const [age, setAge] = useState(50); - const [gender, setGender] = useState("Male"); - const [college, setCollege] = useState("Jadavpur University"); - const [sliderValue, setSliderValue] = useState(60); - const [isMinimize, setIsMinimize] = useState(false); - - const { qLocations, setQLocations } = useQLocations(); - const fetchCurrentUserDetails = () => { const userDetails = localStorage.getItem("user"); if (userDetails) { @@ -234,9 +186,9 @@ export const Map = () => { latitude: currentUserPosition.lat, longitude: currentUserPosition.lng, thresholdDistance: sliderValue * 1000, - age, - gender, - college, + age: age, + gender: gender, + college: college, }), }) .then((res) => { @@ -255,6 +207,97 @@ export const Map = () => { }); }; + useEffect(() => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let intervalId: any; + if (socket) { + socket.onopen = () => { + console.log("WebSocket connection established from client side"); + socketCommJOINROOM(); + intervalId = setInterval(() => { + socketCommSENDLOC(); + }, 3000); + }; + } + + return () => { + clearInterval(intervalId); + }; + }, [socket]); + + useEffect(() => { + if (!localStorage.getItem("user")) { + navigate("/auth"); + } + }, []); + + /* SIMULATE REAL-TIME USER MOVEMENT + + This useEffect is used to simulate the real time movement + of the client. This function runs after 10 seconds the first + time the component mounts and then this function runs after + every consecutive 10 seconds. + + 1. getRandomPosition() to get a random position + 2. setCurrentUserPosition() to the random position that was received + 3. updateCurrentLocation() to update the location to the Postgres + 4. socketCommSENDLOC() to send the data to the ws server + 5. updateDetailsOfQueriedUsers() to update the queried user as per + the new random location generated in a separate useEffect + with currentUserPosition in the dependency array + +*/ + + useEffect(() => { + // Initial timeout after 10 seconds + const initialTimeoutId = setTimeout(() => { + simulateRealTimeUserMovement(); + // Set up interval for consecutive invocations every 10 seconds + const intervalId = setInterval(simulateRealTimeUserMovement, 10000); + + // Cleanup function to clear the interval on component unmount + return () => clearInterval(intervalId); + }, 10000); + + // Cleanup function to clear the initial timeout on component unmount + return () => clearTimeout(initialTimeoutId); + }, []); + + const simulateRealTimeUserMovement = async () => { + setCurrentUserPosition(getRandomPosition()); + updateCurrentLocation(currentUserPosition); + socketCommSENDLOC(); + }; + + // Queries and updates the queried users automatically + // after 500 milliseconds on current user location change + useEffect(() => { + const initialTimeoutId = setTimeout(() => { + updateDetailsOfQueriedUsers(); + }, 500); + + return () => clearTimeout(initialTimeoutId); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [currentUserPosition]); + + const handleSocketConnection = () => { + // first close if already soc conn exist + + closeConnection("202A", currentUserUUID); + + // open a new soc conn if doesn't exist + openConnection(); + }; + + const handleSocketDisconnection = () => { + // close if already soc conn exist + closeConnection("202A", currentUserUUID); + }; + + const handleCloseDialogBox = () => { + setClickedIndex(-1); + }; + useEffect(() => { fetchCurrentUserDetails(); setTimeout(() => { From 29a4ba524d82c45d12a2dd61a7510dd9ca9883a3 Mon Sep 17 00:00:00 2001 From: Manas Pratim Biwas Date: Wed, 1 May 2024 13:12:48 +0530 Subject: [PATCH 5/6] [fix]: update show visibility button - remove checkbox - add switch --- ts-frontend/src/pages/FriendsPage.tsx | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/ts-frontend/src/pages/FriendsPage.tsx b/ts-frontend/src/pages/FriendsPage.tsx index 377cf47..964edc6 100644 --- a/ts-frontend/src/pages/FriendsPage.tsx +++ b/ts-frontend/src/pages/FriendsPage.tsx @@ -7,7 +7,10 @@ import TextField from "@mui/material/TextField"; import MenuItem from "@mui/material/MenuItem"; import FormGroup from "@mui/material/FormGroup"; import FormControlLabel from "@mui/material/FormControlLabel"; -import Checkbox from "@mui/material/Checkbox"; +import Switch from "@mui/material/Switch"; +// import Checkbox from "@mui/material/Checkbox"; +import VisibilityOffIcon from "@mui/icons-material/VisibilityOff"; +import VisibilityIcon from "@mui/icons-material/Visibility"; import Stack from "@mui/material/Stack"; import Button from "@mui/material/Button"; import Backdrop from "@mui/material/Backdrop"; @@ -535,17 +538,31 @@ function FriendsPage() {
- + + } label="Show my visibility" /> + + {visibility && checkVisibilityValidation() ? ( + + ) : visibility ? ( + + ) : ( + + )}
From 51654cc6a7b0c9b7587a1d98a8ea2597911c230d Mon Sep 17 00:00:00 2001 From: Manas Pratim Biwas Date: Wed, 1 May 2024 14:32:47 +0530 Subject: [PATCH 6/6] [fix]: ws auth - in addition to jwt validation checks if the user - is actually present in the mongoDB user database - cuts off users from ws auth in case the user has been deleted --- backend/ws/package-lock.json | 72 +++++ backend/ws/package.json | 1 + backend/ws/src/index.ts | 32 +- backend/ws/src/types/HttpStatusCodes.ts | 382 ++++++++++++++++++++++++ backend/ws/src/utils/index.ts | 21 +- 5 files changed, 502 insertions(+), 6 deletions(-) create mode 100644 backend/ws/src/types/HttpStatusCodes.ts diff --git a/backend/ws/package-lock.json b/backend/ws/package-lock.json index 22213f0..bc4b399 100644 --- a/backend/ws/package-lock.json +++ b/backend/ws/package-lock.json @@ -13,6 +13,7 @@ "@types/express": "^4.17.21", "@types/jsonwebtoken": "^9.0.6", "@types/ws": "^8.5.10", + "axios": "^1.6.8", "cors": "^2.8.5", "dotenv": "^16.4.5", "express": "^4.19.2", @@ -508,6 +509,21 @@ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/axios": { + "version": "1.6.8", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.8.tgz", + "integrity": "sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -690,6 +706,17 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -790,6 +817,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -1226,6 +1261,38 @@ "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", "dev": true }, + "node_modules/follow-redirects": { + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -2060,6 +2127,11 @@ "node": ">= 0.10" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, "node_modules/pstree.remy": { "version": "1.1.8", "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", diff --git a/backend/ws/package.json b/backend/ws/package.json index 3a95e0d..626bd43 100644 --- a/backend/ws/package.json +++ b/backend/ws/package.json @@ -47,6 +47,7 @@ "@types/express": "^4.17.21", "@types/jsonwebtoken": "^9.0.6", "@types/ws": "^8.5.10", + "axios": "^1.6.8", "cors": "^2.8.5", "dotenv": "^16.4.5", "express": "^4.19.2", diff --git a/backend/ws/src/index.ts b/backend/ws/src/index.ts index 7208d05..704ab46 100644 --- a/backend/ws/src/index.ts +++ b/backend/ws/src/index.ts @@ -1,5 +1,6 @@ import express from "express"; import url from "url"; +import axios from "axios"; import { WebSocketServer, WebSocket } from "ws"; import { INCOMING_MESSAGE, @@ -55,7 +56,7 @@ httpServer.on("upgrade", (req, socket, head) => { }); }); -wss.on("connection", function connection(ws: WebSocket, req) { +wss.on("connection", async function connection(ws: WebSocket, req) { console.log( `HTTP Server upgraded to WSS Server and is running on PORT ${PORT}`, ); @@ -67,9 +68,10 @@ wss.on("connection", function connection(ws: WebSocket, req) { const token: string = url.parse(req.url, true).query.token || ""; // const userId = extractUserId(token); + const checkIfAuthorized = await tokenIsValid(token); // Perform auth - if (!token || !tokenIsValid(token)) { - console.log("Unauthorized user"); + if (!token || !checkIfAuthorized) { + console.log("Unauthorized user from ws backend"); // Send an unauthorized message to the client ws.send( JSON.stringify({ @@ -95,7 +97,7 @@ wss.on("connection", function connection(ws: WebSocket, req) { const jsonData = JSON.parse(dataString); console.log("JSON DATA received from client : ", jsonData); - messageHandler(ws, jsonData); + messageHandler(ws, token, jsonData); // wss.clients.forEach(function each(client) { // if (client.readyState === WebSocket.OPEN) { // client.send(data, { binary: isBinary }); @@ -131,7 +133,11 @@ wss.on("close", () => { clearInterval(interval); }); -function messageHandler(ws: WebSocket, message: INCOMING_MESSAGE) { +function messageHandler( + ws: WebSocket, + token: string, + message: INCOMING_MESSAGE, +) { if (message.type == SUPPORTED_MESSAGES.PONG) { console.log("Pong received by ws backend"); const payload = message.payload; @@ -214,6 +220,22 @@ function messageHandler(ws: WebSocket, message: INCOMING_MESSAGE) { return; } + /* + Update the location to the Postgres database + via the ts-backend + */ + + // const response = axios.post("localhost:5050/api/addLocation", + // { + + // }, + // { + // headers : { + // "Authorization": `Bearer ${token}`, + // "Content-Type": "application/json" + // } + // }); + const outgoingPayload: OUTGOING_MESSAGE = { type: OUTGOING_SUPPORTED_MESSAGE.ADD_LOCATION, payload: { diff --git a/backend/ws/src/types/HttpStatusCodes.ts b/backend/ws/src/types/HttpStatusCodes.ts new file mode 100644 index 0000000..f514dec --- /dev/null +++ b/backend/ws/src/types/HttpStatusCodes.ts @@ -0,0 +1,382 @@ +"use strict"; + +/** + * Hypertext Transfer Protocol (HTTP) response status codes. + * @see {@link https://en.wikipedia.org/wiki/List_of_HTTP_status_codes} + */ +enum HttpStatusCode { + /** + * The server has received the request headers and the client should proceed to send the request body + * (in the case of a request for which a body needs to be sent; for example, a POST request). + * Sending a large request body to a server after a request has been rejected for inappropriate headers would be inefficient. + * To have a server check the request's headers, a client must send Expect: 100-continue as a header in its initial request + * and receive a 100 Continue status code in response before sending the body. The response 417 Expectation Failed indicates the request should not be continued. + */ + CONTINUE = 100, + + /** + * The requester has asked the server to switch protocols and the server has agreed to do so. + */ + SWITCHING_PROTOCOLS = 101, + + /** + * A WebDAV request may contain many sub-requests involving file operations, requiring a long time to complete the request. + * This code indicates that the server has received and is processing the request, but no response is available yet. + * This prevents the client from timing out and assuming the request was lost. + */ + PROCESSING = 102, + + /** + * Standard response for successful HTTP requests. + * The actual response will depend on the request method used. + * In a GET request, the response will contain an entity corresponding to the requested resource. + * In a POST request, the response will contain an entity describing or containing the result of the action. + */ + OK = 200, + + /** + * The request has been fulfilled, resulting in the creation of a new resource. + */ + CREATED = 201, + + /** + * The request has been accepted for processing, but the processing has not been completed. + * The request might or might not be eventually acted upon, and may be disallowed when processing occurs. + */ + ACCEPTED = 202, + + /** + * SINCE HTTP/1.1 + * The server is a transforming proxy that received a 200 OK from its origin, + * but is returning a modified version of the origin's response. + */ + NON_AUTHORITATIVE_INFORMATION = 203, + + /** + * The server successfully processed the request and is not returning any content. + */ + NO_CONTENT = 204, + + /** + * The server successfully processed the request, but is not returning any content. + * Unlike a 204 response, this response requires that the requester reset the document view. + */ + RESET_CONTENT = 205, + + /** + * The server is delivering only part of the resource (byte serving) due to a range header sent by the client. + * The range header is used by HTTP clients to enable resuming of interrupted downloads, + * or split a download into multiple simultaneous streams. + */ + PARTIAL_CONTENT = 206, + + /** + * The message body that follows is an XML message and can contain a number of separate response codes, + * depending on how many sub-requests were made. + */ + MULTI_STATUS = 207, + + /** + * The members of a DAV binding have already been enumerated in a preceding part of the (multistatus) response, + * and are not being included again. + */ + ALREADY_REPORTED = 208, + + /** + * The server has fulfilled a request for the resource, + * and the response is a representation of the result of one or more instance-manipulations applied to the current instance. + */ + IM_USED = 226, + + /** + * Indicates multiple options for the resource from which the client may choose (via agent-driven content negotiation). + * For example, this code could be used to present multiple video format options, + * to list files with different filename extensions, or to suggest word-sense disambiguation. + */ + MULTIPLE_CHOICES = 300, + + /** + * This and all future requests should be directed to the given URI. + */ + MOVED_PERMANENTLY = 301, + + /** + * This is an example of industry practice contradicting the standard. + * The HTTP/1.0 specification (RFC 1945) required the client to perform a temporary redirect + * (the original describing phrase was "Moved Temporarily"), but popular browsers implemented 302 + * with the functionality of a 303 See Other. Therefore, HTTP/1.1 added status codes 303 and 307 + * to distinguish between the two behaviours. However, some Web applications and frameworks + * use the 302 status code as if it were the 303. + */ + FOUND = 302, + + /** + * SINCE HTTP/1.1 + * The response to the request can be found under another URI using a GET method. + * When received in response to a POST (or PUT/DELETE), the client should presume that + * the server has received the data and should issue a redirect with a separate GET message. + */ + SEE_OTHER = 303, + + /** + * Indicates that the resource has not been modified since the version specified by the request headers If-Modified-Since or If-None-Match. + * In such case, there is no need to retransmit the resource since the client still has a previously-downloaded copy. + */ + NOT_MODIFIED = 304, + + /** + * SINCE HTTP/1.1 + * The requested resource is available only through a proxy, the address for which is provided in the response. + * Many HTTP clients (such as Mozilla and Internet Explorer) do not correctly handle responses with this status code, primarily for security reasons. + */ + USE_PROXY = 305, + + /** + * No longer used. Originally meant "Subsequent requests should use the specified proxy." + */ + SWITCH_PROXY = 306, + + /** + * SINCE HTTP/1.1 + * In this case, the request should be repeated with another URI; however, future requests should still use the original URI. + * In contrast to how 302 was historically implemented, the request method is not allowed to be changed when reissuing the original request. + * For example, a POST request should be repeated using another POST request. + */ + TEMPORARY_REDIRECT = 307, + + /** + * The request and all future requests should be repeated using another URI. + * 307 and 308 parallel the behaviors of 302 and 301, but do not allow the HTTP method to change. + * So, for example, submitting a form to a permanently redirected resource may continue smoothly. + */ + PERMANENT_REDIRECT = 308, + + /** + * The server cannot or will not process the request due to an apparent client error + * (e.g., malformed request syntax, too large size, invalid request message framing, or deceptive request routing). + */ + BAD_REQUEST = 400, + + /** + * Similar to 403 Forbidden, but specifically for use when authentication is required and has failed or has not yet + * been provided. The response must include a WWW-Authenticate header field containing a challenge applicable to the + * requested resource. See Basic access authentication and Digest access authentication. 401 semantically means + * "unauthenticated",i.e. the user does not have the necessary credentials. + */ + UNAUTHORIZED = 401, + + /** + * Reserved for future use. The original intention was that this code might be used as part of some form of digital + * cash or micro payment scheme, but that has not happened, and this code is not usually used. + * Google Developers API uses this status if a particular developer has exceeded the daily limit on requests. + */ + PAYMENT_REQUIRED = 402, + + /** + * The request was valid, but the server is refusing action. + * The user might not have the necessary permissions for a resource. + */ + FORBIDDEN = 403, + + /** + * The requested resource could not be found but may be available in the future. + * Subsequent requests by the client are permissible. + */ + NOT_FOUND = 404, + + /** + * A request method is not supported for the requested resource; + * for example, a GET request on a form that requires data to be presented via POST, or a PUT request on a read-only resource. + */ + METHOD_NOT_ALLOWED = 405, + + /** + * The requested resource is capable of generating only content not acceptable according to the Accept headers sent in the request. + */ + NOT_ACCEPTABLE = 406, + + /** + * The client must first authenticate itself with the proxy. + */ + PROXY_AUTHENTICATION_REQUIRED = 407, + + /** + * The server timed out waiting for the request. + * According to HTTP specifications: + * "The client did not produce a request within the time that the server was prepared to wait. The client MAY repeat the request without modifications at any later time." + */ + REQUEST_TIMEOUT = 408, + + /** + * Indicates that the request could not be processed because of conflict in the request, + * such as an edit conflict between multiple simultaneous updates. + */ + CONFLICT = 409, + + /** + * Indicates that the resource requested is no longer available and will not be available again. + * This should be used when a resource has been intentionally removed and the resource should be purged. + * Upon receiving a 410 status code, the client should not request the resource in the future. + * Clients such as search engines should remove the resource from their indices. + * Most use cases do not require clients and search engines to purge the resource, and a "404 Not Found" may be used instead. + */ + GONE = 410, + + /** + * The request did not specify the length of its content, which is required by the requested resource. + */ + LENGTH_REQUIRED = 411, + + /** + * The server does not meet one of the preconditions that the requester put on the request. + */ + PRECONDITION_FAILED = 412, + + /** + * The request is larger than the server is willing or able to process. Previously called "Request Entity Too Large". + */ + PAYLOAD_TOO_LARGE = 413, + + /** + * The URI provided was too long for the server to process. Often the result of too much data being encoded as a query-string of a GET request, + * in which case it should be converted to a POST request. + * Called "Request-URI Too Long" previously. + */ + URI_TOO_LONG = 414, + + /** + * The request entity has a media type which the server or resource does not support. + * For example, the client uploads an image as image/svg+xml, but the server requires that images use a different format. + */ + UNSUPPORTED_MEDIA_TYPE = 415, + + /** + * The client has asked for a portion of the file (byte serving), but the server cannot supply that portion. + * For example, if the client asked for a part of the file that lies beyond the end of the file. + * Called "Requested Range Not Satisfiable" previously. + */ + RANGE_NOT_SATISFIABLE = 416, + + /** + * The server cannot meet the requirements of the Expect request-header field. + */ + EXPECTATION_FAILED = 417, + + /** + * This code was defined in 1998 as one of the traditional IETF April Fools' jokes, in RFC 2324, Hyper Text Coffee Pot Control Protocol, + * and is not expected to be implemented by actual HTTP servers. The RFC specifies this code should be returned by + * teapots requested to brew coffee. This HTTP status is used as an Easter egg in some websites, including Google.com. + */ + I_AM_A_TEAPOT = 418, + + /** + * The request was directed at a server that is not able to produce a response (for example because a connection reuse). + */ + MISDIRECTED_REQUEST = 421, + + /** + * The request was well-formed but was unable to be followed due to semantic errors. + */ + UNPROCESSABLE_ENTITY = 422, + + /** + * The resource that is being accessed is locked. + */ + LOCKED = 423, + + /** + * The request failed due to failure of a previous request (e.g., a PROPPATCH). + */ + FAILED_DEPENDENCY = 424, + + /** + * The client should switch to a different protocol such as TLS/1.0, given in the Upgrade header field. + */ + UPGRADE_REQUIRED = 426, + + /** + * The origin server requires the request to be conditional. + * Intended to prevent "the 'lost update' problem, where a client + * GETs a resource's state, modifies it, and PUTs it back to the server, + * when meanwhile a third party has modified the state on the server, leading to a conflict." + */ + PRECONDITION_REQUIRED = 428, + + /** + * The user has sent too many requests in a given amount of time. Intended for use with rate-limiting schemes. + */ + TOO_MANY_REQUESTS = 429, + + /** + * The server is unwilling to process the request because either an individual header field, + * or all the header fields collectively, are too large. + */ + REQUEST_HEADER_FIELDS_TOO_LARGE = 431, + + /** + * A server operator has received a legal demand to deny access to a resource or to a set of resources + * that includes the requested resource. The code 451 was chosen as a reference to the novel Fahrenheit 451. + */ + UNAVAILABLE_FOR_LEGAL_REASONS = 451, + + /** + * A generic error message, given when an unexpected condition was encountered and no more specific message is suitable. + */ + INTERNAL_SERVER_ERROR = 500, + + /** + * The server either does not recognize the request method, or it lacks the ability to fulfill the request. + * Usually this implies future availability (e.g., a new feature of a web-service API). + */ + NOT_IMPLEMENTED = 501, + + /** + * The server was acting as a gateway or proxy and received an invalid response from the upstream server. + */ + BAD_GATEWAY = 502, + + /** + * The server is currently unavailable (because it is overloaded or down for maintenance). + * Generally, this is a temporary state. + */ + SERVICE_UNAVAILABLE = 503, + + /** + * The server was acting as a gateway or proxy and did not receive a timely response from the upstream server. + */ + GATEWAY_TIMEOUT = 504, + + /** + * The server does not support the HTTP protocol version used in the request + */ + HTTP_VERSION_NOT_SUPPORTED = 505, + + /** + * Transparent content negotiation for the request results in a circular reference. + */ + VARIANT_ALSO_NEGOTIATES = 506, + + /** + * The server is unable to store the representation needed to complete the request. + */ + INSUFFICIENT_STORAGE = 507, + + /** + * The server detected an infinite loop while processing the request. + */ + LOOP_DETECTED = 508, + + /** + * Further extensions to the request are required for the server to fulfill it. + */ + NOT_EXTENDED = 510, + + /** + * The client needs to authenticate to gain network access. + * Intended for use by intercepting proxies used to control access to the network (e.g., "captive portals" used + * to require agreement to Terms of Service before granting full Internet access via a Wi-Fi hotspot). + */ + NETWORK_AUTHENTICATION_REQUIRED = 511, +} + +export default HttpStatusCode; diff --git a/backend/ws/src/utils/index.ts b/backend/ws/src/utils/index.ts index 8aaf49d..da7b165 100644 --- a/backend/ws/src/utils/index.ts +++ b/backend/ws/src/utils/index.ts @@ -1,21 +1,40 @@ require("dotenv").config(); +import axios from "axios"; import jwt from "jsonwebtoken"; +import HttpStatusCode from "../types/HttpStatusCodes"; interface TokenPayload { _id: string; } -export default function tokenIsValid(token: string) { +export default async function tokenIsValid(token: string) { const JWT_SECRET = process.env.SECRET_KEY; + const TS_BACKEND_URI = process.env.TS_BACKEND_URI; try { if (!JWT_SECRET) { console.error("SECRET_KEY is undefined. Check the .env"); return false; } + if (!TS_BACKEND_URI) { + console.error("TS_BACKEND_URI is undefined. Check the .env"); + return false; + } + const info = jwt.verify(token, JWT_SECRET) as TokenPayload; + console.log("JWT info received by ws server", info); console.log("JWT userID received by ws server ", info._id); + const response = await axios.get(`${TS_BACKEND_URI}/api/user/${info._id}`, { + headers: { + Authorization: `Bearer ${token}`, + "Content-Type": "application/json", + }, + }); + + if (response.status !== HttpStatusCode.OK) { + return false; + } } catch { return false; }