feat: FCM 포그라운드 알림 처리 강화
- type별 토스트 스타일 분기 (error/warning/success/info) - 포그라운드 사운드 재생 (sound_key 기반) - URL 클릭 이동 지원 - 앱 상태 감지 (포그라운드/백그라운드) - sounds/ 디렉토리 추가
This commit is contained in:
125
public/js/fcm.js
125
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 }),
|
||||
};
|
||||
|
||||
})();
|
||||
|
||||
0
public/sounds/.gitkeep
Normal file
0
public/sounds/.gitkeep
Normal file
Reference in New Issue
Block a user