> ## Documentation Index
> Fetch the complete documentation index at: https://docs.traza.com.au/llms.txt
> Use this file to discover all available pages before exploring further.

# Firebase Cloud Messaging (FCM) Push Notifications

> Implementation guide for native push notifications on iOS and Android

# Firebase Cloud Messaging (FCM) Push Notifications

## Overview

Tatu uses Firebase Cloud Messaging (FCM) to send native push notifications to mobile devices (iOS and Android). This system works alongside our existing in-app notification system to provide a complete notification experience.

## Architecture

### Two-Layer System

1. **In-App Notifications** (Firestore-based)
   * Stores notification data and history
   * Powers the in-app notification center UI
   * Polled every 5 minutes when app is open
   * Source of truth for notification state

2. **Push Notifications** (FCM-based)
   * Wakes up devices when app is closed/backgrounded
   * Shows native OS notifications
   * Triggers app to open and fetch full notification data
   * Delivery mechanism only (not source of truth)

### How It Works

```
Event Occurs (e.g., new booking)
         ↓
createNotification() called
         ↓
    ┌────┴────┐
    ↓         ↓
Firestore   FCM Push
Document    Notification
Created     Sent
    ↓         ↓
In-App      Device
Center      Wakes Up
    ↓         ↓
User sees   User taps
in app      notification
            ↓
        App opens to
        booking detail
```

## Implementation

### 1. User Model

FCM tokens are stored in the user document:

```typescript theme={null}
// types/models/users.ts
export interface BaseUserInfo {
  // ... other fields ...
  fcmTokens?: {
    [deviceId: string]: {
      token: string;
      platform: "ios" | "android" | "web";
      deviceId: string;
      createdAt: number;
      lastUsed: number;
    };
  };
}
```

**Why a map?**

* Users can have multiple devices (iPhone, iPad, Android tablet)
* Easy to update individual device tokens
* Simple to remove tokens for specific devices
* Allows per-device tracking and cleanup

### 2. Backend Implementation

#### Creating Notifications

The `createNotification` function automatically sends FCM push notifications:

```typescript theme={null}
// firebase/server/notifications.ts
import { sendPushNotification } from "./fcm";

export async function createNotification<T extends NotificationTypeEnum>(
  { userId, type, data }: { userId: string; type: T; data: NotificationDataMap[T] }
) {
  // 1. Create Firestore notification document
  const notificationId = await createFirestoreNotification(...);

  // 2. Send FCM push notification (non-blocking)
  sendPushNotification(userId, type, data, notificationId).catch(console.error);

  return notificationId;
}
```

**Key Points:**

* FCM sending is non-blocking and won't fail notification creation
* Firestore write happens first (source of truth)
* FCM failures are logged but don't throw errors

#### FCM Utilities

**`sendPushNotification()`** - Send to one user

```typescript theme={null}
await sendPushNotification(
  userId,
  NotificationTypeEnum.NEW_BOOKING_REQUEST,
  { bookingId: "123", clientName: "John Doe" },
  notificationId,
);
```

**`sendPushNotificationToMultipleUsers()`** - Bulk send

```typescript theme={null}
await sendPushNotificationToMultipleUsers(
  userIds,
  NotificationTypeEnum.NEW_FEATURE,
  (userId) => ({ featureName: "Dark Mode" }),
  (userId) => notificationIdMap[userId],
);
```

**`buildNotificationPayload()`** - Convert notification to FCM format

```typescript theme={null}
const payload = buildNotificationPayload(
  NotificationTypeEnum.BOOKING_CONFIRMED,
  { bookingId: "123", artistName: "Jane Smith" },
);
// Returns: { title: "Booking Confirmed", body: "Jane Smith confirmed your booking", ... }
```

### 3. API Endpoints

#### Register FCM Token

```typescript theme={null}
POST /api/notifications/register-fcm-token
Body: {
  token: string,
  deviceId: string,
  platform: "ios" | "android" | "web"
}
```

#### Remove FCM Token

```typescript theme={null}
POST / api / notifications / remove - fcm - token;
Body: {
  deviceId: string;
}
```

#### Update Token Usage

```typescript theme={null}
POST / api / notifications / update - fcm - token - usage;
Body: {
  deviceId: string;
}
```

### 4. Frontend/Mobile Implementation

#### React Hooks

```typescript theme={null}
import { useRegisterFCMToken } from "@/hooks/notifications/useFCMTokenRegistration";

// Manual registration
const { mutate: registerToken } = useRegisterFCMToken();

registerToken({
  token: "fcm_token_from_device",
  deviceId: "unique_device_id",
  platform: "ios",
});
```

#### Auto-Registration Hook

```typescript theme={null}
import { useFCMTokenAutoRegistration } from "@/hooks/notifications/useFCMTokenRegistration";

// In your React Native app
function App() {
  const [fcmToken, setFcmToken] = useState(null);
  const [deviceId, setDeviceId] = useState(null);

  // Auto-registers and manages token lifecycle
  const { isRegistered, isRegistering, error } = useFCMTokenAutoRegistration(
    fcmToken,
    deviceId,
    Platform.OS,
  );

  // ... rest of app
}
```

## Mobile App Integration

### React Native (Expo)

```typescript theme={null}
import * as Notifications from 'expo-notifications';
import * as Device from 'expo-device';
import { Platform } from 'react-native';
import { useRegisterFCMToken } from '@/hooks/notifications/useFCMTokenRegistration';

// 1. Request permissions
async function registerForPushNotifications() {
  if (!Device.isDevice) {
    console.log('Must use physical device for push notifications');
    return null;
  }

  // Check/request permissions
  const { status: existingStatus } = await Notifications.getPermissionsAsync();
  let finalStatus = existingStatus;

  if (existingStatus !== 'granted') {
    const { status } = await Notifications.requestPermissionsAsync();
    finalStatus = status;
  }

  if (finalStatus !== 'granted') {
    console.log('Permission not granted for push notifications');
    return null;
  }

  // Get token
  const token = (await Notifications.getExpoPushTokenAsync()).data;

  // For Android, set notification channel
  if (Platform.OS === 'android') {
    Notifications.setNotificationChannelAsync('default', {
      name: 'default',
      importance: Notifications.AndroidImportance.MAX,
      vibrationPattern: [0, 250, 250, 250],
      lightColor: '#FF231F7C',
    });
  }

  return token;
}

// 2. Use in your app
function App() {
  const { mutate: registerToken } = useRegisterFCMToken();

  useEffect(() => {
    registerForPushNotifications().then(token => {
      if (token) {
        registerToken({
          token,
          deviceId: Device.deviceId,
          platform: Platform.OS,
        });
      }
    });
  }, []);

  // 3. Handle notification taps
  useEffect(() => {
    const subscription = Notifications.addNotificationResponseReceivedListener(response => {
      const data = response.notification.request.content.data;

      // Navigate based on notification data
      if (data.screen === 'booking' && data.bookingId) {
        navigation.navigate('BookingDetail', { id: data.bookingId });
      }
    });

    return () => subscription.remove();
  }, []);

  return <YourApp />;
}
```

### iOS Configuration

1. **Enable Push Notifications Capability**
   * In Xcode, select your target
   * Go to "Signing & Capabilities"
   * Add "Push Notifications" capability

2. **Configure APNs**
   * Create APNs key in Apple Developer Console
   * Upload to Firebase Console (Project Settings → Cloud Messaging → iOS)

### Android Configuration

1. **google-services.json**
   * Download from Firebase Console
   * Place in `android/app/` directory

2. **Notification Channels** (Android 8.0+)
   * Already configured in the code above
   * Can customize per notification type if needed

## Notification Payload Structure

### What's Sent via FCM

```json theme={null}
{
  "notification": {
    "title": "New Booking Request",
    "body": "John Doe sent you a booking request"
  },
  "data": {
    "notificationId": "firestore_notification_id",
    "type": "NEW_BOOKING_REQUEST",
    "bookingId": "123",
    "clientName": "John Doe",
    "screen": "booking"
  },
  "android": {
    "priority": "high",
    "notification": {
      "channelId": "default",
      "sound": "default"
    }
  },
  "apns": {
    "payload": {
      "aps": {
        "sound": "default",
        "badge": 1
      }
    }
  }
}
```

### Notification Types

All notification types from `NotificationTypeEnum` are supported:

* **Booking**: `NEW_BOOKING_REQUEST`, `BOOKING_CONFIRMED`, `BOOKING_CANCELLED`, etc.
* **Session**: `SESSION_CONFIRMED`, `SESSION_COMPLETED`, etc.
* **System**: `NEW_FEATURE`, etc.

Each type has a custom title and body defined in `buildNotificationPayload.ts`.

## Token Management

### Token Lifecycle

1. **Registration** - When user logs in or app starts
2. **Updates** - When token changes (rare but possible)
3. **Removal** - When user logs out or revokes permissions
4. **Cleanup** - Invalid tokens automatically removed on send failure

### Invalid Token Cleanup

When FCM returns errors (expired/invalid tokens), they're automatically removed:

```typescript theme={null}
// In sendPushNotification.ts
if (response.failureCount > 0) {
  const failedTokens = /* ... extract failed tokens ... */;
  await cleanupInvalidTokens(userId, failedTokens);
}
```

### Manual Cleanup

You can also manually remove tokens:

```typescript theme={null}
const { mutate: removeToken } = useRemoveFCMToken();

// On logout
function handleLogout() {
  removeToken(deviceId);
  // ... rest of logout logic
}
```

## Testing

### Local Testing (Development)

1. **Use Firebase Console**
   * Go to Cloud Messaging
   * Use "Send test message"
   * Enter your device token
   * Test different notification types

2. **Use Postman/cURL**

   ```bash theme={null}
   curl -X POST http://localhost:3000/api/notifications/register-fcm-token \
     -H "Content-Type: application/json" \
     -H "Authorization: Bearer YOUR_TOKEN" \
     -d '{
       "token": "device_fcm_token",
       "deviceId": "test_device_123",
       "platform": "ios"
     }'
   ```

3. **Trigger Real Notifications**
   * Create a test booking
   * Notification will automatically trigger
   * Check device for push notification

### Production Testing

* Test on physical devices (simulators have limitations)
* Test with app in foreground, background, and closed
* Test notification tapping and deep linking
* Test with multiple devices per user
* Test token cleanup on logout

## Troubleshooting

### Notifications Not Received

1. **Check token registration**

   ```typescript theme={null}
   // Log token after registration
   console.log("FCM Token registered:", token);
   ```

2. **Verify token in Firestore**
   * Check user document has `fcmTokens` field
   * Verify token value matches device

3. **Check Firebase Console logs**
   * Go to Cloud Functions logs
   * Look for "FCM notification sent" messages
   * Check for errors

4. **Platform-specific issues**
   * **iOS**: Verify APNs certificate is uploaded
   * **Android**: Check google-services.json is correct
   * **Both**: Ensure permissions are granted

### Tokens Keep Getting Removed

* Check if tokens are valid (not expired)
* Verify device has stable internet connection
* Check Firebase project configuration

### Deep Linking Not Working

* Verify notification data includes correct fields
* Check navigation logic in notification handler
* Ensure screens exist in navigation stack

## Best Practices

### Do's ✅

* Always register tokens on app start
* Remove tokens on logout
* Handle notification taps for deep linking
* Test on physical devices
* Log FCM operations for debugging
* Use non-blocking FCM sends (current implementation)

### Don'ts ❌

* Don't block notification creation on FCM failures
* Don't store tokens in frontend state only
* Don't forget to clean up invalid tokens
* Don't send sensitive data in FCM payload (use notification ID instead)
* Don't rely on FCM for notification history (use Firestore)

## Future Enhancements

1. **Badge Count Management**
   * Track unread notification count
   * Update badge number dynamically
   * Sync across devices

2. **Rich Notifications**
   * Include images (tattoo photos)
   * Action buttons (Accept/Decline)
   * Custom notification sounds

3. **Notification Preferences**
   * Per-notification-type toggles
   * Quiet hours settings
   * Platform-specific preferences

4. **Analytics**
   * Track notification delivery rates
   * Measure tap-through rates
   * Monitor token lifecycle

## Security Considerations

* FCM tokens are stored in user documents (backend only)
* Tokens are NOT exposed in public user profiles
* Invalid tokens are automatically cleaned up
* Each device has a unique token
* Tokens can be revoked by removing from user document

## Resources

* [Firebase Cloud Messaging Documentation](https://firebase.google.com/docs/cloud-messaging)
* [Expo Push Notifications Guide](https://docs.expo.dev/push-notifications/overview/)
* [React Native Firebase](https://rnfirebase.io/)
* [APNs Configuration](https://developer.apple.com/documentation/usernotifications)
