/**
* Firebase Initialization - Simplified Version (Using Compat SDK)
*
* This module handles Firebase initialization and FCM token retrieval.
* Uses compat SDK to match the service worker implementation.
*/
// Use compat SDK to match service worker
// This ensures consistent token generation
declare const firebase: {
apps: unknown[];
initializeApp: (config: unknown) => unknown;
messaging: () => {
getToken: (options: { vapidKey: string }) => Promise<string>;
onMessage: (callback: (payload: unknown) => void) => void;
};
};
/**
* Firebase configuration
* Using the same config as the service worker
*/
const firebaseConfig = {
apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY,
authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN,
projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
storageBucket: process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET,
messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID,
appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID,
measurementId: process.env.NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID,
};
/**
* VAPID key for web push notifications
* This key is used to identify your app to Firebase
*/
const VAPID_KEY = process.env.NEXT_PUBLIC_FIREBASE_VAPID_KEY;
// Firebase instances
let messaging: ReturnType<typeof firebase.messaging> | null = null;
let isInitialized = false;
/**
* Load Firebase compat SDK scripts
*/
function loadFirebaseScripts(): Promise<void> {
return new Promise((resolve, reject) => {
// Check if already loaded
if (typeof firebase !== 'undefined') {
resolve();
return;
}
// Load Firebase app compat
const appScript = document.createElement('script');
appScript.src =
'https://www.gstatic.com/firebasejs/10.7.1/firebase-app-compat.js';
appScript.async = true;
appScript.onload = () => {
// Load Firebase messaging compat
const messagingScript = document.createElement('script');
messagingScript.src =
'https://www.gstatic.com/firebasejs/10.7.1/firebase-messaging-compat.js';
messagingScript.async = true;
messagingScript.onload = () => {
// console.log('[FCM] ✅ Firebase scripts loaded');
resolve();
};
messagingScript.onerror = () => {
reject(new Error('Failed to load Firebase messaging script'));
};
document.head.appendChild(messagingScript);
};
appScript.onerror = () => {
reject(new Error('Failed to load Firebase app script'));
};
document.head.appendChild(appScript);
});
}
/**
* Initialize Firebase app
*/
export async function initializeFirebase(): Promise<void> {
// console.log('[FCM] Initializing Firebase...');
// Return if already initialized
if (isInitialized) {
// console.log('[FCM] Firebase already initialized');
return;
}
try {
// Load Firebase scripts first
await loadFirebaseScripts();
// Check if Firebase app already exists
if (!firebase.apps.length) {
firebase.initializeApp(firebaseConfig);
// console.log('[FCM] ✅ Firebase app initialized');
} else {
// console.log('[FCM] ✅ Using existing Firebase app');
}
// Register service worker and initialize messaging
if (typeof window !== 'undefined' && 'serviceWorker' in navigator) {
try {
// Register the service worker
// console.log('[FCM] Registering service worker...');
await navigator.serviceWorker.register('/firebase-messaging-sw.js');
// console.log('[FCM] ✅ Service worker registered');
// Wait for service worker to be ready
await navigator.serviceWorker.ready;
// console.log('[FCM] ✅ Service worker ready');
// Initialize Firebase Messaging
messaging = firebase.messaging();
// console.log('[FCM] ✅ Firebase Messaging initialized');
isInitialized = true;
} catch (error) {
console.error('[FCM] ❌ Error setting up service worker:', error);
}
}
} catch (error) {
// console.error('[FCM] ❌ Error initializing Firebase:', error);
throw error;
}
}
/**
* Get FCM token for push notifications
*
* @returns FCM token string, or null if unavailable
*/
export async function getFCMToken(): Promise<string | null> {
try {
// console.log('[FCM] Getting FCM token...');
// Initialize Firebase if not already done
if (!isInitialized) {
await initializeFirebase();
}
// Check if messaging is available
if (!messaging) {
// console.warn('[FCM] ⚠️ Messaging not available');
return null;
}
// Check if browser supports notifications
if (!('Notification' in window)) {
// console.warn('[FCM] ⚠️ Browser does not support notifications');
return null;
}
// Check current permission
let permission = Notification.permission;
// console.log('[FCM] Current permission:', permission);
// Request permission if not yet granted
// Note: Some browsers block automatic permission requests
// The user must interact with the page first
if (permission === 'default') {
// console.log('[FCM] Requesting notification permission...');
try {
permission = await Notification.requestPermission();
// console.log('[FCM] Permission result:', permission);
} catch (error) {
console.error('[FCM] Error requesting permission:', error);
return null;
}
}
// Check if permission was denied
if (permission === 'denied') {
// console.warn(
// '[FCM] ⚠️ Notification permission denied. Push notifications will not work.'
// );
return null;
}
// Check if permission was granted
if (permission !== 'granted') {
// console.warn(
// '[FCM] ⚠️ Notification permission not granted (still default). User needs to allow notifications in browser settings or when prompted. Login will work without FCM token.'
// );
return null;
}
// Check if VAPID key is available
if (!VAPID_KEY) {
console.error('[FCM] ❌ VAPID key is not configured');
return null;
}
// Get FCM token
// console.log('[FCM] Getting token with VAPID key...');
const token = await messaging.getToken({ vapidKey: VAPID_KEY });
if (token) {
// console.log('[FCM] ✅ Token retrieved successfully');
// console.log('[FCM] Token:', token);
return token;
} else {
// console.warn('[FCM] ⚠️ No token available');
return null;
}
} catch (error) {
console.error('[FCM] ❌ Error getting FCM token:', error);
return null;
}
}
/**
* Firebase message payload type
*/
export interface FirebaseMessagePayload {
notification?: {
title?: string;
body?: string;
image?: string;
};
data?: Record<string, string>;
[key: string]: unknown;
}
/**
* Listen for foreground messages (when app is open)
*
* @param callback - Function to call when a message is received
* @returns Unsubscribe function to stop listening
*/
export function onForegroundMessage(
callback: (payload: FirebaseMessagePayload) => void
): (() => void) | void {
if (!messaging) {
// console.warn('[FCM] ⚠️ Messaging not available for foreground messages');
return;
}
// onMessage returns an unsubscribe function
const unsubscribe = messaging.onMessage((payload) => {
// console.log('[FCM] 📩 Foreground message received');
// console.log('[FCM] Payload:', payload);
callback(payload as FirebaseMessagePayload);
});
// Return the unsubscribe function
return unsubscribe;
}
/**
* Get Messaging instance
*/
export function getMessagingInstance(): typeof messaging {
return messaging;
}