-
Notifications
You must be signed in to change notification settings - Fork 1
Fix(client): FCM알람 커스텀 및 api instance 수정 #162
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,22 +1,29 @@ | ||
| /* eslint-env serviceworker */ | ||
| /* eslint-disable no-undef */ | ||
|
|
||
| importScripts( | ||
| './firebase_sdk/firebase-app-compat.js', | ||
| './firebase_sdk/firebase-messaging-compat.js', | ||
| './firebase-config.js' | ||
| ); | ||
|
|
||
| self.addEventListener('install', function () { | ||
| self.skipWaiting(); | ||
| }); | ||
|
|
||
| self.addEventListener('activate', function () { | ||
| console.log('fcm sw activate..'); | ||
| console.log('실행중..'); | ||
| }); | ||
|
|
||
| self.addEventListener('push', function (e) { | ||
| if (!e.data.json()) return; | ||
| const resultData = e.data.json().notification; | ||
| const notificationTitle = resultData.title; | ||
| firebase.initializeApp(firebaseConfig); | ||
| const messaging = firebase.messaging(); | ||
|
|
||
| messaging.onBackgroundMessage((payload) => { | ||
| console.log('Received background message ', payload); | ||
|
|
||
| const notificationTitle = payload.notification?.title ?? '알림이 도착했어요!'; | ||
| const notificationOptions = { | ||
| body: resultData.body, | ||
| body: payload.notification?.body, | ||
| }; | ||
| console.log(resultData.title, { | ||
| body: resultData.body, | ||
| }); | ||
| e.waitUntil( | ||
| self.registration.showNotification(notificationTitle, notificationOptions) | ||
| ); | ||
| self.registration.showNotification(notificationTitle, notificationOptions); | ||
| }); | ||
|
Comment on lines
+21
to
29
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. onBackgroundMessage는 “data‑only” 메시지에만 호출됩니다. click 핸들러와 data fallback을 추가하세요.
messaging.onBackgroundMessage((payload) => {
console.log('Received background message ', payload);
- const notificationTitle = payload.notification?.title ?? '알림이 도착했어요!';
- const notificationOptions = {
- body: payload.notification?.body,
- };
- self.registration.showNotification(notificationTitle, notificationOptions);
+ const titleFromData = payload.data && (payload.data.title || payload.data.notificationTitle);
+ const bodyFromData = payload.data && (payload.data.body || payload.data.notificationBody);
+ const notificationTitle =
+ payload.notification?.title ?? titleFromData ?? '알림이 도착했어요!';
+ const notificationOptions = {
+ body: payload.notification?.body ?? bodyFromData ?? '',
+ data: payload.data || {},
+ // 아이콘/배지 등 필요 시 추가
+ // icon: '/icons/icon-192.png',
+ // badge: '/icons/badge-72.png',
+ };
+ self.registration.showNotification(notificationTitle, notificationOptions);
});
+
+// 알림 클릭 시 포커스/열기
+self.addEventListener('notificationclick', (event) => {
+ event.notification.close();
+ const url = event.notification?.data?.url || event.notification?.data?.click_action || '/';
+ event.waitUntil(
+ (async () => {
+ const allClients = await clients.matchAll({ type: 'window', includeUncontrolled: true });
+ const existing = allClients.find((c) => c.url.includes(url));
+ if (existing) return existing.focus();
+ return clients.openWindow(url);
+ })()
+ );
+}); |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -37,9 +37,19 @@ apiRequest.interceptors.request.use(async (config) => { | |
| const isNoAuth = noAuthNeeded.some((url) => config.url?.includes(url)); | ||
|
|
||
| if (!isNoAuth) { | ||
| const token = localStorage.getItem('token'); | ||
|
|
||
| if (!token || token === 'undefined' || token === 'null' || token === '') { | ||
| let token = localStorage.getItem('token'); | ||
| const email = localStorage.getItem('email'); | ||
| if (email) { | ||
| try { | ||
| token = await refreshToken(email); | ||
| } catch (err) { | ||
| console.error('요청 인터셉터에서 토큰 재발급 실패:', err); | ||
| localStorage.removeItem('token'); | ||
| localStorage.removeItem('email'); | ||
| window.location.href = '/onboarding'; | ||
| throw err; | ||
| } | ||
|
Comment on lines
+40
to
+51
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 매 요청마다 토큰을 재발급하고 있어 서버 부하 및 중복 호출 위험이 큽니다. email 존재 시 무조건 refreshToken을 호출해 모든 요청이 재발급을 트리거합니다. 동시 다발 요청 시 토큰 재발급이 중복으로 발생하고, 응답 인터셉터의 재시도와 겹쳐 지연 및 불필요 트래픽이 생깁니다. 토큰이 없거나 만료(또는 만료 임박)한 경우에만 재발급하고, 단일 진행(single-flight)으로 동시 요청을 합치는 게 안전합니다. 아래처럼 만료 검사 + 단일 진행으로 수정 제안합니다(요청 인터셉터 부분만 diff). 헬퍼는 별도 코드 블록 참고. if (!isNoAuth) {
- let token = localStorage.getItem('token');
- const email = localStorage.getItem('email');
- if (email) {
- try {
- token = await refreshToken(email);
- } catch (err) {
- console.error('요청 인터셉터에서 토큰 재발급 실패:', err);
- localStorage.removeItem('token');
- localStorage.removeItem('email');
- window.location.href = '/onboarding';
- throw err;
- }
- } else {
- console.error('토큰이 없습니다. 온보딩을 먼저 완료해주세요.');
- throw new Error('토큰이 없습니다. 온보딩을 먼저 완료해주세요.');
- }
-
- config.headers.Authorization = `Bearer ${token}`;
+ let token = localStorage.getItem('token');
+ if (!token || isTokenExpired(token)) {
+ const email = localStorage.getItem('email');
+ if (!email) {
+ console.error('토큰/이메일이 없습니다. 온보딩을 먼저 완료해주세요.');
+ localStorage.removeItem('token');
+ localStorage.removeItem('email');
+ window.location.href = '/onboarding';
+ throw new Error('인증 정보 없음');
+ }
+ try {
+ if (!refreshPromise) {
+ refreshPromise = refreshToken(email).finally(() => {
+ refreshPromise = null;
+ });
+ }
+ token = await refreshPromise;
+ } catch (err) {
+ console.error('요청 인터셉터에서 토큰 재발급 실패:', err);
+ localStorage.removeItem('token');
+ localStorage.removeItem('email');
+ window.location.href = '/onboarding';
+ throw err;
+ }
+ }
+ config.headers = config.headers ?? {};
+ (config.headers as any)['Authorization'] = `Bearer ${token}`;
}헬퍼/전역 보조 코드(파일 상단 아무 곳에 추가): let refreshPromise: Promise<string> | null = null;
function isTokenExpired(token: string, skewSec = 60): boolean {
try {
const [, payload] = token.split('.');
const data = JSON.parse(atob(payload));
if (!data?.exp) return false;
const now = Math.floor(Date.now() / 1000);
return data.exp - now <= skewSec;
} catch {
// 파싱 실패 시 보수적으로 만료로 간주하여 재발급 유도
return true;
}
}🤖 Prompt for AI Agents |
||
| } else { | ||
| console.error('토큰이 없습니다. 온보딩을 먼저 완료해주세요.'); | ||
| throw new Error('토큰이 없습니다. 온보딩을 먼저 완료해주세요.'); | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,8 +1,12 @@ | ||
| { | ||
| "rewrites": [ | ||
| { | ||
| "source": "/firebase-messaging-sw.js", | ||
| "destination": "/firebase-messaging-sw.js" | ||
| }, | ||
| { | ||
| "source": "/(.*)", | ||
| "destination": "/index.html" | ||
| } | ||
| ] | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
firebase-config 로딩 방식 점검 필요 (런타임 ReferenceError 위험).
importScripts('./firebase-config.js')는 글로벌 스코프에firebaseConfig변수가 정의되어 있어야 합니다. 현재 레포에는src/firebase-config.ts만 보이며, 번들 산출물이public/firebase-config.js로 생성되지 않으면 SW에서firebaseConfig참조 시 즉시 실패합니다. 또한 이 파일과firebase_sdk/*가 SPA 캐치올 리라이트의 영향을 받지 않는지 확인이 필요합니다.다음으로 존재/형식을 확인해 주세요(전역 변수 할당 여부까지 점검):
가능하면
public/firebase-config.js는 다음과 같이 전역에 주입되도록 유지하세요.🏁 Script executed:
Length of output: 418
firebase-config.js 및 SDK 파일 경로와 전역 변수 정의를 확인하려면 아래 스크립트를 실행해 주세요.
🏁 Script executed:
Length of output: 228
apps/client/public에
firebase-config.js및firebase-sdk파일이 없음importScripts에서 참조하는firebase-config.js,firebase-app-compat.js,firebase-messaging-compat.js파일이 존재하지 않아 서비스 워커 로딩 시 ReferenceError가 발생합니다. 빌드 프로세스가 해당 파일들을apps/client/public에 산출하도록 설정하거나, importScripts 경로를 올바르게 조정하세요.🤖 Prompt for AI Agents