diff --git a/public/js/fcm.js b/public/js/fcm.js index 38906260..c247af9c 100644 --- a/public/js/fcm.js +++ b/public/js/fcm.js @@ -1,20 +1,30 @@ /** * FCM (Firebase Cloud Messaging) Push Notification Handler * Capacitor 앱에서만 동작, 웹 브라우저에서는 무시됨 + * + * Payload data 스키마: + * - type: 알림 타입 (invoice_failed, order_completed 등) + * - url: 클릭 시 이동 URL + * - sound_key: 사운드 파일 키 (sounds/{sound_key}.wav) */ (function () { 'use strict'; - console.log('FCM.js LOADED'); + console.log('[FCM] fcm.js LOADED v2'); const CONFIG = { apiBaseUrl: window.SAM_CONFIG?.apiBaseUrl || 'https://api.codebridge-x.com', fcmTokenKey: 'fcm_token', apiTokenKey: 'api_access_token', apiKeyHeader: window.SAM_CONFIG?.apiKey || '', + soundBasePath: '/sounds/', + defaultSound: 'default', }; + // 앱 상태 (포그라운드 여부) + let isAppForeground = true; + // DOM 준비되면 실행 (이미 로드됐으면 즉시) if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', bootstrap); @@ -28,6 +38,16 @@ return; } + // 앱 상태 리스너 (포그라운드/백그라운드) + if (window.Capacitor?.Plugins?.App) { + const { App } = Capacitor.Plugins; + + App.addListener('appStateChange', ({ isActive }) => { + isAppForeground = isActive; + console.log('[FCM] App state:', isActive ? 'foreground' : 'background'); + }); + } + await initializeFCM(); } @@ -51,9 +71,7 @@ PushNotifications.addListener('pushNotificationReceived', (notification) => { console.log('[FCM] Push received (foreground):', notification); - if (typeof showToast === 'function') { - showToast(notification.body || notification.title, 'info'); - } + handleForegroundNotification(notification); }); PushNotifications.addListener('pushNotificationActionPerformed', (action) => { @@ -182,9 +200,108 @@ return window.SAM_CONFIG?.appVersion || null; } + /** + * 포그라운드 알림 처리 + */ + function handleForegroundNotification(notification) { + const data = notification.data || {}; + const type = data.type || 'default'; + const url = data.url; + const soundKey = data.sound_key; + + console.log('[FCM] Notification data:', { type, url, soundKey }); + + // 1. 포그라운드에서만 사운드 재생 (백그라운드는 OS 채널 사운드) + if (isAppForeground && soundKey) { + playNotificationSound(soundKey); + } + + // 2. 타입별 토스트 메시지 표시 + const title = notification.title || '알림'; + const body = notification.body || ''; + const toastType = getToastTypeByNotificationType(type); + + if (typeof showToast === 'function') { + showToast(`${title}: ${body}`, toastType); + } + + // 3. 클릭 가능한 토스트 (URL이 있는 경우) + if (url && typeof showClickableToast === 'function') { + showClickableToast(title, body, () => { + window.location.href = url; + }); + } + } + + /** + * 알림 사운드 재생 (포그라운드 전용) + */ + function playNotificationSound(soundKey) { + try { + const soundPath = `${CONFIG.soundBasePath}${soundKey}.wav`; + const audio = new Audio(soundPath); + + audio.volume = 0.5; + audio.play().catch(err => { + console.warn('[FCM] Sound play failed, trying default:', err.message); + // 기본 사운드 시도 + if (soundKey !== CONFIG.defaultSound) { + const defaultAudio = new Audio(`${CONFIG.soundBasePath}${CONFIG.defaultSound}.wav`); + defaultAudio.volume = 0.5; + defaultAudio.play().catch(() => {}); + } + }); + } catch (err) { + console.warn('[FCM] Sound error:', err); + } + } + + /** + * 알림 타입별 토스트 스타일 결정 + */ + function getToastTypeByNotificationType(type) { + const typeMap = { + // 긴급/에러 + 'invoice_failed': 'error', + 'payment_failed': 'error', + 'order_cancelled': 'error', + + // 경고 + 'approval_required': 'warning', + 'stock_low': 'warning', + + // 성공 + 'order_completed': 'success', + 'payment_completed': 'success', + 'approval_approved': 'success', + + // 기본 + 'default': 'info', + }; + + return typeMap[type] || 'info'; + } + + /** + * 클릭 가능한 토스트 (URL 이동용) + * showClickableToast가 없으면 기본 토스트 사용 + */ + if (typeof window.showClickableToast === 'undefined') { + window.showClickableToast = function(title, body, onClick) { + // 기본 구현: 일반 토스트 + 클릭 핸들러는 무시 + if (typeof showToast === 'function') { + showToast(`${title}: ${body}`, 'info'); + } + console.log('[FCM] showClickableToast not implemented, URL click ignored'); + }; + } + window.FCM = { unregisterToken, reinitialize: initializeFCM, + // 디버그/테스트용 + playSound: playNotificationSound, + testForeground: (data) => handleForegroundNotification({ title: 'Test', body: 'Test notification', data }), }; })(); diff --git a/public/sounds/.gitkeep b/public/sounds/.gitkeep new file mode 100644 index 00000000..e69de29b