# Firebase Cloud Messaging (FCM) - Comprehensive Guide ## Overview This guide consolidates all FCM (Firebase Cloud Messaging) knowledge for Sojorn, covering setup, deployment, troubleshooting, and platform-specific considerations for both Web and Android. ## Quick Start (TL;DR) 1. Get VAPID key from Firebase Console 2. Download Firebase service account JSON 3. Update Flutter app with VAPID key 4. Upload JSON to server at `/opt/sojorn/firebase-service-account.json` 5. Add to `/opt/sojorn/.env`: `FIREBASE_CREDENTIALS_FILE=/opt/sojorn/firebase-service-account.json` 6. Restart Go backend 7. Test notifications --- ## Architecture Overview ### How FCM Works in Sojorn 1. **User opens app** → Flutter requests notification permission 2. **Permission granted** → Firebase generates FCM token 3. **Token sent to backend** → Stored in `fcm_tokens` table 4. **Event occurs** (new message, follow, etc.) → Go backend calls `PushService.SendPush()` 5. **FCM sends notification** → User's device/browser receives it 6. **User clicks notification** → App opens to relevant screen ### Notification Triggers - New chat message (`chat_handler.go:156`) - New follower (`user_handler.go:141`) - Follow request accepted (`user_handler.go:319`) --- ## Platform Differences ### Web (Working ✅) - Uses VAPID key for authentication - Service worker handles background messages - Token format: `d2n2ELGKel7yzPL3wZLGSe:APA91b...` - Requires user to grant notification permission in browser ### Android (Requires Setup ❓) - Uses `google-services.json` for authentication - Native Android handles background messages - Token format: Different from web, longer - Requires runtime permission on Android 13+ - Needs notification channels (Android 8+) --- ## Setup Instructions ### Step 1: Get Firebase Credentials #### A. Get VAPID Key (for Web Push) 1. Go to https://console.firebase.google.com/project/sojorn-a7a78/settings/cloudmessaging 2. Scroll to **Web configuration** section 3. Under **Web Push certificates**, copy the **Key pair** 4. It should look like: `BNxS7_very_long_string_of_characters...` #### B. Download Service Account JSON (for Server) 1. Go to https://console.firebase.google.com/project/sojorn-a7a78/settings/serviceaccounts 2. Click **Generate new private key** 3. Click **Generate key** - downloads JSON file 4. Save it somewhere safe (you'll upload it to server) **Example JSON structure:** ```json { "type": "service_account", "project_id": "sojorn-a7a78", "private_key_id": "abc123...", "private_key": "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n", "client_email": "firebase-adminsdk-xxxxx@sojorn-a7a78.iam.gserviceaccount.com", "client_id": "123456789...", "auth_uri": "https://accounts.google.com/o/oauth2/auth", "token_uri": "https://oauth2.googleapis.com/token", "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/..." } ``` ### Step 2: Update Flutter App with VAPID Key **File:** `sojorn_app/lib/config/firebase_web_config.dart` Replace line 24: ```dart static const String _vapidKey = 'YOUR_VAPID_KEY_HERE'; ``` With your actual VAPID key: ```dart static const String _vapidKey = 'BNxS7_your_actual_vapid_key_from_firebase_console'; ``` **Commit and push:** ```bash cd c:\Webs\Sojorn git add sojorn_app/lib/config/firebase_web_config.dart git commit -m "Add FCM VAPID key for web push notifications" git push ``` ### Step 3: Upload Firebase Service Account JSON to Server **From Windows PowerShell:** ```powershell scp -i "C:\Users\Patrick\.ssh\mpls.pem" "C:\path\to\sojorn-a7a78-firebase-adminsdk-xxxxx.json" patrick@194.238.28.122:/tmp/firebase-service-account.json ``` Replace `C:\path\to\...` with the actual path to your downloaded JSON file. ### Step 4: Configure Server **SSH to server:** ```bash ssh -i "C:\Users\Patrick\.ssh\mpls.pem" patrick@194.238.28.122 ``` **Manual setup:** ```bash # Move JSON file sudo mv /tmp/firebase-service-account.json /opt/sojorn/firebase-service-account.json sudo chmod 600 /opt/sojorn/firebase-service-account.json sudo chown patrick:patrick /opt/sojorn/firebase-service-account.json # Edit .env sudo nano /opt/sojorn/.env ``` Add these lines to `.env`: ```bash # Firebase Cloud Messaging FIREBASE_CREDENTIALS_FILE=/opt/sojorn/firebase-service-account.json FIREBASE_WEB_VAPID_KEY=BNxS7_your_actual_vapid_key_here ``` Save and exit (Ctrl+X, Y, Enter) ### Step 5: Restart Go Backend ```bash cd /home/patrick/sojorn-backend sudo systemctl restart sojorn-api sudo systemctl status sojorn-api ``` **Check logs for successful initialization:** ```bash sudo journalctl -u sojorn-api -f --since "1 minute ago" ``` Look for: ``` [INFO] PushService initialized successfully ``` --- ## Android-Specific Setup ### Prerequisites 1. **google-services.json**: Download from Firebase Console for Android app 2. **Package Name**: Must match `com.gosojorn.app` 3. **Build Configuration**: Proper Gradle setup 4. **Permissions**: Runtime notification permissions (Android 13+) ### Android Configuration Files #### 1. google-services.json **Location:** `sojorn_app/android/app/google-services.json` **Verify package name:** ```json { "project_info": { "project_number": "486753572104", "project_id": "sojorn-a7a78" }, "client": [ { "client_info": { "mobilesdk_app_id": "1:486753572104:android:abc123...", "android_client_info": { "package_name": "com.gosojorn.app" } } } ] } ``` #### 2. build.gradle.kts (Project Level) **File:** `sojorn_app/android/build.gradle.kts` ```kotlin dependencies { classpath("com.google.gms:google-services:4.4.0") } ``` #### 3. build.gradle.kts (App Level) **File:** `sojorn_app/android/app/build.gradle.kts` ```kotlin plugins { id("com.android.application") id("kotlin-android") id("com.google.gms.google-services") } android { namespace = "com.gosojorn.app" // ... other config } dependencies { implementation("com.google.firebase:firebase-messaging") // ... other dependencies } ``` #### 4. AndroidManifest.xml **File:** `sojorn_app/android/app/src/main/AndroidManifest.xml` ```xml ``` #### 5. strings.xml (Notification Channel) **File:** `sojorn_app/android/app/src/main/res/values/strings.xml` ```xml chat_messages Chat messages ``` --- ## Testing & Verification ### Test 1: Check Token Registration #### Web 1. Open Sojorn web app in browser 2. Open DevTools (F12) > Console 3. Look for: `FCM token registered (web): d2n2ELGKel7yzPL3wZLGSe...` 4. If you see "Web push is missing FIREBASE_WEB_VAPID_KEY", VAPID key is not set correctly #### Android 1. Run the app: `cd c:\Webs\Sojorn && .\run_dev.ps1` 2. Check logs: `adb logcat | findstr "FCM"` 3. Look for: ``` [FCM] Initializing for platform: android [FCM] Token registered (android): eXaMpLe... [FCM] Token synced with Go Backend successfully ``` ### Test 2: Check Database ```bash sudo -u postgres psql sojorn ``` ```sql -- Check FCM tokens are being stored SELECT user_id, platform, LEFT(fcm_token, 30) as token_preview, created_at FROM public.fcm_tokens ORDER BY created_at DESC LIMIT 5; ``` **Expected output:** ``` user_id | platform | token_preview | created_at -------------------------------------+----------+--------------------------------+------------------- 5568b545-5215-4734-875f-84b3106cd170 | web | d2n2ELGKel7yzPL3wZLGSe:APA91b | 2026-01-29 05:50 5568b545-5215-4734-875f-84b3106cd170 | android | eXaMpLe_android_token_here... | 2026-01-29 06:00 ``` ### Test 3: Send Test Message 1. Open two browser windows (or use two different users) 2. User A sends a chat message to User B 3. User B should receive a push notification (if browser is in background) **Check server logs:** ```bash sudo journalctl -u sojorn-api -f | grep -i push ``` You should see: ``` [INFO] Sending push notification to user 5568b545... [INFO] Push notification sent successfully ``` --- ## Troubleshooting ### Common Issues & Solutions #### Issue: "Web push is missing FIREBASE_WEB_VAPID_KEY" **Cause:** VAPID key not set in Flutter app **Fix:** 1. Update `firebase_web_config.dart` with actual VAPID key 2. Hot restart Flutter app 3. Check console again #### Issue: "Failed to initialize PushService" **Cause:** Firebase service account JSON not found or invalid **Fix:** ```bash # Check file exists ls -la /opt/sojorn/firebase-service-account.json # Check .env has correct path sudo cat /opt/sojorn/.env | grep FIREBASE_CREDENTIALS_FILE # Validate JSON cat /opt/sojorn/firebase-service-account.json | jq . # Check permissions ls -la /opt/sojorn/firebase-service-account.json # Should show: -rw------- 1 patrick patrick ``` #### Issue: Android "Token is null after getToken()" **Cause:** Firebase not properly initialized or `google-services.json` mismatch **Fix:** 1. Verify `google-services.json` package name matches: `"package_name": "com.gosojorn.app"` 2. Check `build.gradle.kts` has: `applicationId = "com.gosojorn.app"` 3. Rebuild: `flutter clean && flutter pub get && flutter run` #### Issue: Android "Permission denied" **Cause:** User denied notification permission or Android 13+ permission not requested **Fix:** 1. Check `AndroidManifest.xml` has: `` 2. On Android 13+, permission must be requested at runtime 3. Uninstall and reinstall app to re-trigger permission prompt #### Issue: Notifications not received **Checklist:** - [ ] Browser notification permissions granted - [ ] FCM token registered (check console) - [ ] Token stored in database (check SQL) - [ ] Go backend logs show push being sent - [ ] Service worker registered (check DevTools > Application > Service Workers) **Check service worker:** 1. Open DevTools > Application > Service Workers 2. Should see `firebase-messaging-sw.js` registered 3. If not, check `sojorn_app/web/firebase-messaging-sw.js` exists --- ## Debug Checklist for Android Run through this checklist: - [ ] `google-services.json` exists in `android/app/` - [ ] Package name matches in all files - [ ] `build.gradle.kts` has `google-services` plugin - [ ] `AndroidManifest.xml` has `POST_NOTIFICATIONS` permission - [ ] App has notification permission granted - [ ] Android logs show FCM initialization - [ ] Android logs show token generated - [ ] Token appears in database `fcm_tokens` table - [ ] Backend logs show notification being sent - [ ] Android logs show notification received --- ## Current Configuration **Firebase Project:** - Project ID: `sojorn-a7a78` - Sender ID: `486753572104` - Console: https://console.firebase.google.com/project/sojorn-a7a78 **Server Paths:** - .env: `/opt/sojorn/.env` - Service Account: `/opt/sojorn/firebase-service-account.json` - Backend: `/home/patrick/sojorn-backend` **Flutter Files:** - Config: `sojorn_app/lib/config/firebase_web_config.dart` - Service Worker: `sojorn_app/web/firebase-messaging-sw.js` - Notification Service: `sojorn_app/lib/services/notification_service.dart` **Android Files:** - Firebase Config: `sojorn_app/android/app/google-services.json` - Build Config: `sojorn_app/android/app/build.gradle.kts` - Manifest: `sojorn_app/android/app/src/main/AndroidManifest.xml` - Strings: `sojorn_app/android/app/src/main/res/values/strings.xml` --- ## Quick Reference Commands ```bash # SSH to server ssh -i "C:\Users\Patrick\.ssh\mpls.pem" patrick@194.238.28.122 # Check .env sudo cat /opt/sojorn/.env | grep FIREBASE # Check service account file ls -la /opt/sojorn/firebase-service-account.json cat /opt/sojorn/firebase-service-account.json | jq .project_id # Restart backend sudo systemctl restart sojorn-api # View logs sudo journalctl -u sojorn-api -f # Check FCM tokens in DB sudo -u postgres psql sojorn -c "SELECT COUNT(*) as token_count FROM public.fcm_tokens;" # View recent tokens sudo -u postgres psql sojorn -c "SELECT user_id, platform, created_at FROM public.fcm_tokens ORDER BY created_at DESC LIMIT 5;" # Android debug commands adb logcat | findstr "FCM" adb shell pm list packages | findstr gosojorn adb uninstall com.gosojorn.app adb shell dumpsys notification | findstr gosojorn ``` --- ## Expected Behavior **When working correctly:** ### Web 1. App starts → User grants notification permission 2. Token generated → `FCM token registered (web): ...` 3. Token synced → Token appears in database 4. Message sent → Backend sends push → Notification appears in browser ### Android 1. App starts → `[FCM] Initializing for platform: android` 2. Permission requested → User grants → `[FCM] Permission status: AuthorizationStatus.authorized` 3. Token generated → `[FCM] Token registered (android): eXaMpLe...` 4. Token synced → `[FCM] Token synced with Go Backend successfully` 5. Message sent → Backend sends push → `[FCM] Foreground message received` 6. Notification appears in Android notification tray --- ## Files Modified During Implementation 1. `sojorn_app/lib/config/firebase_web_config.dart` - Added VAPID key placeholder 2. `go-backend/.env.example` - Updated FCM configuration format 3. `sojorn_app/android/app/google-services.json` - Firebase Android configuration 4. `sojorn_app/android/app/build.gradle.kts` - Gradle configuration 5. `sojorn_app/android/app/src/main/AndroidManifest.xml` - Permissions and metadata 6. `sojorn_app/lib/services/notification_service.dart` - Enhanced logging for debugging --- ## Next Steps After Deployment 1. Monitor logs for FCM errors 2. Test notifications with real users 3. Check FCM token count grows as users log in 4. Verify push notifications work on: - Chrome (desktop & mobile) - Firefox (desktop & mobile) - Safari (if supported) - Edge - Android devices --- ## Support If you encounter issues: 1. Check logs: `sudo journalctl -u sojorn-api -f` 2. Verify configuration: `sudo cat /opt/sojorn/.env | grep FIREBASE` 3. Test JSON validity: `cat /opt/sojorn/firebase-service-account.json | jq .` 4. Check Firebase Console for errors: https://console.firebase.google.com/project/sojorn-a7a78/notification 5. For Android issues, share logcat output: `adb logcat | findstr "FCM"` --- **Last Updated**: January 30, 2026 **Status**: ✅ Web notifications working, Android setup in progress