refactor: remove Firebase Cloud Messaging configuration and related code for push notifications

This commit is contained in:
Nawaz Dhandala
2026-02-10 21:38:42 +00:00
parent 51ed9fc2bb
commit 09d82f64de
12 changed files with 44 additions and 294 deletions

View File

@@ -548,13 +548,3 @@ export const InboundEmailDomain: string | undefined =
export const InboundEmailWebhookSecret: string | undefined =
process.env["INBOUND_EMAIL_WEBHOOK_SECRET"] || undefined;
// Firebase Cloud Messaging (FCM) Configuration for Native Push Notifications
export const FirebaseProjectId: string | undefined =
process.env["FIREBASE_PROJECT_ID"] || undefined;
export const FirebaseClientEmail: string | undefined =
process.env["FIREBASE_CLIENT_EMAIL"] || undefined;
export const FirebasePrivateKey: string | null = decodePrivateKey(
process.env["FIREBASE_PRIVATE_KEY"],
);

View File

@@ -10,12 +10,8 @@ import {
VapidPublicKey,
VapidPrivateKey,
VapidSubject,
FirebaseProjectId,
FirebaseClientEmail,
FirebasePrivateKey,
} from "../EnvironmentConfig";
import webpush from "web-push";
import * as firebaseAdmin from "firebase-admin";
import { Expo, ExpoPushMessage, ExpoPushTicket } from "expo-server-sdk";
import PushNotificationUtil from "../Utils/PushNotificationUtil";
import { LIMIT_PER_PROJECT } from "../../Types/Database/LimitMax";
@@ -47,36 +43,8 @@ export interface PushNotificationOptions {
export default class PushNotificationService {
public static isWebPushInitialized = false;
public static isFirebaseInitialized = false;
private static expoClient: Expo = new Expo();
public static initializeFirebase(): void {
if (this.isFirebaseInitialized) {
return;
}
if (!FirebaseProjectId || !FirebaseClientEmail || !FirebasePrivateKey) {
logger.warn(
"Firebase credentials not configured. Native push notifications (iOS/Android) will not work.",
);
return;
}
try {
firebaseAdmin.initializeApp({
credential: firebaseAdmin.credential.cert({
projectId: FirebaseProjectId,
clientEmail: FirebaseClientEmail,
privateKey: FirebasePrivateKey,
}),
});
this.isFirebaseInitialized = true;
logger.info("Firebase Admin SDK initialized successfully");
} catch (error: any) {
logger.error(`Failed to initialize Firebase Admin SDK: ${error.message}`);
}
}
public static initializeWebPush(): void {
if (this.isWebPushInitialized) {
return;
@@ -360,77 +328,6 @@ export default class PushNotificationService {
}
}
private static async sendFcmPushNotification(
fcmToken: string,
message: PushNotificationMessage,
deviceType: PushDeviceType,
_options: PushNotificationOptions,
): Promise<void> {
if (!this.isFirebaseInitialized) {
this.initializeFirebase();
}
if (!this.isFirebaseInitialized) {
throw new Error("Firebase Admin SDK not configured");
}
try {
const dataPayload: { [key: string]: string } = {};
if (message.data) {
for (const key of Object.keys(message.data)) {
dataPayload[key] = String(message.data[key]);
}
}
if (message.url || message.clickAction) {
dataPayload["url"] = message.url || message.clickAction || "";
}
const fcmMessage: firebaseAdmin.messaging.Message = {
token: fcmToken,
notification: {
title: message.title,
body: message.body,
},
data: dataPayload,
android: {
priority: "high" as const,
notification: {
sound: "default",
channelId: "oncall_high",
},
},
apns: {
payload: {
aps: {
sound: "default",
badge: 1,
},
},
},
};
await firebaseAdmin.messaging().send(fcmMessage);
logger.info(
`FCM push notification sent successfully to ${deviceType} device`,
);
} catch (error: any) {
logger.error(
`Failed to send FCM push notification to ${deviceType} device: ${error.message}`,
);
// If the token is invalid, log it
if (
error.code === "messaging/invalid-registration-token" ||
error.code === "messaging/registration-token-not-registered"
) {
logger.info("FCM token is invalid or unregistered");
}
throw error;
}
}
private static async sendExpoPushNotification(
expoPushToken: string,
message: PushNotificationMessage,

View File

@@ -98,7 +98,6 @@
"esbuild": "^0.25.5",
"expo-server-sdk": "^3.10.0",
"express": "^4.21.1",
"firebase-admin": "^13.6.1",
"formik": "^2.4.6",
"history": "^5.3.0",
"ioredis": "^5.3.2",

View File

@@ -1,114 +1,33 @@
# Firebase Push Notifications
# Push Notifications
To enable native push notifications (iOS/Android) for your self-hosted OneUptime instance, you need to configure Firebase Cloud Messaging (FCM). This allows OneUptime to send push notifications to mobile app users.
Native push notifications (iOS/Android) are powered by **Expo Push** and require **no server-side configuration** for self-hosted instances.
## Prerequisites
## How It Works
- Google account with access to [Firebase Console](https://console.firebase.google.com)
- Access to your OneUptime server configuration
- OneUptime mobile app installed on user devices
The OneUptime mobile app registers an Expo Push Token with the backend. When the backend needs to send a notification it POSTs to the public Expo Push API, which routes the message to Apple APNs or Google FCM on behalf of the app.
## Setup Instructions
Web push notifications continue to use VAPID keys and the Web Push protocol.
### Step 1: Create a Firebase Project
## Self-Hosted Setup
1. Go to the [Firebase Console](https://console.firebase.google.com)
2. Click **"Create a project"** (or select an existing project)
3. Enter a project name (e.g., "OneUptime Notifications")
4. Follow the prompts to complete project creation
### Step 2: Generate a Service Account Key
1. In your Firebase project, go to **Project Settings** (gear icon in the top-left)
2. Click the **"Service accounts"** tab
3. Click **"Generate new private key"**
4. Confirm by clicking **"Generate key"**
5. A JSON file will be downloaded automatically — keep this file secure
### Step 3: Extract Values from the JSON File
Open the downloaded JSON file. You will need three values:
```json
{
"project_id": "your-project-id",
"client_email": "firebase-adminsdk-xxxxx@your-project-id.iam.gserviceaccount.com",
"private_key": "-----BEGIN RSA PRIVATE KEY-----\n...\n-----END RSA PRIVATE KEY-----\n"
}
```
Map these to the OneUptime environment variables:
| JSON Field | Environment Variable |
|------------|---------------------|
| `project_id` | `FIREBASE_PROJECT_ID` |
| `client_email` | `FIREBASE_CLIENT_EMAIL` |
| `private_key` | `FIREBASE_PRIVATE_KEY` |
### Step 4: Configure OneUptime
#### Docker Compose
Add these environment variables to your `config.env` file:
```bash
FIREBASE_PROJECT_ID=your-project-id
FIREBASE_CLIENT_EMAIL=firebase-adminsdk-xxxxx@your-project-id.iam.gserviceaccount.com
FIREBASE_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----\n...\n-----END RSA PRIVATE KEY-----\n"
```
**Note:** The private key contains newlines. Wrap it in double quotes in your env file.
#### Kubernetes with Helm
Add these to your `values.yaml` file:
```yaml
firebase:
projectId: "your-project-id"
clientEmail: "firebase-adminsdk-xxxxx@your-project-id.iam.gserviceaccount.com"
privateKey: "-----BEGIN RSA PRIVATE KEY-----\n...\n-----END RSA PRIVATE KEY-----\n"
```
**Important:** Restart your OneUptime server after adding these environment variables so they take effect.
## Environment Variables Reference
| Variable | Description | Required |
|----------|-------------|----------|
| `FIREBASE_PROJECT_ID` | The Firebase project ID from the service account JSON | Yes |
| `FIREBASE_CLIENT_EMAIL` | The service account email from the JSON file | Yes |
| `FIREBASE_PRIVATE_KEY` | The RSA private key from the JSON file (contains newlines) | Yes |
No push notification configuration is required. The mobile app binary handles all platform registration automatically via Expo's push infrastructure.
## Troubleshooting
### Common Issues
### Push notifications not arriving
**Push notifications not being delivered:**
- Verify all three environment variables are set correctly
- Ensure the private key includes the `-----BEGIN RSA PRIVATE KEY-----` and `-----END RSA PRIVATE KEY-----` markers
- Check that the service account has the necessary permissions in Firebase
- Restart your OneUptime server after changing configuration
- Ensure the mobile app was built with EAS Build (Expo Go does not support push notifications)
- Verify the device is registered in the `UserPush` table in your database
- Check OneUptime server logs for Expo Push API errors
- Confirm the device has an active internet connection and notification permissions enabled
**"Invalid credentials" error:**
- Double-check that `FIREBASE_PROJECT_ID` matches the `project_id` in your JSON file
- Verify `FIREBASE_CLIENT_EMAIL` matches the `client_email` exactly
- Ensure the private key is properly quoted and newlines are preserved
### "DeviceNotRegistered" errors in logs
**"Service account not found" error:**
- The service account may have been deleted in the Firebase Console
- Generate a new service account key and update your configuration
## Security Best Practices
1. **Keep the service account key secure** — never commit it to version control
2. **Limit service account permissions** — the default Firebase Admin SDK role is sufficient
3. **Rotate keys periodically** — generate new service account keys and update your configuration
4. **Monitor usage** — check the Firebase Console for unusual notification activity
The Expo Push Token is no longer valid. This usually means the app was uninstalled or the user revoked notification permissions. The token will be cleaned up automatically.
## Support
If you encounter issues with Firebase push notifications, please:
If you encounter issues with push notifications, please:
1. Check the troubleshooting section above
2. Review the OneUptime logs for detailed error messages

View File

@@ -54,7 +54,7 @@ This document outlines the design for a native mobile app (iOS & Android) for On
| **Framework** | React Native + Expo | Shares React expertise with existing Dashboard team; single codebase for iOS & Android; Expo simplifies build/deploy |
| **Navigation** | React Navigation 7 | Industry standard for React Native; supports deep linking, stack/tab navigation |
| **State & Caching** | TanStack Query (React Query) | Automatic caching, background refetch, optimistic updates, offline support |
| **Push Notifications** | Firebase Cloud Messaging (FCM) + APNs | Native push for both platforms; FCM handles Android natively and proxies to APNs for iOS |
| **Push Notifications** | Expo Push + APNs/FCM | Native push for both platforms; Expo Push routes to APNs and FCM with zero server-side credentials |
| **Auth Token Storage** | react-native-keychain | Secure storage in iOS Keychain / Android Keystore |
| **HTTP Client** | Axios | Consistent with existing Common/UI/Utils/API patterns |
| **Forms** | React Hook Form | Lightweight, performant form handling |
@@ -357,7 +357,7 @@ Swipe actions use iOS-native feel (spring animation, haptic on threshold).
│ └────────────────────────────────┘ │
│ ┌──────────────────────────────────────────────────┐│
│ │ Notification Service (Extended) ││
│ │ web-push (existing) + firebase-admin (new) ││
│ │ web-push (existing) + expo-server-sdk (native) ││
│ └──────────────────────────────────────────────────┘│
│ │
│ ┌──────────────────────────────────────────────────┐│
@@ -369,8 +369,8 @@ Swipe actions use iOS-native feel (spring animation, haptic on threshold).
┌──────────────────────────────────────────────────────┐
Firebase Cloud Messaging (FCM)
Delivers push to iOS (APNs) & Android │
Expo Push Service
Routes to APNs (iOS) & FCM (Android)
└──────────────────────────────────────────────────────┘
```
@@ -396,16 +396,16 @@ export enum PushDeviceType {
**Changes needed:**
- Add `PushDeviceType` enum with `web`, `ios`, `android`
- Update `UserPush.deviceType` to use the enum
- For native devices, `deviceToken` stores the FCM registration token (plain string) instead of a web push subscription JSON object
- For native devices, `deviceToken` stores the Expo Push Token (plain string) instead of a web push subscription JSON object
- Add database migration for the new enum values
**Migration file:** `Common/Server/Infrastructure/Postgres/SchemaMigrations/XXXXXXXXX-AddMobileDeviceTypes.ts`
### 2. Extend `PushNotificationService` for FCM
### 2. Extend `PushNotificationService` for Expo Push
**File:** `Common/Server/Services/PushNotificationService.ts`
Add Firebase Cloud Messaging support alongside existing web-push:
Add Expo Push support alongside existing web-push:
```typescript
// Routing logic in sendPushNotification():
@@ -413,23 +413,20 @@ if (deviceType === PushDeviceType.Web) {
// Existing web-push flow (unchanged)
await webpush.sendNotification(subscription, payload);
} else if (deviceType === PushDeviceType.iOS || deviceType === PushDeviceType.Android) {
// New FCM flow
await firebaseAdmin.messaging().send({
token: fcmToken,
notification: { title, body },
// Expo Push flow — no server credentials needed
await expoClient.sendPushNotificationsAsync([{
to: expoPushToken,
title, body,
data: { type, entityId, projectId },
apns: { payload: { aps: { sound: "default", badge: 1 } } },
android: { priority: "high", notification: { sound: "default" } },
});
sound: "default",
priority: "high",
}]);
}
```
**New dependency:** `firebase-admin` npm package
**New dependency:** `expo-server-sdk` npm package
**Configuration:** Add Firebase service account credentials to environment config:
- `FIREBASE_PROJECT_ID`
- `FIREBASE_CLIENT_EMAIL`
- `FIREBASE_PRIVATE_KEY`
**Configuration:** No server-side credentials required. Expo Push is a public API.
### 3. Add Mobile-Friendly On-Call Status Endpoint
@@ -804,7 +801,7 @@ Since OneUptime is self-hostable, the app must first determine which server to c
┌─────────┐ POST {serverUrl}/api/user-push ┌──────────┐
│ Register│ ──────────────────────────────────── │ Backend │
│ Device │ { fcmToken, deviceType, │ BaseAPI │
│ Device │ { expoPushToken, deviceType, │ BaseAPI │
│ │ deviceName } │ │
└────┬─────┘ └──────────┘
@@ -1168,7 +1165,7 @@ MobileApp/
│ │ └── useBiometric.ts
│ │
│ ├── notifications/ # Push notification setup
│ │ ├── setup.ts # FCM registration & permission request
│ │ ├── setup.ts # Expo Push registration & permission request
│ │ ├── handlers.ts # Notification tap/action handlers
│ │ ├── channels.ts # Android notification channels
│ │ └── categories.ts # iOS notification categories
@@ -1226,9 +1223,8 @@ MobileApp/
- [ ] Add `PushDeviceType` enum (`web`, `ios`, `android`) to codebase
- [ ] Update `UserPush` model to support native device types
- [ ] Create database migration for new device types
- [ ] Install `firebase-admin` SDK
- [ ] Extend `PushNotificationService` with FCM send logic
- [ ] Configure Firebase project and add credentials to environment
- [ ] Install `expo-server-sdk` package
- [ ] Extend `PushNotificationService` with Expo Push send logic
**Mobile:**
- [ ] Initialize Expo project in `MobileApp/` directory
@@ -1281,21 +1277,21 @@ MobileApp/
### Phase 4: Push Notifications (Weeks 7-8)
**Backend:**
- [ ] Test FCM integration end-to-end
- [ ] Ensure all worker jobs correctly route to FCM for native devices
- [ ] Test Expo Push integration end-to-end
- [ ] Ensure all worker jobs correctly route to Expo Push for native devices
- [ ] Add push notification payload structure with deep link data
**Mobile:**
- [ ] Configure FCM in Expo (expo-notifications + @react-native-firebase/messaging)
- [ ] Configure expo-notifications
- [ ] Implement push notification permission request flow
- [ ] Register FCM token with backend on login (create UserPush)
- [ ] Unregister FCM token on logout (delete UserPush)
- [ ] Register Expo Push token with backend on login (create UserPush)
- [ ] Unregister Expo Push token on logout (delete UserPush)
- [ ] Handle foreground notifications (in-app banner)
- [ ] Handle background notification taps (deep link to detail screen)
- [ ] Implement actionable notifications (Acknowledge button)
- [ ] Set up Android notification channels
- [ ] Set up iOS notification categories
- [ ] Handle FCM token refresh
- [ ] Handle Expo Push token refresh
**Deliverable:** User receives push notifications and can acknowledge from notification.

View File

@@ -250,7 +250,7 @@ DocsNav.push({
url: "/docs/self-hosted/github-integration",
},
{
title: "Firebase Push Notifications",
title: "Push Notifications",
url: "/docs/self-hosted/firebase-push-notifications",
},
{

View File

@@ -203,13 +203,6 @@ Usage:
- name: VAPID_PRIVATE_KEY
value: {{ $.Values.vapid.privateKey }}
- name: FIREBASE_PROJECT_ID
value: {{ $.Values.firebase.projectId }}
- name: FIREBASE_CLIENT_EMAIL
value: {{ $.Values.firebase.clientEmail }}
- name: FIREBASE_PRIVATE_KEY
value: {{ $.Values.firebase.privateKey | quote }}
- name: SLACK_APP_CLIENT_SECRET
value: {{ $.Values.slackApp.clientSecret }}

View File

@@ -727,25 +727,6 @@
},
"additionalProperties": false
},
"firebase": {
"type": "object",
"description": "Firebase Cloud Messaging (FCM) Configuration for Native Push Notifications (iOS/Android)",
"properties": {
"projectId": {
"type": ["string", "null"],
"description": "Firebase project ID"
},
"clientEmail": {
"type": ["string", "null"],
"description": "Firebase service account client email"
},
"privateKey": {
"type": ["string", "null"],
"description": "Firebase service account private key"
}
},
"additionalProperties": false
},
"incidents": {
"type": "object",
"properties": {

View File

@@ -284,14 +284,6 @@ vapid:
privateKey:
subject: mailto:support@oneuptime.com
# Firebase Cloud Messaging (FCM) Configuration for Native Push Notifications (iOS/Android)
# Create a Firebase project and generate a service account key
# See: https://console.firebase.google.com
firebase:
projectId:
clientEmail:
privateKey:
incidents:
disableAutomaticCreation: false

View File

@@ -94,13 +94,9 @@ ServerUrlScreen → LoginScreen → MainTabNavigator (Home, Incidents, Alerts, S
## Push Notifications
To enable native push notifications (iOS/Android), configure Firebase Cloud Messaging on your OneUptime instance:
Native push notifications (iOS/Android) are powered by Expo Push and require no server-side configuration. The mobile app registers an Expo Push Token with the backend on login. The backend sends notifications via the public Expo Push API.
- `FIREBASE_PROJECT_ID`
- `FIREBASE_CLIENT_EMAIL`
- `FIREBASE_PRIVATE_KEY`
For web push, configure VAPID keys instead. See the [Firebase Push Notifications docs](../Docs/Content/self-hosted/firebase-push-notifications.md) for setup instructions.
Web push uses VAPID keys (configured separately). See the [Push Notifications docs](../Docs/Content/self-hosted/firebase-push-notifications.md) for details.
## Troubleshooting

View File

@@ -297,14 +297,6 @@ VAPID_PUBLIC_KEY=
VAPID_PRIVATE_KEY=
VAPID_SUBJECT=mailto:support@oneuptime.com
# Firebase Cloud Messaging (FCM) Configuration for Native Push Notifications (iOS/Android)
# Create a Firebase project at https://console.firebase.google.com
# Go to Project Settings > Service accounts > Generate new private key
# Extract values from the downloaded JSON file:
FIREBASE_PROJECT_ID=
FIREBASE_CLIENT_EMAIL=
FIREBASE_PRIVATE_KEY=
# LLM Environment Variables
# Hugging Face Token for LLM Server to downlod models from Hugging Face

View File

@@ -82,11 +82,6 @@ x-common-runtime-variables: &common-runtime-variables
VAPID_PRIVATE_KEY: ${VAPID_PRIVATE_KEY}
# Firebase Cloud Messaging (FCM) for Native Push Notifications
FIREBASE_PROJECT_ID: ${FIREBASE_PROJECT_ID}
FIREBASE_CLIENT_EMAIL: ${FIREBASE_CLIENT_EMAIL}
FIREBASE_PRIVATE_KEY: ${FIREBASE_PRIVATE_KEY}
DATABASE_PORT: ${DATABASE_PORT}
DATABASE_USERNAME: ${DATABASE_USERNAME}
DATABASE_PASSWORD: ${DATABASE_PASSWORD}