-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #153 from prgrms-web-devcourse-final-project/150-f…
…eature/enhance-web-socket [Feature] #150 웹소켓 개선
- Loading branch information
Showing
12 changed files
with
343 additions
and
129 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import { ReactNode } from 'react' | ||
import useSubscribe from '~hooks/useSubscribe' | ||
import useToken from '~hooks/useToken' | ||
|
||
export default function GlobalHookContainer({ children }: { children: ReactNode }) { | ||
useToken() | ||
useSubscribe() | ||
return <>{children}</> | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import { AnimatePresence } from 'framer-motion' | ||
import { Typo15 } from '~components/Typo' | ||
import { usePushNotificationStore } from '~stores/usePushNotificationStore' | ||
import * as S from './styles' | ||
|
||
export default function PushNotification() { | ||
const { notifications, clearNotification } = usePushNotificationStore() | ||
|
||
return ( | ||
<AnimatePresence> | ||
{notifications.map(({ id, message }) => ( | ||
<S.PushNotification | ||
key={id} | ||
initial={{ opacity: 0 }} | ||
animate={{ opacity: 1 }} | ||
exit={{ opacity: 0 }} | ||
transition={{ duration: 0.3 }} | ||
onClick={() => clearNotification()} | ||
> | ||
<Typo15 $weight='700' $color='default'> | ||
{message} | ||
</Typo15> | ||
</S.PushNotification> | ||
))} | ||
</AnimatePresence> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
import { motion } from 'framer-motion' | ||
import { styled } from 'styled-components' | ||
|
||
export const PushNotification = styled(motion.div)` | ||
position: fixed; | ||
left: 50%; | ||
top: 20px; | ||
background-color: ${({ theme }) => theme.colors.grayscale.gc_4}; | ||
color: ${({ theme }) => theme.colors.grayscale.font_1}; | ||
padding: 16px 36px; | ||
translate: -50%; | ||
border-radius: 16px; | ||
width: 90%; | ||
max-width: 400px; | ||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); | ||
display: flex; | ||
align-items: center; | ||
justify-content: space-between; | ||
font-size: 14px; | ||
line-height: 1.4; | ||
z-index: 1000; | ||
&::before { | ||
content: ''; | ||
position: absolute; | ||
left: 18px; | ||
top: 50%; | ||
translate: 0 -50%; | ||
width: 3.5px; | ||
height: 20px; | ||
background-color: ${({ theme }) => theme.colors.brand.default}; | ||
border-radius: 100px; | ||
} | ||
` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,180 @@ | ||
import { InfiniteData, useQuery, useQueryClient } from '@tanstack/react-query' | ||
import { useEffect } from 'react' | ||
import { useWebSocket } from '~/WebSocketContext' | ||
import { FetchChatMessageListResponse } from '~apis/chat/fetchChatMessageList' | ||
import { fetchHomePageData } from '~apis/main/fetchHomePageData' | ||
import { FetchNotificationListResponse } from '~apis/notification/fetchNotificationList' | ||
import { queryKey } from '~constants/queryKey' | ||
import { usePushNotificationStore } from '~stores/usePushNotificationStore' | ||
import { APIResponse } from '~types/api' | ||
|
||
export default function useSubscribe() { | ||
const { data } = useQuery({ | ||
queryKey: queryKey.home(), | ||
queryFn: () => fetchHomePageData().then(data => data.data), | ||
enabled: !!localStorage.getItem('token'), | ||
}) | ||
|
||
const { isConnected, subscribe } = useWebSocket() | ||
const queryClient = useQueryClient() | ||
const { showNotification } = usePushNotificationStore() | ||
|
||
useEffect(() => { | ||
if (isConnected) { | ||
subscribe(`/user/queue/errors`, message => { | ||
const response = JSON.parse(message.body) | ||
console.log('에러 구독', response) | ||
}) | ||
|
||
subscribe(`/sub/message/${data?.email || ''}`, message => { | ||
const response = JSON.parse(message.body) | ||
if (response.code === 1000) { | ||
//* 첫 연결 시 모든 채팅방 반환 | ||
type Data = { | ||
chatRoomId: number | ||
unreadCount: number | ||
} | ||
const data = response.data as Data[] | ||
|
||
console.log('이메일 구독', response) | ||
|
||
data.forEach((chatRoom: Data) => { | ||
subscribe(`/sub/chat/${chatRoom.chatRoomId}`, message => { | ||
const res = JSON.parse(message.body) as APIResponse<FetchChatMessageListResponse['content'][number]> | ||
console.log('채팅방 구독', res) | ||
queryClient.invalidateQueries({ | ||
queryKey: queryKey.social.chatRoomList(), | ||
}) | ||
if (res.data.chatId) | ||
queryClient.setQueryData<InfiniteData<APIResponse<FetchChatMessageListResponse>>>( | ||
queryKey.social.chatMessageList(res.data.chatRoomId), | ||
oldData => { | ||
if (!oldData) { | ||
const initialPage: APIResponse<FetchChatMessageListResponse> = { | ||
code: 200, | ||
status: 'OK', | ||
message: 'Success', | ||
data: { | ||
content: [res.data], | ||
size: 1, | ||
number: 0, | ||
numberOfElements: 1, | ||
first: true, | ||
last: true, | ||
empty: false, | ||
sort: { | ||
empty: true, | ||
sorted: false, | ||
unsorted: true, | ||
}, | ||
pageable: { | ||
offset: 0, | ||
sort: { | ||
empty: true, | ||
sorted: false, | ||
unsorted: true, | ||
}, | ||
pageSize: 1, | ||
paged: true, | ||
pageNumber: 0, | ||
unpaged: false, | ||
}, | ||
}, | ||
} | ||
return { | ||
pages: [initialPage], | ||
pageParams: [null], | ||
} | ||
} | ||
return { | ||
...oldData, | ||
pages: oldData.pages.map((page, index) => { | ||
if (index === 0) { | ||
return { | ||
...page, | ||
data: { | ||
...page.data, | ||
content: [...page.data.content, res.data], | ||
numberOfElements: page.data.numberOfElements + 1, | ||
}, | ||
} | ||
} | ||
return page | ||
}), | ||
} | ||
} | ||
) | ||
}) | ||
}) | ||
} | ||
if (response.code === 1001) { | ||
//* 첫 연결 이후부터 새로운 채팅방 생성 시 | ||
// const data = response.data as CreateChatRoomResponse | ||
//todo 새로운 채팅방 추가 | ||
queryClient.invalidateQueries({ queryKey: queryKey.social.chatRoomList() }) | ||
} | ||
}) | ||
|
||
subscribe(`/sub/notification/${data?.email || ''}`, message => { | ||
const response = JSON.parse(message.body) as APIResponse<FetchNotificationListResponse['content'][number]> | ||
console.log('알림 구독', response) | ||
if (!response.data.content) { | ||
return | ||
} | ||
showNotification(response.data.content) | ||
console.log(response) | ||
queryClient.setQueryData<InfiniteData<APIResponse<FetchNotificationListResponse>>>( | ||
queryKey.notification(), | ||
oldData => { | ||
if (!oldData) { | ||
return { | ||
pages: [ | ||
{ | ||
code: 200, | ||
status: 'OK', | ||
message: 'Success', | ||
data: { | ||
content: [response.data], | ||
pageable: { | ||
offset: 0, | ||
sort: { empty: true, sorted: false, unsorted: true }, | ||
pageSize: 1, | ||
paged: true, | ||
pageNumber: 0, | ||
unpaged: false, | ||
}, | ||
last: true, | ||
size: 1, | ||
number: 0, | ||
sort: { empty: true, sorted: false, unsorted: true }, | ||
first: true, | ||
numberOfElements: 1, | ||
empty: false, | ||
}, | ||
}, | ||
], | ||
pageParams: [0], | ||
} | ||
} | ||
|
||
return { | ||
...oldData, | ||
pages: oldData.pages.map((page, index) => | ||
index === 0 | ||
? { | ||
...page, | ||
data: { | ||
...page.data, | ||
content: [response.data, ...page.data.content], | ||
numberOfElements: page.data.numberOfElements + 1, | ||
}, | ||
} | ||
: page | ||
), | ||
} | ||
} | ||
) | ||
}) | ||
} | ||
}, [isConnected]) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
import { useEffect } from 'react' | ||
import { useSearchParams, useNavigate } from 'react-router-dom' | ||
|
||
export default function useToken() { | ||
const [searchParams] = useSearchParams() | ||
const navigate = useNavigate() | ||
|
||
useEffect(() => { | ||
console.log('useToken 실행') | ||
const accessToken = searchParams.get('accessToken') | ||
if (accessToken) { | ||
localStorage.setItem('token', accessToken) | ||
console.log('토큰 가져옴(숨김처리 예정) : ', accessToken) | ||
//URL에서 토큰 파라미터 제거하고 홈페이지로 리다이렉트, JWT토큰이 URL에 노출되어 히스토리에 남지 않게 함 | ||
window.history.replaceState({}, '', '/') | ||
return | ||
} | ||
|
||
const storedToken = localStorage.getItem('token') | ||
if (!storedToken) { | ||
console.log('토큰 없음 비로그인 상태. login페이지 이동.') | ||
navigate('/login') | ||
return | ||
} | ||
}, [searchParams, navigate]) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.