가상매매 시간봉 제거

This commit is contained in:
Macbook
2026-05-28 20:48:28 +09:00
parent 7e3644cb62
commit 15160f7d2c
45 changed files with 996 additions and 405 deletions
+113 -58
View File
@@ -1,12 +1,15 @@
import {
registerFcmToken,
deleteFcmToken,
storageGet,
storageSet,
storageRemove,
} from '../lib/shared';
import { PushNotifications } from '@capacitor/push-notifications';
import { Capacitor } from '@capacitor/core';
import { Capacitor, registerPlugin } from '@capacitor/core';
/** AndroidManifest default_notification_channel_id 와 동일 */
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;
@@ -21,93 +24,145 @@ type FcmHandlers = {
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);
}
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 }>;
}
function attachListeners(handlers: FcmHandlers): void {
if (listenersAttached) return;
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;
void PushNotifications.addListener('registration', async token => {
await GoldenFcm.addListener('registration', async data => {
try {
await registerFcmToken(token.value);
console.info('[FCM] token registered with server');
await registerFcmToken(data.value);
await storageSet(FCM_REGISTERED_KEY, '1');
} catch (e) {
console.warn('[FCM] token register failed', e);
}
});
void PushNotifications.addListener('registrationError', err => {
await GoldenFcm.addListener('registrationError', err => {
console.warn('[FCM] registration error', err);
void storageRemove(FCM_REGISTERED_KEY);
});
void PushNotifications.addListener('pushNotificationReceived', notification => {
await GoldenFcm.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);
await GoldenFcm.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');
/** ① 알림 권한만 (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;
}
}
await ensureAndroidNotificationChannel();
attachListeners(handlers);
export async function isFcmRegisteredOnDevice(): Promise<boolean> {
return (await storageGet(FCM_REGISTERED_KEY)) === '1';
}
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;
/** @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 */ }
initialized = false;
tokenRegistered = false;
listenersAttached = false;
await storageRemove(FCM_REGISTERED_KEY);
await storageRemove(FCM_USER_OPT_IN_KEY);
}
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';
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';
}
}