114 lines
3.2 KiB
TypeScript
114 lines
3.2 KiB
TypeScript
import {
|
|
registerFcmToken,
|
|
deleteFcmToken,
|
|
} from '../lib/shared';
|
|
import { PushNotifications } from '@capacitor/push-notifications';
|
|
import { Capacitor } from '@capacitor/core';
|
|
|
|
/** AndroidManifest default_notification_channel_id 와 동일 */
|
|
export const FCM_CHANNEL_ID = 'goldenchart_trade_signals';
|
|
|
|
export interface FcmPayload {
|
|
type?: string;
|
|
market?: string;
|
|
signalId?: string;
|
|
signalType?: string;
|
|
price?: string;
|
|
}
|
|
|
|
type FcmHandlers = {
|
|
onForeground?: (title: string, body: string, data?: FcmPayload) => void;
|
|
onTap?: (data: FcmPayload) => void;
|
|
};
|
|
|
|
let initialized = false;
|
|
let listenersAttached = false;
|
|
|
|
async function ensureAndroidNotificationChannel(): Promise<void> {
|
|
if (Capacitor.getPlatform() !== 'android') return;
|
|
try {
|
|
await PushNotifications.createChannel({
|
|
id: FCM_CHANNEL_ID,
|
|
name: '매매 시그널 알림',
|
|
description: '전략 조건 일치 시 매수·매도 푸시',
|
|
importance: 5,
|
|
visibility: 1,
|
|
vibration: true,
|
|
});
|
|
} catch (e) {
|
|
console.warn('[FCM] createChannel failed', e);
|
|
}
|
|
}
|
|
|
|
function attachListeners(handlers: FcmHandlers): void {
|
|
if (listenersAttached) return;
|
|
listenersAttached = true;
|
|
|
|
void PushNotifications.addListener('registration', async token => {
|
|
try {
|
|
await registerFcmToken(token.value);
|
|
console.info('[FCM] token registered with server');
|
|
} catch (e) {
|
|
console.warn('[FCM] token register failed', e);
|
|
}
|
|
});
|
|
|
|
void PushNotifications.addListener('registrationError', err => {
|
|
console.warn('[FCM] registration error', err);
|
|
});
|
|
|
|
void PushNotifications.addListener('pushNotificationReceived', notification => {
|
|
const title = notification.title ?? 'GoldenChart';
|
|
const body = notification.body ?? '';
|
|
handlers.onForeground?.(title, body, notification.data as FcmPayload);
|
|
});
|
|
|
|
void PushNotifications.addListener('pushNotificationActionPerformed', action => {
|
|
handlers.onTap?.(action.notification.data as FcmPayload);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* FCM 토큰 등록 (로그인 후 호출 — 서버 fcm_push_enabled 와 별개로 토큰만 등록)
|
|
*/
|
|
export async function initFcmPush(handlers: FcmHandlers = {}): Promise<boolean> {
|
|
if (!Capacitor.isNativePlatform()) {
|
|
console.info('[FCM] Web dev — native push skipped');
|
|
return false;
|
|
}
|
|
|
|
await ensureAndroidNotificationChannel();
|
|
attachListeners(handlers);
|
|
|
|
const perm = await PushNotifications.checkPermissions();
|
|
if (perm.receive !== 'granted') {
|
|
const req = await PushNotifications.requestPermissions();
|
|
if (req.receive !== 'granted') {
|
|
console.warn('[FCM] notification permission denied');
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (!initialized) {
|
|
await PushNotifications.register();
|
|
initialized = true;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
export async function unregisterFcmPush(): Promise<void> {
|
|
if (!Capacitor.isNativePlatform()) return;
|
|
try {
|
|
await deleteFcmToken();
|
|
} catch { /* ignore */ }
|
|
initialized = false;
|
|
}
|
|
|
|
export async function checkPushPermission(): Promise<'granted' | 'denied' | 'prompt'> {
|
|
if (!Capacitor.isNativePlatform()) return 'prompt';
|
|
const perm = await PushNotifications.checkPermissions();
|
|
if (perm.receive === 'granted') return 'granted';
|
|
if (perm.receive === 'denied') return 'denied';
|
|
return 'prompt';
|
|
}
|