Receive Firebase notifications in your Electron app using the Firebase JS SDK and Web Push APIs
Receive Firebase push notifications in your Electron app using the modern Firebase JS SDK (v9+) and Web Push APIs.
``bash`
npm i firebase-electron-v2
This library separates setup between Electron's main and renderer processes.
The main process setup is minimal. It primarily prepares to listen for events forwarded from the renderer process via IPC.
`typescript
import { app, BrowserWindow } from 'electron';
import { setup as setupPushReceiver } from 'firebase-electron';
let mainWindow: BrowserWindow | null;
app.on('ready', () => {
mainWindow = new BrowserWindow({
// ... your window options
webPreferences: {
// Ensure you have appropriate webPreferences for IPC and potentially contextIsolation
// preload: path.join(__dirname, 'preload.js') // Example if using preload
nodeIntegration: false, // Recommended: false
contextIsolation: true, // Recommended: true
// Service workers require secure origins (https or localhost) or custom protocols
},
});
// Option 1: Serve your index.html via a secure context
// mainWindow.loadURL('https://localhost:3000'); // Example using a local dev server
// Option 2: Or register a custom protocol
// protocol.registerFileProtocol('app', (request, callback) => { / ... / });
// mainWindow.loadURL('app://./index.html');
// Call setup after creating the window but before loading content usually works best.
if (mainWindow) {
setupPushReceiver(mainWindow.webContents);
}
// ... rest of your main process setup
});
`
This is where the core Firebase messaging initialization happens.
`typescript
// Assuming you have exposed ipcRenderer.send via contextBridge in your preload script
// Example preload.ts:
// import { contextBridge, ipcRenderer } from 'electron';
// contextBridge.exposeInMainWorld('electronIpc', {
// send: (channel, ...args) => ipcRenderer.send(channel, ...args),
// // You might also need to expose ipcRenderer.on for receiving events
// on: (channel, listener) => {
// const subscription = (event, ...args) => listener(...args);
// ipcRenderer.on(channel, subscription);
// return () => ipcRenderer.removeListener(channel, subscription); // Return cleanup function
// }
// });
// In renderer.ts:
declare global {
interface Window {
electronIpc: {
send: (channel: string, ...args: any[]) => void;
on: (channel: string, listener: (...args: any[]) => void) => () => void;
};
}
}
// Import the initialization function from the renderer entry point
import { initializeFirebaseMessaging, type IpcSender } from 'firebase-electron-v2/renderer';
// Import constants from the specific constants entry point
import {
NOTIFICATION_RECEIVED,
TOKEN_UPDATED,
// NOTIFICATION_SERVICE_ERROR, // Optional: Listen for errors forwarded from main if needed
} from 'firebase-electron/dist/electron/consts'; // Note the specific path
// Your Firebase project configuration (including VAPID key)
const firebaseCredentials = {
apiKey: 'YOUR_API_KEY',
authDomain: 'YOUR_AUTH_DOMAIN',
projectId: 'YOUR_PROJECT_ID',
storageBucket: 'YOUR_STORAGE_BUCKET',
messagingSenderId: 'YOUR_MESSAGING_SENDER_ID',
appId: 'YOUR_APP_ID',
vapidKey: 'YOUR_VAPID_KEY',
};
// Create the ipcSender object using the exposed API from preload script
const ipcSender: IpcSender = {
send: window.electronIpc.send, // Pass the actual send function
};
// Call initializeFirebaseMessaging to start the process, passing credentials AND the ipcSender
initializeFirebaseMessaging(firebaseCredentials, ipcSender)
.then(() => {
console.log('Firebase Messaging setup initiated in renderer.');
})
.catch((err) => {
console.error('Error initiating Firebase Messaging setup:', err);
});
// Listen for the updated FCM token (using the exposed 'on' method from preload)
const unsubscribeTokenUpdates = window.electronIpc.on(TOKEN_UPDATED, (token) => {
console.log('FCM Token received in renderer:', token);
// IMPORTANT: Send this token to your application server
// Example: sendTokenToServer(token);
});
// Handle notifications received while the app is in the foreground (using the exposed 'on' method)
const unsubscribeNotifications = window.electronIpc.on(NOTIFICATION_RECEIVED, (notificationPayload) => {
console.log('Notification received in foreground:', notificationPayload);
// Display the notification using an in-app UI or the Notification API
// Example: new Notification(notificationPayload.title, { body: notificationPayload.body });
});
// Optional: Listen for errors forwarded from the main process setup
// const unsubscribeErrors = window.electronIpc.on(NOTIFICATION_SERVICE_ERROR, (error) => {
// console.error('Error reported from main process push receiver setup:', error);
// });
// Remember to clean up listeners when the component unmounts or window closes
// window.addEventListener('beforeunload', () => {
// unsubscribeTokenUpdates();
// unsubscribeNotifications();
// unsubscribeErrors(); // If listening for errors
// });
`
This library relies on a Service Worker to handle push notifications when your application is in the background or closed.
1. Location: The firebase-messaging-sw.js file must be served from the root of your application (e.g., https://your-app.com/firebase-messaging-sw.js or accessible at / on your local development server). This library's build process places the file in dist/firebase-messaging-sw.js. You need to configure your Electron app's build or server setup to copy and serve this file from the root./firebase-config.json
2. Credentials: The service worker runs independently and needs its _own_ copy of the Firebase configuration. You must configure this. Common methods:
- Build-Time Substitution: Use build tools (like Webpack DefinePlugin, Vite env variables) to replace placeholders in the service worker file during your application's build process.
- Fetch Configuration: Have the service worker fetch a JSON configuration file (e.g., ) that you serve alongside your application.
The library's dist/firebase-messaging-sw.js contains placeholders like PLACEHOLDER_API_KEY. You need to modify it or use a build process to inject the correct values.
Example firebase-messaging-sw.js (after replacing placeholders):
`javascript
// firebase-messaging-sw.js (ensure this is served from your app root)
import { initializeApp } from 'firebase/app';
import { getMessaging, onBackgroundMessage } from 'firebase/messaging/sw';
// Replace these with your actual credentials (e.g., via build process)
const firebaseConfig = {
apiKey: 'YOUR_ACTUAL_API_KEY',
authDomain: 'YOUR_ACTUAL_AUTH_DOMAIN',
projectId: 'YOUR_ACTUAL_PROJECT_ID',
storageBucket: 'YOUR_ACTUAL_STORAGE_BUCKET',
messagingSenderId: 'YOUR_ACTUAL_SENDER_ID',
appId: 'YOUR_ACTUAL_APP_ID',
};
// Initialize Firebase
try {
const app = initializeApp(firebaseConfig);
const messaging = getMessaging(app);
onBackgroundMessage(messaging, (payload) => {
console.log('[firebase-messaging-sw.js] Received background message ', payload);
const notificationTitle = payload.notification?.title || 'Background Message';
const notificationOptions = {
body: payload.notification?.body || '',
icon: payload.notification?.icon || '/default-icon.png', // Provide a default icon
};
// Use self.registration to show the notification
self.registration.showNotification(notificationTitle, notificationOptions);
});
} catch (e) {
console.error('Error initializing Firebase in SW:', e);
}
// Optional: Add notification click handler
self.addEventListener('notificationclick', (event) => {
event.notification.close();
console.log('Notification clicked:', event.notification);
// Add logic here to open/focus a window, e.g.:
// event.waitUntil(
// clients.matchAll({ type: 'window', includeUncontrolled: true }).then(windowClients => {
// // Check if a window is already open
// for (let client of windowClients) {
// if (client.url === '/' && 'focus' in client) { // Adjust URL check as needed
// return client.focus();
// }
// }
// // If not, open a new window
// if (clients.openWindow) {
// return clients.openWindow('/'); // Adjust URL to open
// }
// })
// );
});
`
1. Go to Firebase Console & login to your account
2. Select your project
3. Click on the Project Settings cog iconProject Settings
4. Click on General
5. Make sure you're on the tabYour apps
6. Scroll down to the sectionAdd app
7. If you don't have a Web app, click on Web
- Select (>) iconRegister app
- Fill in the required fields
- Click on apiKey
8. Copy the configuration object values (, authDomain, projectId, storageBucket, messagingSenderId, appId) shown under the SDK setup and configuration section (select Config or CDN).Cloud Messaging
9. Go to the tab.Web configuration
10. Under > Web Push certificates, click Generate key pair if you don't have one.Key pair
11. Copy the value in the column. This is your vapidKey.
electron-push-receiver used the Legacy FCM API which is deprecated and being shut down.
This package (firebase-electron-v2) is a refactored solution using the modern Firebase JS SDK (v9+) that relies on standard Web Push APIs via a Service Worker.
Key Differences & Migration Steps:
1. Architecture: The core logic now resides in the renderer process and a service worker, not the main process.
2. Main Process: The setup function (imported from firebase-electron-v2) is still called in the main process, but it primarily sets up IPC listeners.initializeFirebaseMessaging
3. Renderer Process:
- You must import from firebase-electron-v2/renderer.initializeFirebaseMessaging(firebaseCredentials)
- Call instead of sending START_NOTIFICATION_SERVICE via IPC.TOKEN_UPDATED
- Listen for via ipcRenderer.on to get the FCM token and send it to your server.NOTIFICATION_RECEIVED
- Listen for via ipcRenderer.on to handle foreground messages.firebase-messaging-sw.js
4. Service Worker:
- You must create/configure and serve the file (provided in dist/) from your application root.apiKey
- You must provide Firebase credentials within this service worker file (e.g., via a build step).
5. Credentials: You now need the full Firebase Web config (, authDomain, projectId, etc.) plus the vapidKey in your renderer process.
Credits to Matthieu Lemoine and contributors for the original electron-push-receiver.
- Uses modern Firebase JS SDK (v9+) and standard Web Push APIs.
- Relies on a Service Worker for background notifications.
- Clear separation between main (setup) and renderer (initializeFirebaseMessaging) initialization.electron-config
- Uses updated dependencies.
- Removed legacy dependencies (, request, protobufjs, etc.).tsup
- Simplified core logic.
- Requires Node.js >= 18 (as required by modern Firebase SDK).
- Uses for building and vitest for testing.
- Completely written in TypeScript.
> [!CAUTION] > Breaking changes: Requires full Firebase Web config + VAPID key. Initialization logic moved to the renderer. Requires manual setup and configuration of the Service Worker (firebase-messaging-sw.js).
1. Make sure you have Node.js >= v18 installed.
2. Install dependencies with npm install..env
3. If running tests that require credentials, create a file from .env.template and fill in your Firebase project details.npm run build
4. Build with .npm run test
5. Run tests with .
> [!NOTE]
> Currently, the automated tests (npm run test`) are not implemented for the refactored codebase. Contributions are welcome!