import { Injectable, type OnModuleInit } from '@nestjs/common'; import { apps, initializeApp, credential, messaging, type ServiceAccount, } from 'firebase-admin'; import { type LoggerService } from '@modules/shared/infrastructure/logger.service'; export interface SendPushDto { token: string; title: string; body: string; data?: Record; } @Injectable() export class FcmService implements OnModuleInit { private initialized = false; constructor(private readonly logger: LoggerService) {} onModuleInit(): void { const serviceAccountJson = process.env['FIREBASE_SERVICE_ACCOUNT']; if (!serviceAccountJson) { this.logger.warn( 'FIREBASE_SERVICE_ACCOUNT not set — push notifications disabled', 'FcmService', ); return; } try { const serviceAccount = JSON.parse(serviceAccountJson) as ServiceAccount; if (!apps.length) { initializeApp({ credential: credential.cert(serviceAccount) }); } this.initialized = true; this.logger.log('Firebase Admin initialized for push notifications', 'FcmService'); } catch (error) { this.logger.error( `Failed to initialize Firebase: ${error instanceof Error ? error.message : String(error)}`, 'FcmService', ); } } get isAvailable(): boolean { return this.initialized; } async send(dto: SendPushDto): Promise { if (!this.initialized) { throw new Error('FCM not initialized — FIREBASE_SERVICE_ACCOUNT not configured'); } try { const messageId = await messaging().send({ token: dto.token, notification: { title: dto.title, body: dto.body, }, data: dto.data, android: { priority: 'high' }, apns: { payload: { aps: { sound: 'default' } } }, }); this.logger.log(`Push sent to token ${dto.token.slice(0, 10)}...: ${messageId}`, 'FcmService'); return messageId; } catch (error) { this.logger.error( `Failed to send push: ${error instanceof Error ? error.message : String(error)}`, 'FcmService', ); throw error; } } }