Files
sam-react-prod/src/hooks/useFCM.ts
kent f400f01db7 feat(WEB): FCM 푸시 알림 시스템 구현
- FCMProvider 컨텍스트 및 useFCM 훅 추가
- Capacitor FCM 플러그인 통합
- 알림 사운드 파일 추가 (default.wav, push_notification.wav)
- Firebase 메시징 패키지 의존성 추가
2025-12-30 17:31:23 +09:00

138 lines
3.3 KiB
TypeScript

'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 };
}