6.8 KiB
6.8 KiB
FCM (Firebase Cloud Messaging) Implementation Guide
Overview
This document describes the complete FCM push notification implementation for Sojorn, covering both the Go backend and Flutter client. The system supports Android, iOS, and Web platforms.
Architecture
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Flutter App │────▶│ Go Backend │────▶│ Firebase FCM │
│ (Android/Web) │ │ Push Service │ │ Cloud Messaging │
└─────────────────┘ └─────────────────┘ └─────────────────┘
│ │ │
│ POST /notifications/device │
│ (Register FCM Token) │
│ │ │
│ │ SendPush() │
│ │──────────────────────▶│
│ │ │
│◀──────────────────────│◀──────────────────────│
│ Push Notification │ │
Backend Configuration
Required Environment Variables
# Firebase Cloud Messaging (FCM)
FIREBASE_CREDENTIALS_FILE=/opt/sojorn/firebase-service-account.json
FIREBASE_WEB_VAPID_KEY=BNxS7_your_actual_vapid_key_here
Firebase Service Account JSON
Download from Firebase Console > Project Settings > Service Accounts > Generate New Private Key
The JSON file should contain:
project_idprivate_keyclient_email
Database Schema
user_fcm_tokens Table
CREATE TABLE IF NOT EXISTS user_fcm_tokens (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id UUID NOT NULL REFERENCES profiles(id) ON DELETE CASCADE,
token TEXT NOT NULL,
device_type TEXT, -- 'web', 'android', 'ios', 'desktop'
last_updated TIMESTAMPTZ DEFAULT NOW(),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE(user_id, token) -- Prevents duplicate tokens per device
);
Key Features:
- Composite unique constraint on
(user_id, token)prevents duplicate registrations device_typecolumn supports platform-specific logicON DELETE CASCADEensures tokens are cleaned up when user is deleted
Push Notification Triggers
| Event | Handler | Recipient | Data Payload |
|---|---|---|---|
| New Follower | user_handler.go:Follow |
Followed user | type, follower_id |
| Follow Request | user_handler.go:Follow |
Target user | type, follower_id |
| Follow Accepted | user_handler.go:AcceptFollowRequest |
Requester | type, follower_id |
| New Chat Message | chat_handler.go:SendMessage |
Receiver | type, conversation_id, encrypted |
| Comment on Post | post_handler.go:CreateComment |
Post author | type, post_id, post_type, target |
| Post Saved | post_handler.go:SavePost |
Post author | type, post_id, post_type, target |
| Beacon Vouched | post_handler.go:VouchBeacon |
Beacon author | type, beacon_id, target |
| Beacon Reported | post_handler.go:ReportBeacon |
Beacon author | type, beacon_id, target |
Data Payload Structure
All push notifications include a data payload for deep linking:
{
"type": "comment|save|reply|chat|new_follower|follow_request|beacon_vouch",
"post_id": "uuid", // For post-related notifications
"conversation_id": "uuid", // For chat notifications
"follower_id": "uuid", // For follow notifications
"beacon_id": "uuid", // For beacon notifications
"target": "main_feed|quip_feed|beacon_map|secure_chat|profile|thread_view"
}
Flutter Client Implementation
Initialization Flow
- Android 13+ Permission Check: Explicitly request
POST_NOTIFICATIONSpermission - Firebase Permission Request: Request FCM permissions via SDK
- Token Retrieval: Get FCM token (with VAPID key for web)
- Backend Registration: POST token to
/notifications/device - Set up Listeners: Handle token refresh and message callbacks
Deep Linking (Message Open Handling)
The Flutter client handles these notification types:
| Type | Navigation Target |
|---|---|
chat, new_message |
SecureChatScreen with conversation |
save, comment, reply |
Based on target field (home/quips/beacon) |
new_follower, follow_request |
Profile screen of follower |
beacon_vouch, beacon_report |
Beacon map |
Logout Flow
On logout, the client:
- Calls
DELETE /notifications/devicewith the current token - Deletes the token from Firebase locally
- Resets initialization state
API Endpoints
Register Device Token
POST /api/v1/notifications/device
Authorization: Bearer <JWT>
Content-Type: application/json
{
"fcm_token": "device_token_here",
"platform": "android|ios|web|desktop"
}
Unregister Device Token
DELETE /api/v1/notifications/device
Authorization: Bearer <JWT>
Content-Type: application/json
{
"fcm_token": "device_token_here"
}
Troubleshooting
Token Not Registering
- Check Firebase is initialized properly
- Verify
FIREBASE_CREDENTIALS_FILEpath is correct - For web, ensure
FIREBASE_WEB_VAPID_KEYis set - Check network connectivity to Go backend
Notifications Not Arriving
- Verify token exists in
user_fcm_tokenstable - Check Firebase Console for delivery reports
- Ensure app hasn't restricted background data
- On Android 13+, verify POST_NOTIFICATIONS permission
Invalid Token Cleanup
The PushService automatically removes invalid tokens when FCM returns messaging.IsRegistrationTokenNotRegistered error.
Testing
Manual Token Verification
SELECT user_id, token, device_type, last_updated
FROM user_fcm_tokens
WHERE user_id = 'your-user-uuid';
Send Test Notification
Use Firebase Console > Cloud Messaging > Send test message with the device token.
Platform-Specific Notes
Android
- Target SDK 33+ requires
POST_NOTIFICATIONSruntime permission - Add to
AndroidManifest.xml:<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
Web
- Requires valid VAPID key from Firebase Console
- Service worker must be properly configured
- HTTPS required (except localhost)
iOS
- Requires APNs configuration in Firebase
- Provisioning profile must include push notification capability