From 4bc193d4d8b83aa99d453acce74cad6f38eceef7 Mon Sep 17 00:00:00 2001 From: Cho SeungYeon <111514472+layout-SY@users.noreply.github.com> Date: Wed, 28 May 2025 10:53:08 +0900 Subject: [PATCH 1/4] =?UTF-8?q?bug=20:=20=EC=8B=A4=EC=8B=9C=EA=B0=84=20?= =?UTF-8?q?=EC=95=8C=EB=A6=BC=20=EC=8B=A4=ED=96=89=20=EC=A4=91=EC=97=90=20?= =?UTF-8?q?API=20=EB=AA=A8=EB=93=A0=20=EC=9A=94=EC=B2=AD=EC=9D=B4=20?= =?UTF-8?q?=EB=8B=A4=EC=9A=B4=EB=90=98=EB=8A=94=20=EB=B2=84=EA=B7=B8=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../NotificationInitializer.tsx | 9 +++ .../notificationLive/NotificationProvider.tsx | 16 ++++ src/context/SseContext.tsx | 16 ++++ src/hooks/user/useNotification.ts | 81 +++++++++---------- 4 files changed, 78 insertions(+), 44 deletions(-) create mode 100644 src/components/user/notificationLive/NotificationInitializer.tsx create mode 100644 src/components/user/notificationLive/NotificationProvider.tsx create mode 100644 src/context/SseContext.tsx diff --git a/src/components/user/notificationLive/NotificationInitializer.tsx b/src/components/user/notificationLive/NotificationInitializer.tsx new file mode 100644 index 00000000..29c8088b --- /dev/null +++ b/src/components/user/notificationLive/NotificationInitializer.tsx @@ -0,0 +1,9 @@ +import useNotification from '../../../hooks/user/useNotification'; + +const NotificationInitializer = () => { + useNotification(); + + return null; +}; + +export default NotificationInitializer; diff --git a/src/components/user/notificationLive/NotificationProvider.tsx b/src/components/user/notificationLive/NotificationProvider.tsx new file mode 100644 index 00000000..307dcdf3 --- /dev/null +++ b/src/components/user/notificationLive/NotificationProvider.tsx @@ -0,0 +1,16 @@ +import { ReactNode, useState } from 'react'; +import { AlarmLive } from '../../../models/alarm'; +import { SseContext } from '../../../context/SseContext'; + +export const NotificationProvider = ({ children }: { children: ReactNode }) => { + const [signalData, setSignalData] = useState(null); + + const clearSignal = () => setSignalData(null); + const setSignal = (data: AlarmLive | null) => setSignalData(data); + + return ( + + {children} + + ); +}; diff --git a/src/context/SseContext.tsx b/src/context/SseContext.tsx new file mode 100644 index 00000000..d30064f8 --- /dev/null +++ b/src/context/SseContext.tsx @@ -0,0 +1,16 @@ +import { createContext, useContext } from 'react'; +import { AlarmLive } from '../models/alarm'; + +export interface SseContextProps { + signalData: AlarmLive | null; + clearSignal: () => void; + setSignal: (data: AlarmLive | null) => void; +} + +export const SseContext = createContext({ + signalData: null, + clearSignal: () => {}, + setSignal: () => {}, +}); + +export const useNotificationContext = () => useContext(SseContext); diff --git a/src/hooks/user/useNotification.ts b/src/hooks/user/useNotification.ts index 54f5c343..f409f845 100644 --- a/src/hooks/user/useNotification.ts +++ b/src/hooks/user/useNotification.ts @@ -1,19 +1,18 @@ -import { EventSourcePolyfill, NativeEventSource } from 'event-source-polyfill'; -import { useEffect, useRef, useState } from 'react'; +import { useEffect, useRef } from 'react'; import { useQueryClient } from '@tanstack/react-query'; import { AlarmList } from '../queries/user/keys'; import type { AlarmLive } from '../../models/alarm'; import useAuthStore, { getTokens } from '../../store/authStore'; import { useToast } from '../useToast'; +import { useNotificationContext } from '../../context/SseContext'; const useNotification = () => { - const [signalData, setSignalData] = useState(null); const queryClient = useQueryClient(); const userId = useAuthStore((state) => state.userData?.id); const { showToast } = useToast(); + const { setSignal } = useNotificationContext(); const eventSourceRef = useRef(null); - const EventSourceImpl = EventSourcePolyfill || NativeEventSource; useEffect(() => { if (!userId) { @@ -26,52 +25,46 @@ const useNotification = () => { if (eventSourceRef.current) { return; - } + } else { + // 헤더가 아닌 파라미터 형태로 바꾸면서 Polyfill 제외 하기 -> CORS Preflight를 유발하여 요청 지연의 원인이 될 수 있음. + const eventSource = new EventSource( + `${import.meta.env.VITE_APP_API_BASE_URL}user/sse?accessToken=${ + getTokens().accessToken || getTokens().refreshToken + ? getTokens().accessToken + : '' + }` + ); - // 헤더가 아닌 파라미터 형태로 바꾸면서 Polyfill 제외 하기 -> CORS Preflight를 유발하여 요청 지연의 원인이 될 수 있음. - const eventSource = new EventSourceImpl( - `${import.meta.env.VITE_APP_API_BASE_URL}user/sse`, - { - headers: { - Authorization: - getTokens().accessToken || getTokens().refreshToken - ? `Bearer ${getTokens().accessToken}` - : '', - 'Content-Type': 'application/json', - }, - heartbeatTimeout: 12 * 60 * 1000, - } - ); + eventSourceRef.current = eventSource; - eventSourceRef.current = eventSource; + eventSource.onopen = () => { + console.log('확인'); + console.log(eventSource.readyState); + }; - eventSource.addEventListener('alarm', (e) => { - const event = e as MessageEvent; - try { - const eventData: AlarmLive = JSON.parse(event.data); + eventSource.addEventListener('alarm', (e) => { + const event = e as MessageEvent; + try { + const eventData: AlarmLive = JSON.parse(event.data); + console.log(eventData); - if (eventData) { - queryClient.invalidateQueries({ - queryKey: [AlarmList.myAlarmList, userId], - }); - } + if (eventData) { + queryClient.invalidateQueries({ + queryKey: [AlarmList.myAlarmList, userId], + }); + } - setSignalData(eventData); - } catch (error) { - console.error(error); - } - }); - eventSource.onerror = (e) => { - console.error(e); - }; - }, [queryClient, userId]); - - useEffect(() => { - if (signalData) { - showToast(signalData, 3000); + setSignal(eventData); + showToast(eventData, 3000); + } catch (error) { + console.error(error); + } + }); + eventSource.onerror = (e) => { + console.error(e); + }; } - }, [signalData, showToast]); - return { signalData, setSignalData }; + }, [queryClient, userId]); }; export default useNotification; From f2dd243de530cca50b8116fd2884c81557d09c27 Mon Sep 17 00:00:00 2001 From: Cho SeungYeon <111514472+layout-SY@users.noreply.github.com> Date: Wed, 28 May 2025 10:53:53 +0900 Subject: [PATCH 2/4] =?UTF-8?q?feat=20:=20=EC=8B=A4=EC=8B=9C=EA=B0=84=20?= =?UTF-8?q?=EC=95=8C=EB=A6=BC=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20context?= =?UTF-8?q?=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/routes/AppRoutes.tsx | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/routes/AppRoutes.tsx b/src/routes/AppRoutes.tsx index 2f4b65ca..bf4faeb0 100644 --- a/src/routes/AppRoutes.tsx +++ b/src/routes/AppRoutes.tsx @@ -11,8 +11,10 @@ import useAuthStore from '../store/authStore'; import ProtectRoute from '../components/common/ProtectRoute'; import NotFoundPage from '../pages/notFoundPage/NotFoundPage'; import QueryErrorBoundary from '../components/common/error/QueryErrorBoundary'; -import { ToastProvider } from '../components/common/Toast/ToastProvider'; import { ROUTES } from '../constants/user/routes'; +import { ToastProvider } from '../components/common/Toast/ToastProvider'; +import NotificationInitializer from '../components/user/notificationLive/NotificationInitializer'; +import { NotificationProvider } from '../components/user/notificationLive/NotificationProvider'; const Login = lazy(() => import('../pages/login/Login')); const LoginSuccess = lazy(() => import('../pages/login/LoginSuccess')); const Register = lazy(() => import('../pages/user/register/Register')); @@ -385,9 +387,12 @@ const AppRoutes = () => { const router = createBrowserRouter([ { element: ( - - - + + + + + + ), children: [...newRouteList, { path: '*', element: }], From f8bea5200fc6089931c69dd2727a8db9239d04f5 Mon Sep 17 00:00:00 2001 From: Cho SeungYeon <111514472+layout-SY@users.noreply.github.com> Date: Thu, 29 May 2025 22:18:14 +0900 Subject: [PATCH 3/4] =?UTF-8?q?feat=20:=20=ED=97=A4=EB=8D=94=20=EC=95=8C?= =?UTF-8?q?=EB=A6=BC=EC=97=90=20=EC=8B=A4=EC=8B=9C=EA=B0=84=20=EC=95=8C?= =?UTF-8?q?=EB=A6=BC=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/common/header/Header.tsx | 36 ++++++++++++------------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/src/components/common/header/Header.tsx b/src/components/common/header/Header.tsx index a8c6c482..f7eeecd8 100644 --- a/src/components/common/header/Header.tsx +++ b/src/components/common/header/Header.tsx @@ -11,13 +11,14 @@ import loadingImg from '../../../assets/loadingImg.svg'; import { useModal } from '../../../hooks/useModal'; import Modal from '../modal/Modal'; import { formatImgPath } from '../../../util/formatImgPath'; -// import bell from '../../../assets/bell.svg'; -// import Notification from './Notification/Notification'; -// import bellLogined from '../../../assets/bellLogined.svg'; -// import { useEffect } from 'react'; -// import { testLiveAlarm } from '../../../api/alarm.api'; +import bell from '../../../assets/bell.svg'; +import Notification from './Notification/Notification'; +import bellLogined from '../../../assets/bellLogined.svg'; import { useMyProfileInfo } from '../../../hooks/user/useMyInfo'; import { ROUTES } from '../../../constants/user/routes'; +import { useNotificationContext } from '../../../context/SseContext'; +import { useEffect } from 'react'; +import { testLiveAlarm } from '../../../api/alarm.api'; function Header() { const location = useLocation(); @@ -26,11 +27,7 @@ function Header() { const isLoggedIn = useAuthStore((state) => state.isLoggedIn); const { myData, isLoading } = useMyProfileInfo(); - // const { signalData, setSignalData } = useNotification(); - - // useEffect(() => { - // testLiveAlarm(); - // }, []); + const { signalData, clearSignal } = useNotificationContext(); const profileImg = myData?.profileImg ? `${import.meta.env.VITE_APP_IMAGE_CDN_URL}/${formatImgPath( @@ -38,6 +35,10 @@ function Header() { )}?w=86&h=86&fit=crop&crop=entropy&auto=format,enhance&q=60` : DefaultImg; + useEffect(() => { + testLiveAlarm(); + }, []); + return ( @@ -52,26 +53,23 @@ function Header() { 공지사항 - {/* + {isLoggedIn ? ( setSignalData(null)}> - 알림 - {signalData && } - - ) : ( + 알림 - ) + {signalData && } + } + comment={false} > ) : ( 알림 )} - */} + Date: Thu, 29 May 2025 22:45:37 +0900 Subject: [PATCH 4/4] =?UTF-8?q?feat=20:=20=EB=A6=AC=EB=B7=B0=20=EC=82=AC?= =?UTF-8?q?=ED=95=AD=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/common/header/Header.tsx | 4 +++- src/hooks/user/useNotification.ts | 9 ++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/components/common/header/Header.tsx b/src/components/common/header/Header.tsx index 65a7e5d0..8a31f5fb 100644 --- a/src/components/common/header/Header.tsx +++ b/src/components/common/header/Header.tsx @@ -42,7 +42,9 @@ function Header() { : DefaultImg; useEffect(() => { - testLiveAlarm(); + if (process.env.NODE_ENV === 'deployment') { + testLiveAlarm(); + } }, []); return ( diff --git a/src/hooks/user/useNotification.ts b/src/hooks/user/useNotification.ts index 7b1ef377..c0980469 100644 --- a/src/hooks/user/useNotification.ts +++ b/src/hooks/user/useNotification.ts @@ -63,7 +63,14 @@ const useNotification = () => { console.error(e); }; } - }, [queryClient, userId]); + + return () => { + if (eventSourceRef.current) { + eventSourceRef.current.close(); + eventSourceRef.current = null; + } + }; + }, [queryClient, userId, accessToken]); }; export default useNotification;