feat(WEB): FCM 푸시 알림 시스템 구현
- FCMProvider 컨텍스트 및 useFCM 훅 추가 - Capacitor FCM 플러그인 통합 - 알림 사운드 파일 추가 (default.wav, push_notification.wav) - Firebase 메시징 패키지 의존성 추가
This commit is contained in:
137
src/hooks/useFCM.ts
Normal file
137
src/hooks/useFCM.ts
Normal file
@@ -0,0 +1,137 @@
|
||||
'use client';
|
||||
|
||||
/**
|
||||
* FCM 푸시 알림 훅
|
||||
*
|
||||
* Capacitor 네이티브 앱에서 FCM 푸시 알림을 처리합니다.
|
||||
* - 로그인 상태에서 자동으로 FCM 초기화
|
||||
* - 포그라운드 알림을 sonner 토스트로 표시
|
||||
* - 로그아웃 시 FCM 토큰 해제
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* // FCMProvider에서 사용
|
||||
* function FCMProvider({ children }) {
|
||||
* useFCM();
|
||||
* return <>{children}</>;
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
|
||||
import { useEffect, useRef, useCallback } from 'react';
|
||||
import { toast } from 'sonner';
|
||||
import {
|
||||
initializeFCM,
|
||||
unregisterFCMToken,
|
||||
isCapacitorNative,
|
||||
getToastTypeByNotificationType,
|
||||
type FCMNotification,
|
||||
type ToastType,
|
||||
} from '@/lib/capacitor/fcm';
|
||||
import { hasAuthToken } from '@/lib/api/auth-headers';
|
||||
|
||||
export function useFCM() {
|
||||
const initialized = useRef(false);
|
||||
|
||||
/**
|
||||
* 포그라운드 알림 핸들러 (sonner 토스트)
|
||||
*/
|
||||
const handleForegroundNotification = useCallback((notification: FCMNotification) => {
|
||||
const { title, body, data } = notification;
|
||||
const type = data?.type;
|
||||
const url = data?.url;
|
||||
|
||||
// 타입별 토스트 스타일 결정
|
||||
const toastType: ToastType = getToastTypeByNotificationType(type);
|
||||
|
||||
// 토스트 옵션
|
||||
const toastOptions = {
|
||||
description: body,
|
||||
duration: 5000,
|
||||
action: url
|
||||
? {
|
||||
label: '보기',
|
||||
onClick: () => {
|
||||
window.location.href = url;
|
||||
},
|
||||
}
|
||||
: undefined,
|
||||
};
|
||||
|
||||
// 타입별 토스트 표시
|
||||
const toastTitle = title || '알림';
|
||||
|
||||
switch (toastType) {
|
||||
case 'error':
|
||||
toast.error(toastTitle, toastOptions);
|
||||
break;
|
||||
case 'warning':
|
||||
toast.warning(toastTitle, toastOptions);
|
||||
break;
|
||||
case 'success':
|
||||
toast.success(toastTitle, toastOptions);
|
||||
break;
|
||||
default:
|
||||
toast.info(toastTitle, toastOptions);
|
||||
}
|
||||
}, []);
|
||||
|
||||
/**
|
||||
* FCM 초기화
|
||||
*/
|
||||
useEffect(() => {
|
||||
// 네이티브 환경이 아니면 무시
|
||||
if (!isCapacitorNative()) {
|
||||
console.log('[useFCM] Not in native environment, skipping');
|
||||
return;
|
||||
}
|
||||
|
||||
// 이미 초기화됐으면 무시
|
||||
if (initialized.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 로그인 상태가 아니면 무시
|
||||
if (!hasAuthToken()) {
|
||||
console.log('[useFCM] No auth token, skipping FCM initialization');
|
||||
return;
|
||||
}
|
||||
|
||||
// FCM 초기화
|
||||
initialized.current = true;
|
||||
console.log('[useFCM] Initializing FCM...');
|
||||
|
||||
initializeFCM(handleForegroundNotification).then((success) => {
|
||||
if (success) {
|
||||
console.log('[useFCM] FCM initialized successfully');
|
||||
} else {
|
||||
console.log('[useFCM] FCM initialization failed or skipped');
|
||||
initialized.current = false;
|
||||
}
|
||||
});
|
||||
}, [handleForegroundNotification]);
|
||||
|
||||
/**
|
||||
* FCM 토큰 해제 (로그아웃 시 호출)
|
||||
*/
|
||||
const cleanup = useCallback(async () => {
|
||||
console.log('[useFCM] Cleaning up FCM...');
|
||||
await unregisterFCMToken();
|
||||
initialized.current = false;
|
||||
}, []);
|
||||
|
||||
return { cleanup };
|
||||
}
|
||||
|
||||
/**
|
||||
* FCM cleanup 함수만 반환하는 훅
|
||||
* (로그아웃 로직에서 사용)
|
||||
*/
|
||||
export function useFCMCleanup() {
|
||||
const cleanup = useCallback(async () => {
|
||||
if (!isCapacitorNative()) return;
|
||||
await unregisterFCMToken();
|
||||
}, []);
|
||||
|
||||
return { cleanup };
|
||||
}
|
||||
Reference in New Issue
Block a user