From 09d82f64de31b8bee2a05e9b11a1438707061d9f Mon Sep 17 00:00:00 2001 From: Nawaz Dhandala Date: Tue, 10 Feb 2026 21:38:42 +0000 Subject: [PATCH] refactor: remove Firebase Cloud Messaging configuration and related code for push notifications --- Common/Server/EnvironmentConfig.ts | 10 -- .../Services/PushNotificationService.ts | 103 ---------------- Common/package.json | 1 - .../firebase-push-notifications.md | 111 +++--------------- Docs/Plan/MobileApp.md | 56 ++++----- Docs/Utils/Nav.ts | 2 +- .../Public/oneuptime/templates/_helpers.tpl | 7 -- HelmChart/Public/oneuptime/values.schema.json | 19 --- HelmChart/Public/oneuptime/values.yaml | 8 -- MobileApp/README.md | 8 +- config.example.env | 8 -- docker-compose.base.yml | 5 - 12 files changed, 44 insertions(+), 294 deletions(-) diff --git a/Common/Server/EnvironmentConfig.ts b/Common/Server/EnvironmentConfig.ts index 6a37c945ae..b0d72bb2e3 100644 --- a/Common/Server/EnvironmentConfig.ts +++ b/Common/Server/EnvironmentConfig.ts @@ -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"], -); diff --git a/Common/Server/Services/PushNotificationService.ts b/Common/Server/Services/PushNotificationService.ts index 2fbd6fb27a..c80ab4381f 100644 --- a/Common/Server/Services/PushNotificationService.ts +++ b/Common/Server/Services/PushNotificationService.ts @@ -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 { - 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, diff --git a/Common/package.json b/Common/package.json index 2e2d93f19a..85f03bf6e3 100644 --- a/Common/package.json +++ b/Common/package.json @@ -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", diff --git a/Docs/Content/self-hosted/firebase-push-notifications.md b/Docs/Content/self-hosted/firebase-push-notifications.md index 7b3672fbec..a0a44c9a35 100644 --- a/Docs/Content/self-hosted/firebase-push-notifications.md +++ b/Docs/Content/self-hosted/firebase-push-notifications.md @@ -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 diff --git a/Docs/Plan/MobileApp.md b/Docs/Plan/MobileApp.md index fe09770427..1ae9a55b9e 100644 --- a/Docs/Plan/MobileApp.md +++ b/Docs/Plan/MobileApp.md @@ -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. diff --git a/Docs/Utils/Nav.ts b/Docs/Utils/Nav.ts index 2fc19cf1d0..fc9c3ac40f 100644 --- a/Docs/Utils/Nav.ts +++ b/Docs/Utils/Nav.ts @@ -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", }, { diff --git a/HelmChart/Public/oneuptime/templates/_helpers.tpl b/HelmChart/Public/oneuptime/templates/_helpers.tpl index 162dab4365..86c42e2fc4 100644 --- a/HelmChart/Public/oneuptime/templates/_helpers.tpl +++ b/HelmChart/Public/oneuptime/templates/_helpers.tpl @@ -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 }} diff --git a/HelmChart/Public/oneuptime/values.schema.json b/HelmChart/Public/oneuptime/values.schema.json index 0d6811c0b0..631b9694b0 100644 --- a/HelmChart/Public/oneuptime/values.schema.json +++ b/HelmChart/Public/oneuptime/values.schema.json @@ -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": { diff --git a/HelmChart/Public/oneuptime/values.yaml b/HelmChart/Public/oneuptime/values.yaml index 870d3d769d..3bcea8e5ef 100644 --- a/HelmChart/Public/oneuptime/values.yaml +++ b/HelmChart/Public/oneuptime/values.yaml @@ -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 diff --git a/MobileApp/README.md b/MobileApp/README.md index edd4c1c997..c760ca9e54 100644 --- a/MobileApp/README.md +++ b/MobileApp/README.md @@ -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 diff --git a/config.example.env b/config.example.env index dcdfe124be..bf44916dd5 100644 --- a/config.example.env +++ b/config.example.env @@ -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 diff --git a/docker-compose.base.yml b/docker-compose.base.yml index 21eb6b5a20..b0a38e0bee 100644 --- a/docker-compose.base.yml +++ b/docker-compose.base.yml @@ -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}