From 0433e4695eefd6ea064bba494455148120eb8e11 Mon Sep 17 00:00:00 2001 From: Emmy D'Anello Date: Tue, 17 Dec 2024 01:06:23 +0100 Subject: [PATCH] =?UTF-8?q?Transmission=20plus=20imm=C3=A9diate=20via=20we?= =?UTF-8?q?bsockets?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../providers/GeolocationProvider.tsx | 12 +- client/hooks/useLocation.ts | 6 +- client/package-lock.json | 139 ++++++++++- client/package.json | 3 +- .../utils/features/location/locationSlice.ts | 8 +- client/utils/geolocation.ts | 21 +- client/utils/socket.ts | 5 + server/package-lock.json | 225 ++++++++++++++++++ server/package.json | 3 + .../geolocations/geolocations.gateway.spec.ts | 18 ++ .../src/geolocations/geolocations.gateway.ts | 13 + .../src/geolocations/geolocations.module.ts | 7 +- 12 files changed, 448 insertions(+), 12 deletions(-) create mode 100644 client/utils/socket.ts create mode 100644 server/src/geolocations/geolocations.gateway.spec.ts create mode 100644 server/src/geolocations/geolocations.gateway.ts diff --git a/client/components/providers/GeolocationProvider.tsx b/client/components/providers/GeolocationProvider.tsx index e27b680..7d2fd5e 100644 --- a/client/components/providers/GeolocationProvider.tsx +++ b/client/components/providers/GeolocationProvider.tsx @@ -1,11 +1,13 @@ -import { ReactNode, useEffect, useState } from 'react' +import { ReactNode, useEffect } from 'react' import { useAuth } from '@/hooks/useAuth' -import { useQueuedLocations, useSetLastPlayerLocations, useUnqueueLocation } from '@/hooks/useLocation' +import { useQueuedLocations, useSetLastPlayerLocation, useSetLastPlayerLocations, useUnqueueLocation } from '@/hooks/useLocation' import { useGeolocationMutation } from '@/hooks/mutations/useGeolocationMutation' import { useStartGeolocationServiceEffect } from '@/utils/geolocation' import { Platform } from 'react-native' import { useQuery } from '@tanstack/react-query' import { isAuthValid } from '@/utils/features/auth/authSlice' +import { socket } from '@/utils/socket' +import { PlayerLocation } from '@/utils/features/location/locationSlice' export default function GeolocationProvider({ children }: { children: ReactNode }) { useStartGeolocationServiceEffect() @@ -14,6 +16,7 @@ export default function GeolocationProvider({ children }: { children: ReactNode const geolocationsQueue = useQueuedLocations() const unqueueLocation = useUnqueueLocation() const setLastPlayerLocations = useSetLastPlayerLocations() + const setLastPlayerLocation = useSetLastPlayerLocation() const geolocationMutation = useGeolocationMutation({ auth, onPostSuccess: (data, variables) => unqueueLocation(variables), @@ -47,6 +50,11 @@ export default function GeolocationProvider({ children }: { children: ReactNode setLastPlayerLocations(lastLocationsQuery.data) }, [lastLocationsQuery.status, lastLocationsQuery.dataUpdatedAt]) + socket.on('last-location', (data: PlayerLocation) => { + if (data.playerId) + setLastPlayerLocation(data) + }) + return <> {children} diff --git a/client/hooks/useLocation.ts b/client/hooks/useLocation.ts index 2f8f2fe..cfe1343 100644 --- a/client/hooks/useLocation.ts +++ b/client/hooks/useLocation.ts @@ -1,6 +1,6 @@ import { LocationObject } from "expo-location" import { useAppDispatch, useAppSelector } from "./useStore" -import { PlayerLocation, setLastLocation, setLastPlayerLocations, unqueueLocation } from "@/utils/features/location/locationSlice" +import { PlayerLocation, setLastLocation, setLastPlayerLocation, setLastPlayerLocations, unqueueLocation } from "@/utils/features/location/locationSlice" export const useLastOwnLocation = () => useAppSelector((state) => state.location.lastOwnLocation) export const useQueuedLocations = () => useAppSelector((state) => state.location.queuedLocations) @@ -18,3 +18,7 @@ export const useSetLastPlayerLocations = () => { const dispatch = useAppDispatch() return (playerLocations: PlayerLocation[]) => dispatch(setLastPlayerLocations(playerLocations)) } +export const useSetLastPlayerLocation = () => { + const dispatch = useAppDispatch() + return (playerLocation: PlayerLocation) => dispatch(setLastPlayerLocation(playerLocation)) +} diff --git a/client/package-lock.json b/client/package-lock.json index a46031a..f68618a 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -52,7 +52,8 @@ "react-native-screens": "~4.1.0", "react-native-web": "~0.19.13", "react-native-webview": "13.12.2", - "react-redux": "^9.1.2" + "react-redux": "^9.1.2", + "socket.io-client": "^4.8.1" }, "devDependencies": { "@babel/core": "^7.25.2", @@ -4654,6 +4655,12 @@ "@sinonjs/commons": "^3.0.0" } }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", + "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", + "license": "MIT" + }, "node_modules/@tanstack/query-async-storage-persister": { "version": "5.62.7", "resolved": "https://registry.npmjs.org/@tanstack/query-async-storage-persister/-/query-async-storage-persister-5.62.7.tgz", @@ -7349,6 +7356,66 @@ "once": "^1.4.0" } }, + "node_modules/engine.io-client": { + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.2.tgz", + "integrity": "sha512-TAr+NKeoVTjEVW8P3iHguO1LO6RlUz9O5Y8o7EY0fU+gY1NYqas7NN3slpFtbXEsLMHk0h90fJMfKjRkQ0qUIw==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.17.1", + "xmlhttprequest-ssl": "~2.1.1" + } + }, + "node_modules/engine.io-client/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/engine.io-client/node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/engine.io-parser": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", + "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/enhanced-resolve": { "version": "5.17.1", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz", @@ -14798,6 +14865,68 @@ "node": ">=8.0.0" } }, + "node_modules/socket.io-client": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.1.tgz", + "integrity": "sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.2", + "engine.io-client": "~6.6.1", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-client/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io-parser": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", + "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-parser/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, "node_modules/sort-asc": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/sort-asc/-/sort-asc-0.2.0.tgz", @@ -16705,6 +16834,14 @@ "dev": true, "license": "MIT" }, + "node_modules/xmlhttprequest-ssl": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz", + "integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", diff --git a/client/package.json b/client/package.json index 280ef37..6874e5b 100644 --- a/client/package.json +++ b/client/package.json @@ -58,7 +58,8 @@ "react-native-screens": "~4.1.0", "react-native-web": "~0.19.13", "react-native-webview": "13.12.2", - "react-redux": "^9.1.2" + "react-redux": "^9.1.2", + "socket.io-client": "^4.8.1" }, "devDependencies": { "@babel/core": "^7.25.2", diff --git a/client/utils/features/location/locationSlice.ts b/client/utils/features/location/locationSlice.ts index e863dfa..0e2a3da 100644 --- a/client/utils/features/location/locationSlice.ts +++ b/client/utils/features/location/locationSlice.ts @@ -3,7 +3,7 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit' import { LocationObject } from 'expo-location' export type PlayerLocation = { - id: number + id?: number playerId: number longitude: number latitude: number @@ -49,10 +49,14 @@ export const locationSlice = createSlice({ }, setLastPlayerLocations: (state, action: PayloadAction) => { state.lastPlayerLocations = action.payload + }, + setLastPlayerLocation: (state, action: PayloadAction) => { + state.lastPlayerLocations = state.lastPlayerLocations.filter(playerLoc => playerLoc.playerId !== action.payload.playerId) + state.lastPlayerLocations.push(action.payload) } }, }) -export const { setLastLocation, unqueueLocation, setLastPlayerLocations } = locationSlice.actions +export const { setLastLocation, unqueueLocation, setLastPlayerLocation, setLastPlayerLocations } = locationSlice.actions export default locationSlice.reducer diff --git a/client/utils/geolocation.ts b/client/utils/geolocation.ts index aa84ec8..d6bc2c7 100644 --- a/client/utils/geolocation.ts +++ b/client/utils/geolocation.ts @@ -1,9 +1,10 @@ import * as Location from 'expo-location' import * as TaskManager from 'expo-task-manager' import { Platform } from 'react-native' -import { setLastLocation } from './features/location/locationSlice' +import { PlayerLocation, setLastLocation } from './features/location/locationSlice' import store from './store' import { useEffect } from 'react' +import { socket } from './socket' const LOCATION_TASK = "fetch-geolocation" @@ -13,7 +14,23 @@ TaskManager.defineTask(LOCATION_TASK, async ({ data, error }: any) => { return } const { locations } = data - store.dispatch(setLastLocation(locations.at(-1))) + const lastLoc: Location.LocationObject = locations.at(-1) + store.dispatch(setLastLocation(lastLoc)) + console.log("sending-loc", lastLoc, socket.active) + const playerId = store.getState().game.playerId + if (socket.active && playerId) { + const lastLocToSend: PlayerLocation = { + playerId: playerId, + longitude: lastLoc.coords.longitude, + latitude: lastLoc.coords.latitude, + speed: lastLoc.coords.speed ?? 0, + accuracy: lastLoc.coords.accuracy ?? 0, + altitude: lastLoc.coords.accuracy ?? 0, + altitudeAccuracy: lastLoc.coords.altitudeAccuracy ?? 0, + timestamp: new Date(lastLoc.timestamp).toISOString(), + } + socket.emit('last-location', { playerId: playerId, loc: lastLocToSend }) + } }) export async function startGeolocationService(): Promise void)> { diff --git a/client/utils/socket.ts b/client/utils/socket.ts new file mode 100644 index 0000000..1c83d73 --- /dev/null +++ b/client/utils/socket.ts @@ -0,0 +1,5 @@ +import { io } from 'socket.io-client' + +export const socket = io("http://192.168.1.198:3000", { + reconnection: true, +}) diff --git a/server/package-lock.json b/server/package-lock.json index 44ff913..3a04aa5 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -14,7 +14,10 @@ "@nestjs/jwt": "^10.2.0", "@nestjs/passport": "^10.0.3", "@nestjs/platform-express": "^10.0.0", + "@nestjs/platform-socket.io": "^10.4.15", + "@nestjs/platform-ws": "^10.4.15", "@nestjs/swagger": "^8.1.0", + "@nestjs/websockets": "^10.4.15", "@prisma/client": "^6.0.1", "bcrypt": "^5.1.1", "class-transformer": "^0.5.1", @@ -1815,6 +1818,65 @@ "@nestjs/core": "^10.0.0" } }, + "node_modules/@nestjs/platform-socket.io": { + "version": "10.4.15", + "resolved": "https://registry.npmjs.org/@nestjs/platform-socket.io/-/platform-socket.io-10.4.15.tgz", + "integrity": "sha512-KZAxNEADPwoORixh3NJgGYWMVGORVPKeTqjD7hbF8TPDLKWWxru9yasBQwEz2/wXH/WgpkQbbaYwx4nUjCIVpw==", + "license": "MIT", + "dependencies": { + "socket.io": "4.8.1", + "tslib": "2.8.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nest" + }, + "peerDependencies": { + "@nestjs/common": "^10.0.0", + "@nestjs/websockets": "^10.0.0", + "rxjs": "^7.1.0" + } + }, + "node_modules/@nestjs/platform-ws": { + "version": "10.4.15", + "resolved": "https://registry.npmjs.org/@nestjs/platform-ws/-/platform-ws-10.4.15.tgz", + "integrity": "sha512-EvioQ4zq5LcaL+wdCfcxWgX/R65f4/VN/qFN18cfoVAxWRRa/JfHtWDT+b1lacAU8jPnYjLNAtWPKXc/mcZ1eQ==", + "license": "MIT", + "dependencies": { + "tslib": "2.8.1", + "ws": "8.18.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nest" + }, + "peerDependencies": { + "@nestjs/common": "^10.0.0", + "@nestjs/websockets": "^10.0.0", + "rxjs": "^7.1.0" + } + }, + "node_modules/@nestjs/platform-ws/node_modules/ws": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/@nestjs/schematics": { "version": "10.2.3", "resolved": "https://registry.npmjs.org/@nestjs/schematics/-/schematics-10.2.3.tgz", @@ -1900,6 +1962,29 @@ } } }, + "node_modules/@nestjs/websockets": { + "version": "10.4.15", + "resolved": "https://registry.npmjs.org/@nestjs/websockets/-/websockets-10.4.15.tgz", + "integrity": "sha512-OmCUJwvtagzXfMVko595O98UI3M9zg+URL+/HV7vd3QPMCZ3uGCKSq15YYJ99LHJn9NyK4e4Szm2KnHtUg2QzA==", + "license": "MIT", + "dependencies": { + "iterare": "1.2.1", + "object-hash": "3.0.0", + "tslib": "2.8.1" + }, + "peerDependencies": { + "@nestjs/common": "^10.0.0", + "@nestjs/core": "^10.0.0", + "@nestjs/platform-socket.io": "^10.0.0", + "reflect-metadata": "^0.1.12 || ^0.2.0", + "rxjs": "^7.1.0" + }, + "peerDependenciesMeta": { + "@nestjs/platform-socket.io": { + "optional": true + } + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -2077,6 +2162,12 @@ "@sinonjs/commons": "^3.0.0" } }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", + "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", + "license": "MIT" + }, "node_modules/@tsconfig/node10": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", @@ -2181,6 +2272,12 @@ "@types/node": "*" } }, + "node_modules/@types/cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==", + "license": "MIT" + }, "node_modules/@types/cookiejar": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.5.tgz", @@ -2188,6 +2285,15 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/cors": { + "version": "2.8.17", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz", + "integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/eslint": { "version": "9.6.1", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", @@ -3285,6 +3391,15 @@ ], "license": "MIT" }, + "node_modules/base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", + "license": "MIT", + "engines": { + "node": "^4.5.0 || >= 5.9" + } + }, "node_modules/bcrypt": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.1.tgz", @@ -4304,6 +4419,45 @@ "node": ">= 0.8" } }, + "node_modules/engine.io": { + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.2.tgz", + "integrity": "sha512-gmNvsYi9C8iErnZdVcJnvCpSKbWTt1E8+JZo8b+daLninywUWi5NQ5STSHZ9rFjFO7imNcvb8Pc5pe/wMR5xEw==", + "license": "MIT", + "dependencies": { + "@types/cookie": "^0.4.1", + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.7.2", + "cors": "~2.8.5", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.17.1" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/engine.io-parser": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", + "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/engine.io/node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/enhanced-resolve": { "version": "5.17.1", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz", @@ -7408,6 +7562,15 @@ "node": ">=0.10.0" } }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, "node_modules/object-inspect": { "version": "1.13.3", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz", @@ -8590,6 +8753,47 @@ "node": ">=8" } }, + "node_modules/socket.io": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.1.tgz", + "integrity": "sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "cors": "~2.8.5", + "debug": "~4.3.2", + "engine.io": "~6.6.0", + "socket.io-adapter": "~2.5.2", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/socket.io-adapter": { + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz", + "integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==", + "license": "MIT", + "dependencies": { + "debug": "~4.3.4", + "ws": "~8.17.1" + } + }, + "node_modules/socket.io-parser": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", + "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/source-map": { "version": "0.7.4", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", @@ -9802,6 +10006,27 @@ "dev": true, "license": "ISC" }, + "node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", diff --git a/server/package.json b/server/package.json index 4e724af..7112fc1 100644 --- a/server/package.json +++ b/server/package.json @@ -25,7 +25,10 @@ "@nestjs/jwt": "^10.2.0", "@nestjs/passport": "^10.0.3", "@nestjs/platform-express": "^10.0.0", + "@nestjs/platform-socket.io": "^10.4.15", + "@nestjs/platform-ws": "^10.4.15", "@nestjs/swagger": "^8.1.0", + "@nestjs/websockets": "^10.4.15", "@prisma/client": "^6.0.1", "bcrypt": "^5.1.1", "class-transformer": "^0.5.1", diff --git a/server/src/geolocations/geolocations.gateway.spec.ts b/server/src/geolocations/geolocations.gateway.spec.ts new file mode 100644 index 0000000..27b8f0e --- /dev/null +++ b/server/src/geolocations/geolocations.gateway.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing' +import { GeolocationsGateway } from './geolocations.gateway' + +describe('GeolocationsGateway', () => { + let gateway: GeolocationsGateway + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [GeolocationsGateway], + }).compile(); + + gateway = module.get(GeolocationsGateway) + }); + + it('should be defined', () => { + expect(gateway).toBeDefined() + }) +}) diff --git a/server/src/geolocations/geolocations.gateway.ts b/server/src/geolocations/geolocations.gateway.ts new file mode 100644 index 0000000..4e0d065 --- /dev/null +++ b/server/src/geolocations/geolocations.gateway.ts @@ -0,0 +1,13 @@ +import { SubscribeMessage, WebSocketGateway, WebSocketServer } from '@nestjs/websockets' +import { Server, Socket } from 'socket.io' + +@WebSocketGateway() +export class GeolocationsGateway { + @WebSocketServer() + server: Server + + @SubscribeMessage('last-location') + handleLastLocation(client: Socket, payload: any) { + return this.server.emit('last-location', payload) + } +} diff --git a/server/src/geolocations/geolocations.module.ts b/server/src/geolocations/geolocations.module.ts index ba8f227..4ccec6e 100644 --- a/server/src/geolocations/geolocations.module.ts +++ b/server/src/geolocations/geolocations.module.ts @@ -1,11 +1,12 @@ import { Module } from '@nestjs/common' -import { GeolocationsService } from './geolocations.service' -import { GeolocationsController } from './geolocations.controller' import { PrismaModule } from 'src/prisma/prisma.module' +import { GeolocationsController } from './geolocations.controller' +import { GeolocationsService } from './geolocations.service' +import { GeolocationsGateway } from './geolocations.gateway' @Module({ controllers: [GeolocationsController], - providers: [GeolocationsService], + providers: [GeolocationsService, GeolocationsGateway], imports: [PrismaModule], }) export class GeolocationsModule {}