Files
goldenChart/app/src/services/fcm.ts
T
2026-05-28 20:48:28 +09:00

169 lines
5.3 KiB
TypeScript

import {
registerFcmToken,
deleteFcmToken,
storageGet,
storageSet,
storageRemove,
} from '../lib/shared';
import { Capacitor, registerPlugin } from '@capacitor/core';
export const FCM_CHANNEL_ID = 'goldenchart_trade_signals';
export const FCM_USER_OPT_IN_KEY = 'gc_fcm_user_opt_in';
export const FCM_REGISTERED_KEY = 'gc_fcm_registered';
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;
};
interface GoldenFcmPlugin {
checkPermissions(): Promise<{ receive: string }>;
requestPermissions(): Promise<{ receive: string }>;
register(): Promise<{ value: string }>;
unregister(): Promise<void>;
addListener(
event: 'registration',
listener: (data: { value: string }) => void,
): Promise<{ remove: () => void }>;
addListener(
event: 'registrationError',
listener: (data: { error?: string }) => void,
): Promise<{ remove: () => void }>;
addListener(
event: 'pushNotificationReceived',
listener: (data: { title?: string; body?: string; data?: FcmPayload }) => void,
): Promise<{ remove: () => void }>;
addListener(
event: 'pushNotificationActionPerformed',
listener: (data: { notification?: { data?: FcmPayload } }) => void,
): Promise<{ remove: () => void }>;
}
const GoldenFcm = registerPlugin<GoldenFcmPlugin>('GoldenFcm');
let tokenRegistered = false;
let listenersAttached = false;
async function userOptedIn(): Promise<boolean> {
return (await storageGet(FCM_USER_OPT_IN_KEY)) === '1';
}
export async function markFcmUserOptIn(): Promise<void> {
await storageSet(FCM_USER_OPT_IN_KEY, '1');
}
async function attachListeners(handlers: {
onForeground?: (title: string, body: string, data?: FcmPayload) => void;
onTap?: (data: FcmPayload) => void;
}): Promise<void> {
if (listenersAttached || Capacitor.getPlatform() !== 'android') return;
listenersAttached = true;
await GoldenFcm.addListener('registration', async data => {
try {
await registerFcmToken(data.value);
await storageSet(FCM_REGISTERED_KEY, '1');
} catch (e) {
console.warn('[FCM] token register failed', e);
}
});
await GoldenFcm.addListener('registrationError', err => {
console.warn('[FCM] registration error', err);
void storageRemove(FCM_REGISTERED_KEY);
});
await GoldenFcm.addListener('pushNotificationReceived', notification => {
const title = notification.title ?? 'GoldenChart';
const body = notification.body ?? '';
handlers.onForeground?.(title, body, notification.data as FcmPayload);
});
await GoldenFcm.addListener('pushNotificationActionPerformed', action => {
handlers.onTap?.(action.notification?.data as FcmPayload);
});
}
/** ① 알림 권한만 (FCM 미호출 — 허용 직후 크래시 방지) */
export async function requestNotificationPermissionOnly(): Promise<boolean> {
if (Capacitor.getPlatform() !== 'android') return false;
await markFcmUserOptIn();
const perm = await GoldenFcm.checkPermissions();
if (perm.receive === 'granted') return true;
const req = await GoldenFcm.requestPermissions();
return req.receive === 'granted';
}
/** ② 권한 허용 후 FCM 토큰 등록 */
export async function registerFcmTokenOnly(): Promise<boolean> {
if (Capacitor.getPlatform() !== 'android') return false;
if (!(await userOptedIn())) return false;
const perm = await GoldenFcm.checkPermissions();
if (perm.receive !== 'granted') return false;
try {
await attachListeners({});
if (tokenRegistered) return true;
await new Promise(r => setTimeout(r, 800));
const result = await GoldenFcm.register();
tokenRegistered = true;
if (result?.value) {
await registerFcmToken(result.value);
await storageSet(FCM_REGISTERED_KEY, '1');
}
return !!result?.value;
} catch (e) {
console.warn('[FCM] register failed', e);
return false;
}
}
export async function isFcmRegisteredOnDevice(): Promise<boolean> {
return (await storageGet(FCM_REGISTERED_KEY)) === '1';
}
/** @deprecated — requestNotificationPermissionOnly + registerFcmTokenOnly 사용 */
export async function initFcmPush(): Promise<boolean> {
const ok = await requestNotificationPermissionOnly();
if (!ok) return false;
await new Promise(r => setTimeout(r, 1000));
return registerFcmTokenOnly();
}
export async function unregisterFcmPush(): Promise<void> {
if (!Capacitor.isNativePlatform()) return;
try {
await deleteFcmToken();
if (Capacitor.getPlatform() === 'android' && (await userOptedIn())) {
await GoldenFcm.unregister();
}
} catch { /* ignore */ }
tokenRegistered = false;
listenersAttached = false;
await storageRemove(FCM_REGISTERED_KEY);
await storageRemove(FCM_USER_OPT_IN_KEY);
}
export async function checkPushPermission(): Promise<'granted' | 'denied' | 'prompt' | 'unknown'> {
if (!Capacitor.isNativePlatform()) return 'unknown';
try {
if (Capacitor.getPlatform() !== 'android') return 'unknown';
const perm = await GoldenFcm.checkPermissions();
if (perm.receive === 'granted') return 'granted';
if (perm.receive === 'denied') return 'denied';
return 'prompt';
} catch {
return 'unknown';
}
}