524 lines
15 KiB
Markdown
524 lines
15 KiB
Markdown
# 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
|
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
|
<uses-permission android:name="android.permission.INTERNET" />
|
|
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
|
<uses-permission android:name="android.permission.VIBRATE" />
|
|
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
|
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
|
|
|
<application>
|
|
<meta-data
|
|
android:name="com.google.firebase.messaging.default_notification_icon"
|
|
android:resource="@drawable/ic_notification" />
|
|
<meta-data
|
|
android:name="com.google.firebase.messaging.default_notification_color"
|
|
android:resource="@color/colorAccent" />
|
|
<meta-data
|
|
android:name="com.google.firebase.messaging.default_notification_channel_id"
|
|
android:value="@string/default_notification_channel_id" />
|
|
</application>
|
|
</manifest>
|
|
```
|
|
|
|
#### 5. strings.xml (Notification Channel)
|
|
**File:** `sojorn_app/android/app/src/main/res/values/strings.xml`
|
|
```xml
|
|
<resources>
|
|
<string name="default_notification_channel_id">chat_messages</string>
|
|
<string name="default_notification_channel_name">Chat messages</string>
|
|
</resources>
|
|
```
|
|
|
|
---
|
|
|
|
## 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: `<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />`
|
|
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
|