Repository navigation
Conversation
- firebase SDK 설치 및 앱 초기화(config/messaging) 모듈 추가 - 백그라운드 수신 서비스워커 추가(config는 쿼리스트링으로 주입) - 토큰 등록/해제 API(notification) 추가 - 웹/네이티브 분기용 platform 유틸 추가 - 권한요청→토큰발급→백엔드등록 + 포그라운드 수신 useFcmToken 훅 추가 네이티브(Capacitor) 경로는 WebView Web Push 미지원으로 추후 플러그인 연동 예정 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- webPush.ts: 권한요청/토큰발급·등록/조건부 등록/해제를 React 밖에서도 재사용 가능한 plain 함수로 분리 (로그인 자동등록·로그아웃 revoke에서 재사용) - preference.ts: 코드로 해제 불가한 브라우저 권한을 보완할 알림 사용 의도 저장 - useFcmToken: action 레이어 위의 얇은 래퍼(enable/disable + 포그라운드 수신)로 정리 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- FcmAutoRegister: 인증 영역 진입 시 권한 상태/사용자 의도에 따라 토큰을 조용히 재등록하거나 1회 권한 요청 - 인증 레이아웃과 클럽 메인 레이아웃에 마운트 (페이지 로드당 1회 가드) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- NotificationSettingItem: 토글 ON(권한요청·토큰등록)/OFF(토큰해제) 제어 - 브라우저 권한은 코드로 해제 불가하므로 사용자 의도(preference)와 함께 판단 - 고객지원 섹션 최상단에 배치 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- 쿠키/스토리지 정리 전에 revokeCurrentToken 호출 (revoke API가 인증 쿠키 필요) - best-effort 처리로 실패해도 로그아웃 흐름은 진행 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
다른 세션/계정에서 이어서 알림 수신 테스트를 진행할 수 있도록 현재 상태·전제조건·테스트 절차·함정·백엔드 협의사항 정리 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- PC Chrome 백그라운드 수신 확인 완료 (2026-10-19) - Firebase 콘솔 UI 변경 반영 (새 알림 만들기 → 새 캠페인) - 토큰 선택(체크/추가) 후 테스트 버튼 활성화 단계 명시 - Windows 알림 차단(집중 지원/방해 금지) 함정 추가 — 이번 테스트 실제 원인 - 세팅 완료 항목과 남은 작업(백엔드 발송, 포그라운드 핸들러) 구분 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- webPush.ts, NotificationSettingItem.tsx 줄바꿈 정리 (로직 변화 없음) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars.
|
PR 테스트 결과✅ Jest: 통과 🎉 모든 테스트를 통과했습니다! |
|
구현한 기능 Preview: https://weeth-ec9gjw86y-weethsite-4975s-projects.vercel.app |
PR 검증 결과✅ TypeScript: 통과 |
nabbang6
left a comment
There was a problem hiding this comment.
확인이 늦었습니다....ㅠㅠ 전반적으로 깔끔 꼼꼼하게 잘 설정해주셔서 큰 문제는 없을 것 같아요 너무 고생하셧습니다~!!! 👍
그리고 어드민 LNB 하단 프로필 쪽에서 알림 표시되는 부분은 주언님께서 디자인 잡아주셧어서, 제가 전에 admin/layout/NotificationModal.tsx <요 컴포넌트로 UI만 구현해둿엇어요! 추후 연결 작업하실 때 요거 사용하셔두 괜찮을 것 같아서 참고용으로 적어두어요,,,ㅎㅎ 파잇띵 ✊👊
| let hasRun = false; | ||
|
|
||
| /** | ||
| * 로그인 직후(인증 영역 진입 시) 웹 푸시 토큰을 등록하는 비가시 컴포넌트. | ||
| * | ||
| * - 권한 granted: 조용히 토큰 재등록 (토큰 회전/계정 변경 대응) | ||
| * - 권한 default + 미결정: 1회 권한 요청 (로그인 직후 프롬프트) | ||
| * - 권한 denied 또는 사용자가 알림 끔(disabled): 아무것도 하지 않음 | ||
| * | ||
| * 네이티브(Capacitor)에서는 동작하지 않으며, 추후 플러그인 경로에서 별도 처리한다. | ||
| */ | ||
| export function FcmAutoRegister() { | ||
| useEffect(() => { | ||
| if (hasRun || isNativeApp()) return; | ||
| hasRun = true; |
There was a problem hiding this comment.
요기 hasRun이 한 세션 동안 true로 유지되고 잇어서, 사용자가 로그아웃 -> 페이지 새로고침을 하지 않고 재로그인했을 때 notificationApi.registerToken이 호출되지 않음 -> fcm 토큰이 새로 등록되지 않는 문제가 잇는 것 같습니다!
재로그인 후 푸시 알림이 동작하지 않는 상황이 있을 수 잇으니...
아래처럼 수정해주면 조금 더 안전할 것 같아용 b
// 모듈 플래그 제거, ref로 교체 → 컴포넌트 인스턴스 생애 기준으로 관리
export function FcmAutoRegister() {
const hasRun = useRef(false);
useEffect(() => {
if (hasRun.current || isNativeApp()) return;
hasRun.current = true;
// ... 이하 동일
}, []);
return null;
}
| function clearBrowserStorage() { | ||
| localStorage.clear(); |
There was a problem hiding this comment.
로그아웃할 때 로컬스토리지 전체가 초기화되면서 weeth:fcm-preference 키도 함께 제거되고 잇는 것 같아요!
요것 때문에 사용자가 알림을 끄더라도 로그아웃 -> 재로그인 시 권한 요청 알림이 반복적으로 발생하고 잇는 것 같습니닷
// FCM 사용 의도는 디바이스 단위 설정이므로 로그아웃 시에도 보존
const FCM_PREFERENCE_KEY = 'weeth:fcm-preference';
function clearBrowserStorage() {
const fcmPref = localStorage.getItem(FCM_PREFERENCE_KEY);
localStorage.clear();
sessionStorage.clear();
clearClientCookies();
if (fcmPref) localStorage.setItem(FCM_PREFERENCE_KEY,
fcmPref);
}
요로코롬... fcm 키는 유지되게 처리해줌 ux 측면에서 좋을 것 같아용
| if ('focus' in client) { | ||
| client.navigate(targetUrl); | ||
| return client.focus(); | ||
| } |
There was a problem hiding this comment.
포커스처럼 client.navigate(targetUrl) 부분도 await 처리 해주면 좋을 것 같습니당!
PR 테스트 결과✅ Jest: 통과 🎉 모든 테스트를 통과했습니다! |
|
구현한 기능 Preview: https://weeth-iilswag3d-weethsite-4975s-projects.vercel.app |
PR 검증 결과✅ TypeScript: 통과 🎉 모든 검증을 통과했습니다! |
woneeeee
left a comment
There was a problem hiding this comment.
수고하셨습니다~.~ 유진님 깃허브 프사 바꾸셨네용 ㅎ.ㅎ💕
| self.clients.matchAll({ type: 'window', includeUncontrolled: true }).then((clientList) => { | ||
| for (const client of clientList) { | ||
| if ('focus' in client) { | ||
| client.navigate(targetUrl); |
There was a problem hiding this comment.
이 SW 스코프(/firebase-cloud-messaging-push-scope)는 앱 페이지를 제어하지 않아서 matchAll로 잡힌 클라이언트가 전부 uncontrolled라고 합니다! 이러면 client.navigate()가 타입 에러로 거절돼서 focus()까지 못 가고, 클릭해도 아무 일도 안 일어날 가능성이 잇다고 합니당,,, ㅠㅠㅠ
focus() 후 postMessage로 앱이 라우팅하거나, navigate 실패 시 openWindow로 폴백시켜주면 조을 것 같아용!! data.link는 same-origin 검증도 부탁드려요. 클로드가 이것도 하면 좋다고 합니다 ㅎㅎ
| export async function revokeCurrentToken(): Promise<void> { | ||
| if (!isWebPushSupportable() || Notification.permission !== 'granted') return; | ||
| const messaging = await getMessagingInstance(); | ||
| if (!messaging) return; | ||
|
|
||
| try { | ||
| const registration = await getExistingRegistration(); | ||
| if (registration && FIREBASE_VAPID_KEY) { | ||
| const token = await getToken(messaging, { | ||
| vapidKey: FIREBASE_VAPID_KEY, | ||
| serviceWorkerRegistration: registration, | ||
| }); | ||
| if (token) { | ||
| await notificationApi.revokeToken({ token }); | ||
| } | ||
| } | ||
| await deleteToken(messaging); | ||
| } catch { | ||
| // 로그아웃 흐름을 막지 않도록 실패는 조용히 무시 | ||
| } | ||
| } |
There was a problem hiding this comment.
토글 OFF 때 revoke가 실패해도 catch에서 삼켜서, UI는 껐다곻 하는데 서버에 토큰이 남는다구 합니다...!! revoke가 throw하면 deleteToken도 건너뛴다네용,,, 권한이 granted가 아니면 revoke 자체를 건너뛰는 것도 공용 기기에서 이전 사용자의 알림이 남는다는 큰 이슈가 잇을수도 잇어요! 코어는 throw하고 로그아웃 쪽에서만 best-effort로 감싸는 구조로 하면 더 안정성이 높아진다고 하니 그런 식으로 진행해도 좋을 것 같아요~
| getMessagingInstance().then((messaging) => { | ||
| if (!messaging) return; | ||
| unsubscribe = onMessage(messaging, (payload) => { | ||
| onForegroundMessageRef.current?.(payload); | ||
| }); | ||
| }); | ||
|
|
||
| return () => unsubscribe?.(); |
There was a problem hiding this comment.
getMessagingInstance()가 완료되기 전에 컴포넌트가 언마운트되면, cleanup 시점에는 unsubscribe가 없어 해제가 실행되지 않고 이후 .then()에서 구독이 생성될 수 있을 것 같습니다! cleanup에서 disposed 플래그를 설정하고, .then()에서 해당 값을 확인해 구독 등록을 막으면 어떨까용??
| if (token) { | ||
| await notificationApi.revokeToken({ token }); | ||
| } | ||
| } | ||
| await deleteToken(messaging); | ||
| } catch { | ||
| // 로그아웃 흐름을 막지 않도록 실패는 조용히 무시 |
There was a problem hiding this comment.
이 부분에서 백엔드 해제가 실패해도 오류를 무시해서 알림 설정 화면에서는 성공으로 처리되는 것 같습니다!
revoke 실패 시 deleteToken()도 건너뛰어 토큰이 남을 수 있을 것 같은데, 설정 화면에서는 실패를 감지할 수 있도록 오류를 전달하고, 로그아웃에서만 실패를 무시하도록 분리하면 어떨까요??
이 pr만 빼서 릴리즈하기 번거로우니까 일단 머지 안 하고 둘게요!!
✅ PR 유형
어떤 변경 사항이 있었나요?
📌 관련 이슈번호
✅ Key Changes
FCM 기반 웹 푸시 알림을 도입했습니다.
기반 설정 (
e7f24189)lib/firebase/config.ts,lib/firebase/messaging.ts)public/firebase-messaging-sw.js) — config는 쿼리스트링 주입lib/push/webPush.ts), 플랫폼 판별(platform.ts), 사용 의도 저장(preference.ts)lib/apis/notification.ts)동작 연결
04b45374웹 푸시 로직을 action 레이어 기준으로 분리a47a22ec로그인 직후 토큰 자동 등록 (FcmAutoRegister— 인증/메인 레이아웃에 마운트)8e54d928마이페이지 고객지원에 알림설정 토글 추가 (NotificationSettingItem)e39936f1로그아웃 시 토큰 해제(revoke) 연결문서/정리
docs/로그/FCM-웹푸시-테스트-핸드오프.md)📸 스크린샷 or 실행영상
🎸 기타 사항 or 추가 코멘트
typecheck✅ /lint✅ (0 errors) /format:check✅ /build✅ 통과test: 817 통과 / 4 실패 — 실패는 전부admin/member/MemberPageContent.test.tsx(어드민 멤버, 본 변경과 무관)의waitFor타임아웃이며 이 브랜치와 무관한 기존 이슈입니다. FCM 관련 테스트는 모두 통과.