sojorn/sojorn_docs/features/fcm-implementation.md

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_id
  • private_key
  • client_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_type column supports platform-specific logic
  • ON DELETE CASCADE ensures 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

  1. Android 13+ Permission Check: Explicitly request POST_NOTIFICATIONS permission
  2. Firebase Permission Request: Request FCM permissions via SDK
  3. Token Retrieval: Get FCM token (with VAPID key for web)
  4. Backend Registration: POST token to /notifications/device
  5. 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:

  1. Calls DELETE /notifications/device with the current token
  2. Deletes the token from Firebase locally
  3. 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

  1. Check Firebase is initialized properly
  2. Verify FIREBASE_CREDENTIALS_FILE path is correct
  3. For web, ensure FIREBASE_WEB_VAPID_KEY is set
  4. Check network connectivity to Go backend

Notifications Not Arriving

  1. Verify token exists in user_fcm_tokens table
  2. Check Firebase Console for delivery reports
  3. Ensure app hasn't restricted background data
  4. 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_NOTIFICATIONS runtime 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