Skip to content

Commit

Permalink
feat: add notification click event, token refresh feature
Browse files Browse the repository at this point in the history
  • Loading branch information
ptyoiy committed Jul 3, 2024
1 parent aead43c commit d106f80
Show file tree
Hide file tree
Showing 17 changed files with 273 additions and 411 deletions.
72 changes: 34 additions & 38 deletions public/firebaseSW.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
importScripts('https://www.gstatic.com/firebasejs/8.10.1/firebase-app.js');
importScripts('https://www.gstatic.com/firebasejs/8.10.1/firebase-messaging.js');

const firebaseConfig = {
apiKey: 'AIzaSyAEU-CHEmYMLlSLnRLpmoABONwqHZGVREw',
authDomain: 'test-csey.firebaseapp.com',
Expand All @@ -12,47 +13,42 @@ const firebaseConfig = {
const app = firebase.initializeApp(firebaseConfig);

const messaging = firebase.messaging(app);
console.log('#@#object');

self.addEventListener('push', (event) => {
console.log("Push");
const options = {
body: event.data.text(),
icon: 'icon.png',
badge: 'badge.png',
};
event.waitUntil(self.registration.showNotification('Hi-Notification', options));
});

// messaging
// .getToken({
// vapidKey:
// 'BBHaXtLh_pZUn89NG0Jp1J_1N0wGS2R_9vllG20dfYsJ4dF5ZmTDofKD0HbBhoeZYhngL3YQ0GHUxRnnXpAkoko',
// })
// .then((currentToken) => {
// if (currentToken) {
// // Send the token to your server and update the UI if necessary
// // ...
// console.log({ currentToken });
// } else {
// // Show permission request UI
// console.log('No registration token available. Request permission to generate one.');
// // ...
// }
// })
// .catch((err) => {
// console.log('An error occurred while retrieving token. ', err);
// // ...
// });

messaging.onBackgroundMessage((payload) => {
console.log('[firebase-messaging-sw.js] Received background message ', payload);
// Customize notification here
const notificationTitle = 'Background Message Title';
const notificationOptions = {
body: 'Background Message body.',
icon: '/firebase-logo.png',
};
self.registration.showNotification(payload.notification.title, {
body: payload.notification.body,
data: payload.fcmOptions.link,
});
});

self.registration.showNotification(notificationTitle, notificationOptions);
self.addEventListener('notificationclick', (event) => {
const { data } = event.notification;
const [type, id] = data.split('=');
let clientFound = false;
console.log({ event });
event.notification.close();
event.waitUntil(
clients.matchAll({ type: 'window', includeUncontrolled: true }).then((clientList) => {
console.log({ clients, clientList });
for (let i = 0; i < clientList.length; i++) {
const client = clientList[i];
if (client.visibilityState === 'visible') {
client.postMessage({
action: 'notificationClick',
data
});
clientFound = true;
break;
}
}
if (!clientFound) {
clients
.openWindow(
`https://localhost:8080/${type === 'events' ? 'main' : 'notification'}?${type}=${id}`
);
}
})
);
});
84 changes: 70 additions & 14 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
/* eslint-disable react/jsx-props-no-spreading */
// eslint-disable-next-line import/no-extraneous-dependencies
import { initializeApp } from '@firebase/app';
import { Modal } from '@mui/material';
import { Unsubscribe, getMessaging, onMessage } from 'firebase/messaging';
import { useContext, useEffect } from 'react';
import { Navigate, Route, Routes, useSearchParams } from 'react-router-dom';
import { Navigate, Route, Routes, useNavigate, useSearchParams } from 'react-router-dom';
import Wrapper from './AppStyles';
import { useUserInfoQuery } from './api/query';
import BottomNavbar from './components/BottomNavbar';
import HeaderLogo from './components/HeaderLogo';
import { userContext } from './context';
Expand All @@ -15,37 +17,91 @@ import NoticePage from './pages/NoticePage';
import { SetMajor, Signup } from './pages/SignupPage';
import TerracePage from './pages/TerracePage';

const firebaseConfig = {
apiKey: 'AIzaSyAEU-CHEmYMLlSLnRLpmoABONwqHZGVREw',
authDomain: 'test-csey.firebaseapp.com',
projectId: 'test-csey',
storageBucket: 'test-csey.appspot.com',
messagingSenderId: '491214968405',
appId: '1:491214968405:web:08e469439f00c3c07ba6dc',
measurementId: 'G-JGZP14R2TG',
};
const app = initializeApp(firebaseConfig);

function App() {
console.log('APP rerender');
const { userData, dispatch } = useContext(userContext);
const { userData } = useContext(userContext);
const { modal, modalDispatch } = useContext(ModalContext);
const [, setSearchParams] = useSearchParams();
useUserInfoQuery(userData, dispatch);
const navigate = useNavigate();
const { Component, props } = modal;
const handleModalClose = () => {
setSearchParams({});
modalDispatch({ payload: 'close' });
};
useEffect(() => {
let unsub: Unsubscribe;
let registration: ServiceWorkerRegistration;
if ('serviceWorker' in navigator) {
navigator.serviceWorker
.register('/public/firebaseSW.js')
.then((registration) => {
console.log('Service Worker registered with scope:', registration.scope);
})
.catch((error) => {
console.log('Service Worker registration failed:', error);
})
.finally(() => {
console.log('finally');
// TODO: onMessage에서 notification 나오게 만드는 코드 작성하기
const regist = async () => {
console.log(
'unreg:',
await navigator.serviceWorker.getRegistration('https://localhost:8080/public/')
);
registration = await navigator.serviceWorker.register('/public/firebaseSW.js');
if (registration) {
console.log('registration success', registration.scope, registration.active);
registration.showNotification('registration success', {
body: 'registration success body',
data: 'naver.com',
});
}
navigator.serviceWorker.addEventListener('message', (event) => {
if (!event.data.action) return;
const { data, action } = event.data;
console.log('data:', event.data);
const [type, id] = (data as string).split('=');
switch (action) {
case 'notificationClick':
navigate(`/${type === 'events' ? 'main' : 'notification'}`);
modalDispatch({
payload: 'mainModal',
props: {
postId: +id,
type: type as any
}
});
break;
// skip default
}
});
const messaging = getMessaging(app);
unsub = onMessage(messaging, (payload) => {
console.log({ payload });
registration.showNotification(payload.notification.title, {
body: payload.notification.body,
data: payload.fcmOptions.link,
});
});
};
regist();
}
return () => {
if (unsub) {
console.log('clean');
unsub();
registration.unregister();
}
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return (
<Wrapper>
<HeaderLogo />
<Routes>
<Route path="/" element={<Navigate replace to="/main" />} />
<Route path="/public" element={<Navigate replace to="/main" />} />
<Route path="/signup" element={<Signup />} />
<Route path="/setmajor" element={<SetMajor />} />
<Route path="/terrace" element={<TerracePage />} />
Expand Down
10 changes: 8 additions & 2 deletions src/api/axios/alarm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,17 @@ export type SetAlarmBody = {
fcmToken: string;
};

function setAlarm({ alarmData, fcmToken }: SetAlarmBody) {
export function setAlarm({ alarmData, fcmToken }: SetAlarmBody) {
console.log('set alarm called');
const loginInfo = JSON.parse(localStorage.getItem('info'));
return axios.put(`/${loginInfo.userId}/alarms`, {
alarmData, fcmToken
});
}
export default setAlarm;

export function retireToken(token: string) {
console.log('retireToken called');
return axios.post('/fcm-token/retire', {
token
});
}
1 change: 1 addition & 0 deletions src/api/axios/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ import { getUserInfo, kakaoLogout, login } from './user';

axios.defaults.baseURL = '/api';
axios.defaults.withCredentials = true;

export { getUserInfo, kakaoLogout, login };
2 changes: 1 addition & 1 deletion src/api/query/alarm.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* eslint-disable max-len */
import { useMutation } from '@tanstack/react-query';
import setAlarm from '../axios/alarm';
import { setAlarm } from '../axios/alarm';

const useAlarmMutation = () => {
// const queryClient = useQueryClient();
Expand Down
1 change: 0 additions & 1 deletion src/api/query/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import { useLoginQuery, useUserInfoQuery } from './user';

export {
useAlarmMutation,
useNoticeByIdQuery as useAlertByIdQuery,
useAlertsQuery,
useEventBookmarkMutation,
useEventByIdQuery,
Expand Down
43 changes: 11 additions & 32 deletions src/api/query/user.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { useQuery, useQueryClient } from '@tanstack/react-query';
import { AxiosError } from 'axios';
import { useEffect } from 'react';
import { Action, UserData } from '../../context';
import { useQuery } from '@tanstack/react-query';
import axios, { AxiosError } from 'axios';
import { UserData } from '../../context';
import { User } from '../../context/user.d';
import { login } from '../axios';
import { getAccessToken, getUserInfo } from '../axios/user';
import { getUserInfo } from '../axios/user';

export const useLoginQuery = (id: number, kakao_accessToken: string) => {
const info = useQuery({
Expand All @@ -17,19 +16,17 @@ export const useLoginQuery = (id: number, kakao_accessToken: string) => {
return info;
};

export const useUserInfoQuery = (userData: UserData, dispatch: React.Dispatch<Action>) => {
export const useUserInfoQuery = (userData: UserData) => {
const { userId, isAuthenticated } = userData;
const queryClient = useQueryClient();
// const queryClient = useQueryClient();
const userInfo = useQuery({
queryKey: ['userInfo'],
queryFn: () => getUserInfo(userId),
enabled: isAuthenticated,
enabled: isAuthenticated && Boolean((axios.interceptors.response as any).handlers.length),
staleTime: Infinity,
select(data) {
const {
alarm, bookmark, eventLike, noticeLike, noticeRead,
} = data;
return new User(userData, alarm, bookmark, noticeRead, noticeLike, eventLike);
const { bookmark, eventLike, noticeLike, noticeRead } = data;
return new User(userData, bookmark, noticeRead, noticeLike, eventLike);
},
retry(_failureCount, error: AxiosError) {
// 401 == accessToken이 없음
Expand All @@ -40,28 +37,10 @@ export const useUserInfoQuery = (userData: UserData, dispatch: React.Dispatch<Ac
// return false -> 재시도 종료 후 error 플래그on
const maxRetry = _failureCount === 3;
const retryStatus = ![401, 419].some((errorCode) => errorCode === error?.response?.status);
console.count('retry');
console.log({ retryStatus });
return maxRetry || retryStatus;
},
});
const { isError, error } = userInfo;
useEffect(() => {
const fetchNewAccessToken = async () => {
try {
const refreshToken = localStorage.getItem('refreshToken');
await getAccessToken(refreshToken);
// 쿠키에 저장된 새로운 AccessToken으로 이전에 실패한 쿼리 다시 실행
await queryClient.invalidateQueries({ queryKey: ['userInfo'] });
} catch (_error) {
// RefreshToken이 만료된 경우 context정보 삭제
dispatch({ payload: 'logout' });
}
};

if (isError) {
fetchNewAccessToken();
}
if (error?.status === 401) dispatch({ payload: 'logout' });
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isError]);
return userInfo;
};
4 changes: 2 additions & 2 deletions src/components/Login/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import { memo, useContext, useEffect, useRef, useState } from 'react';
import KakaoLogin from 'react-kakao-login';
import { Props } from 'react-kakao-login/lib/types';
import { useLoginQuery } from '../../api/query';
import { userContext } from '../../context';
import ChatYellow from '../../assets/Img/ChatYellow.png';
import { userContext } from '../../context';
import * as s from '../../pages/SignupPage/styles';

type LoginButtonProps = {
Expand Down Expand Up @@ -36,7 +36,7 @@ const LoginButton = memo(({ showLogin }: LoginButtonProps) => {
dispatch({
payload: 'authenticate',
data: {
info: { userId: id, name, major },
info: { userId: id, name, major, fcmToken: localStorage.getItem('fcmToken') },
refreshToken: data.refreshToken,
},
});
Expand Down
13 changes: 5 additions & 8 deletions src/components/Modal/MainModal/index.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
/* eslint-disable consistent-return */
import { forwardRef, useContext, useState } from 'react';
import { Modal } from '@mui/material';
import { useSearchParams } from 'react-router-dom';
import {
useAlertByIdQuery,
useEventBookmarkMutation,
useEventByIdQuery,
useEventLikeMutation,
Expand All @@ -24,20 +22,19 @@ import ShareIcon from '../../../assets/Icons/ShareIcon.png';
import CloseBtnSrc from '../../../assets/Icons/modalCloseBtn.png';
import { userContext } from '../../../context';
import { formatDate } from '../../../util/dateUtil';
import ModalImage from '../ModalImage';
import Toast from '../../Toast';
import ModalImage from '../ModalImage';
import Stepper from '../stepper';
import * as s from './styles';
// eslint-disable-next-line import/no-cycle
import { ModalViewType } from '../../../pages/NoticePage';
import { MainModalProps, ModalContext } from '../../../context/modal';
import { ModalViewType } from '../../../pages/NoticePage';

function useFetchDataByType(type: ModalViewType) {
switch (type) {
case 'events':
return useEventByIdQuery;
case 'alerts':
return useAlertByIdQuery;
case 'notices':
return useNoticeByIdQuery;
// skip default
Expand All @@ -59,10 +56,10 @@ const MainModal = forwardRef((_props, _ref) => {
const { modal, modalDispatch } = useContext(ModalContext);
const { type, postId: eventId } = (modal.props as MainModalProps);

const { userData, dispatch } = useContext(userContext);
const { userData } = useContext(userContext);
const { userId } = userData;
const { data, isPending } = useFetchDataByType(type)(eventId.toString());
const { data: user } = useUserInfoQuery(userData, dispatch);
const { data: user } = useUserInfoQuery(userData);
const isLike = user?.[type === 'events' ? 'eventsLike' : 'noticesLike']?.[eventId];
const [toast, setToast] = useState(false);
const [curImgIdx, setCurImgIdx] = useState(0);
Expand Down Expand Up @@ -196,6 +193,6 @@ const MainModal = forwardRef((_props, _ref) => {
{toast && <Toast setToast={setToast} toastText="클립보드에 복사되었습니다." />}
</>
);
})
});

export default MainModal;
Loading

0 comments on commit d106f80

Please sign in to comment.