diff --git a/src/App.tsx b/src/App.tsx index ec5e270..4a49dae 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -12,12 +12,17 @@ import { useRootStore } from '@educt/hooks/useRootStore'; */ import PrivateRoute from '@educt/components/PrivateRoute'; import PublicRoute from '@educt/components/PublicRoute'; +import { useSocketEvent } from './hooks/useSocketEvent'; const App = () => { const { uiStore: { history }, } = useRootStore(); + useSocketEvent('connect', () => console.info('[WebSocket]: Connected.')); + useSocketEvent('disconnect', () => console.info('[WebSocket]: Disconnected.')); + useSocketEvent('connect_error', err => console.error(err)); + return ( diff --git a/src/components/Layout/Layout.tsx b/src/components/Layout/Layout.tsx index 8d28180..691d871 100644 --- a/src/components/Layout/Layout.tsx +++ b/src/components/Layout/Layout.tsx @@ -1,4 +1,4 @@ -import React, { Suspense } from 'react'; +import React, { Suspense, useContext } from 'react'; import { observer } from 'mobx-react'; import { Redirect } from 'react-router'; import { useErrorHandler } from 'react-error-boundary'; @@ -23,6 +23,11 @@ import NavDesktop from '@educt/components/Nav/Desktop'; import { useEffect } from 'react'; import { useRootStore } from '@educt/hooks/useRootStore'; +/** + * Contexts + */ +import { SocketContext } from '@educt/contexts'; + type LayoutPropsType = { roles: UserRoleEnum[] | undefined }; /** @@ -30,6 +35,7 @@ type LayoutPropsType = { roles: UserRoleEnum[] | undefined }; */ const Layout: React.FC = ({ children, roles }) => { const { userStore } = useRootStore(); + const { socket } = useContext(SocketContext); const handleError = useErrorHandler(); /** @@ -39,6 +45,15 @@ const Layout: React.FC = ({ children, roles }) => { userStore.loadCurrentUserData().catch(error => handleError(error)); }, [handleError, userStore]); + /** + * Connect to websocket server + */ + useEffect(() => { + if (!socket?.connected) { + socket?.connect(); + } + }, [socket]); + return ( <>
diff --git a/src/hooks/queries/auth/useLogin.ts b/src/hooks/queries/auth/useLogin.ts index f9f311f..1e4a9d2 100644 --- a/src/hooks/queries/auth/useLogin.ts +++ b/src/hooks/queries/auth/useLogin.ts @@ -1,5 +1,7 @@ import { useToast } from '@chakra-ui/react'; +import { SocketContext } from '@educt/contexts'; import { IToken } from '@educt/interfaces'; +import { useContext } from 'react'; import { useErrorHandler } from 'react-error-boundary'; import useAsync from '../../useAsync'; import { useRootStore } from '../../useRootStore'; @@ -8,6 +10,7 @@ type LoginResultDataType = IToken; const useLogin = () => { const { authStore } = useRootStore(); + const { socket } = useContext(SocketContext); const toast = useToast(); const handleError = useErrorHandler(); @@ -20,6 +23,7 @@ const useLogin = () => { isClosable: true, status: 'success', }); + socket?.connect(); return result.data; } catch (error: any) { if (error.response) { diff --git a/src/hooks/queries/auth/useLogout.ts b/src/hooks/queries/auth/useLogout.ts index 040fedf..ff122fa 100644 --- a/src/hooks/queries/auth/useLogout.ts +++ b/src/hooks/queries/auth/useLogout.ts @@ -3,11 +3,18 @@ import { useHistory } from 'react-router-dom'; import { useRootStore } from '../../useRootStore'; import { useErrorHandler } from 'react-error-boundary'; import useAsync from '../../useAsync'; +import { useContext } from 'react'; + +/** + * Contexts + */ +import { SocketContext } from '@educt/contexts'; type LogoutResultDataType = Record; const useLogout = () => { const { authStore } = useRootStore(); + const { socket } = useContext(SocketContext); const history = useHistory(); const toast = useToast(); const handleError = useErrorHandler(); @@ -17,6 +24,7 @@ const useLogout = () => { const result = await authStore.logout(); history.push('/auth'); toast({ title: 'You are logged out.', isClosable: true, status: 'info' }); + socket?.disconnect(); return result.data; } catch (error) { handleError(error); diff --git a/src/hooks/useSocket.ts b/src/hooks/useSocket.ts index 631385d..9a39ea7 100644 --- a/src/hooks/useSocket.ts +++ b/src/hooks/useSocket.ts @@ -5,12 +5,12 @@ export const useSocket = (url: string) => { const [socket, setSocket] = useState(null); useEffect(() => { - const socketClient = io(url, { autoConnect: true }); + const socketClient = io(url, { autoConnect: false, withCredentials: true }); setSocket(socketClient); return () => { - socket?.disconnect(); + socket?.close(); }; }, []); diff --git a/src/hooks/useSocketEvent.ts b/src/hooks/useSocketEvent.ts index c8ba89e..272597b 100644 --- a/src/hooks/useSocketEvent.ts +++ b/src/hooks/useSocketEvent.ts @@ -1,7 +1,7 @@ import { useContext, useEffect } from 'react'; import { SocketContext } from '@educt/contexts'; -export const useSocketEvent = (event: string, callback: () => void) => { +export const useSocketEvent = (event: string, callback: (...args: any) => void) => { const { socket } = useContext(SocketContext); useEffect(() => { diff --git a/src/pages/main/main.tsx b/src/pages/main/main.tsx index b1c533f..75bd3c2 100644 --- a/src/pages/main/main.tsx +++ b/src/pages/main/main.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import React, { useState } from 'react'; import { Flex, Box, Text, Link, SimpleGrid, Divider } from '@chakra-ui/react'; import { Link as ReactRouterLink } from 'react-router-dom'; import { IPageProps } from '@educt/interfaces'; @@ -22,6 +22,8 @@ import { } from './components'; import { observer } from 'mobx-react'; import { useFetchCourses, useFetchStat } from '@educt/hooks/queries'; +import { useSocketEvent } from '@educt/hooks/useSocketEvent'; +import { useEffect } from 'react'; /** * Main page @@ -34,18 +36,20 @@ const MainPage: React.FC = () => { const { data: stat } = useFetchStat(); const [online, setOnline] = useState(0); - // useEffect(() => { - // const onUpdateOnline = users => { - // console.log(users); - // setOnline(users.length); - // }; + /** + * Update online state on socket event + */ + useSocketEvent('user:online', online => setOnline(online)); - // socket.on('user:online', onUpdateOnline); + useSocketEvent('user:connected', user => { + console.table(user); + }); - // return () => { - // socket.off('user:online', onUpdateOnline); - // }; - // }, [setOnline, socket]); + useEffect(() => { + if (stat) { + setOnline(stat.online); + } + }, [stat?.online]); if (me === null || courses === null || stat === null) return ; diff --git a/src/providers/index.tsx b/src/providers/index.tsx index 8f5d43d..1e9c225 100644 --- a/src/providers/index.tsx +++ b/src/providers/index.tsx @@ -25,7 +25,7 @@ export const RootStoreProvider = ({ children }: { children: ReactNode }) => { * Socket context provider */ export const SocketContextProvider: React.FC = ({ children }) => { - const socket = useSocket(''); + const socket = useSocket('ws://localhost:3333'); return {children}; }; diff --git a/src/types/index.d.ts b/src/types/index.d.ts index d314bdb..2fe9f43 100644 --- a/src/types/index.d.ts +++ b/src/types/index.d.ts @@ -43,6 +43,7 @@ export type LessonProgress = { export type StatInfoType = { courses_count: string; lessons_count: string; + online: number; }; export type LessonVideoType = { diff --git a/vite.config.ts b/vite.config.ts index e5404ba..6f1fbde 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -33,10 +33,6 @@ export default defineConfig({ target: constants.BACKEND_URL, changeOrigin: true, }, - '/socket.io': { - target: constants.BACKEND_URL, - changeOrigin: true, - }, }, }, resolve: {