Skip to main content

FCM - Device Token Management

๐Ÿ“– Overviewโ€‹

FCM device token management handles registration and storage of Firebase Cloud Messaging tokens for web browsers and mobile devices. Each user can have multiple active tokens (supporting concurrent sessions across devices), and tokens are stored in MongoDB for notification delivery.

Source Files:

  • Controller: external/Integrations/FCM/Controller/fcm.js (saveWebToken)
  • Model: external/Integrations/FCM/Model/fcm.js
  • Routes: external/Integrations/FCM/Routes/fcm.js

External API: Firebase Admin SDK (server-side, no direct Firebase API calls for token registration)

๐Ÿ—„๏ธ Collections Usedโ€‹

fcm.tokensโ€‹

  • Operations: Create, Read, Update
  • Model: shared/models/fcm-token.js
  • Usage Context: Store and retrieve FCM tokens for notification delivery

Document Structure:

{
"_id": ObjectId("507f1f77bcf86cd799439011"),
"account_id": ObjectId("507f1f77bcf86cd799439012"),
"user_id": ObjectId("507f1f77bcf86cd799439013"),
"web_token": [
"fcm_token_abc123...",
"fcm_token_xyz789..." // Supports multiple concurrent sessions
],
"createdAt": ISODate("2023-10-01T12:00:00Z"),
"updatedAt": ISODate("2023-10-05T14:30:00Z")
}

๐Ÿ”„ Data Flowโ€‹

Token Registration Flowโ€‹

sequenceDiagram
participant Browser as Web Browser
participant FCMClient as FCM SDK (Client)
participant API as FCM Controller
participant Model as FCM Model
participant DB as MongoDB (fcm.tokens)

Browser->>FCMClient: Request notification permission
FCMClient->>FCMClient: Generate FCM token
FCMClient-->>Browser: Return token string

Browser->>API: POST /v1/e/fcm/web {token}
API->>API: Extract user_id from JWT
API->>Model: findFcm(user_id, account_id)
Model->>DB: findOne query

alt Token document exists
DB-->>Model: Return existing document
Model-->>API: Return document with _id
API->>Model: updateToken(_id, token, type)
Model->>DB: $addToSet token to web_token array
else No document exists
DB-->>Model: Return null
Model-->>API: Return null
API->>Model: saveToken(user_id, account_id, type, token)
Model->>DB: Create new document with token array
end

DB-->>API: Success
API-->>Browser: {success: true, message: "SUCCESS"}

๐Ÿ”ง Business Logic & Functionsโ€‹


Controller Functionsโ€‹

saveWebToken(req, res, next)โ€‹

Purpose: Register FCM device token for authenticated user

Source: Controller/fcm.js

External API Endpoint: N/A (database operation only)

Parameters:

  • req.body.token (String) - FCM device token from client SDK
  • req.body.type (String, optional) - Token type (default: web_token)
  • req.auth.account_id (ObjectId) - Account ID from JWT
  • req.auth.uid (ObjectId) - User ID from JWT

Returns: JSON response with success status

{
"success": true,
"message": "SUCCESS"
}

Business Logic Flow:

  1. Extract Authentication Context

    • Get account_id and uid from JWT token
    • Convert to strings for MongoDB query
  2. Validate Token Parameter

    • Check if token provided in request body
    • Return 400 error if missing
  3. Check Existing Tokens

    • Query database for existing token document
    • Search by user_id and account_id
  4. Update or Create Token Document

    • If exists: Add token to existing web_token array
    • If not exists: Create new document with token
  5. Return Success Response

    • Confirm token registration

Request Example:

POST /v1/e/fcm/web
Authorization: Bearer {jwt_token}
Content-Type: application/json

{
"token": "fcm_token_abc123def456...",
"type": "web_token"
}

Success Response:

{
"success": true,
"message": "SUCCESS"
}

Error Response (Missing Token):

{
"success": false,
"errno": 400,
"message": "Token not provided. (required)"
}

Error Handling:

  • 400 Bad Request: Token not provided in request body
  • Database Errors: Passed to error middleware via next(error)
  • Duplicate Tokens: Handled by $addToSet (no duplicates added)

Example Usage:

// Client-side (browser)
import { getMessaging, getToken } from 'firebase/messaging';

const messaging = getMessaging();
const token = await getToken(messaging, { vapidKey: 'YOUR_VAPID_KEY' });

// Register token with backend
await fetch('/v1/e/fcm/web', {
method: 'POST',
headers: {
Authorization: `Bearer ${jwtToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ token }),
});

Side Effects:

  • โš ๏ธ Database Write: Creates or updates token document
  • โš ๏ธ Array Addition: Token added to web_token array (no duplicates)
  • โ„น๏ธ No Firebase API Call: Server-side registration only

Model Functionsโ€‹

findFcm(userID, accountID)โ€‹

Purpose: Find existing FCM token document for user

Source: Model/fcm.js

Parameters:

  • userID (String) - User ID to search for
  • accountID (String) - Account ID to search for

Returns: Promise<Object|null> - Token document or null

{
"_id": ObjectId,
"account_id": ObjectId,
"user_id": ObjectId,
"web_token": ["token1", "token2"],
"createdAt": ISODate,
"updatedAt": ISODate
}

Business Logic Flow:

  1. Query MongoDB

    • Find document matching account_id and user_id
    • Use .lean() for plain JavaScript object
    • Execute query
  2. Return Result

    • Return document if found
    • Return null if not found

Example Usage:

const existingTokens = await fcmTokenModal.findFcm('user_123', 'account_456');
if (existingTokens) {
// Update existing document
} else {
// Create new document
}

Side Effects:

  • โ„น๏ธ Database Read: Queries fcm.tokens collection

saveToken(userID, accountID, type, token)โ€‹

Purpose: Create new FCM token document

Source: Model/fcm.js

Parameters:

  • userID (String) - User ID
  • accountID (String) - Account ID
  • type (String) - Token type field name (default: web_token)
  • token (String) - FCM token string

Returns: Promise<Boolean> - true on success

Business Logic Flow:

  1. Build Document Data

    • Create object with user_id, account_id
    • Add token to array using dynamic field name (type)
  2. Create MongoDB Document

    • Instantiate new FcmToken model
    • Save to database
  3. Return Success

    • Resolve with true

Document Structure Created:

{
"user_id": "user_123",
"account_id": "account_456",
"web_token": ["fcm_token_abc123..."]
}

Example Usage:

await fcmTokenModal.saveToken('user_123', 'account_456', 'web_token', 'fcm_token_abc123...');
// New document created in fcm.tokens

Side Effects:

  • โš ๏ธ Database Write: Creates new document in fcm.tokens

updateToken(ID, token, type)โ€‹

Purpose: Add token to existing document's token array

Source: Model/fcm.js

Parameters:

  • ID (String) - MongoDB document _id
  • token (String) - FCM token to add
  • type (String) - Token type field name (default: web_token)

Returns: Promise<Boolean> - true on success

Business Logic Flow:

  1. Update Document

    • Use $addToSet to add token to array
    • Prevents duplicate tokens
    • Update by _id
  2. Return Success

    • Resolve with true

MongoDB Operation:

// Updates web_token array
db.fcm.tokens.updateOne({ _id: ObjectId('...') }, { $addToSet: { web_token: ['fcm_token_new'] } });

Example Usage:

await fcmTokenModal.updateToken('507f1f77bcf86cd799439011', 'fcm_token_new', 'web_token');
// Token added to existing web_token array (if not already present)

Side Effects:

  • โš ๏ธ Database Update: Modifies existing document
  • โ„น๏ธ Duplicate Prevention: $addToSet ensures no duplicate tokens

findAll(userIDs, accountID)โ€‹

Purpose: Find all FCM tokens for multiple users (used for notification sending)

Source: Model/fcm.js

Parameters:

  • userIDs (Array<String>) - Array of user IDs
  • accountID (String) - Account ID to filter by

Returns: Promise<Array<Object>> - Array of token documents

[
{
_id: ObjectId,
account_id: ObjectId,
user_id: ObjectId,
web_token: ['token1', 'token2'],
},
// ... more documents
];

Business Logic Flow:

  1. Query Multiple Users

    • Find documents where user_id in userIDs array
    • Filter by account_id
    • Use .lean() for plain objects
  2. Return All Matching Documents

    • Return array of token documents

Example Usage:

const userTokens = await fcmTokenModal.findAll(['user_123', 'user_456', 'user_789'], 'account_abc');

// Extract all tokens for notification
let allTokens = [];
for (const doc of userTokens) {
allTokens.push(...doc.web_token);
}
// allTokens: ['token1', 'token2', 'token3', ...]

Side Effects:

  • โ„น๏ธ Database Read: Queries fcm.tokens collection

๐Ÿ”€ Integration Pointsโ€‹

Internal Servicesโ€‹

Token Registration Trigger Points:

  1. User Login: Client should register token after successful login
  2. Session Start: Register token when user opens application
  3. Token Refresh: Re-register when FCM SDK generates new token
  4. Device Switch: Register new token when user switches devices

Used By:

  • Web application (browser FCM SDK)
  • Mobile applications (iOS/Android FCM SDK)
  • Desktop applications (Electron with FCM)

Client-Side Integration Patternโ€‹

Web Browser Example:

// 1. Initialize Firebase in client
import { initializeApp } from 'firebase/app';
import { getMessaging, getToken } from 'firebase/messaging';

const firebaseConfig = {
/* your config */
};
const app = initializeApp(firebaseConfig);
const messaging = getMessaging(app);

// 2. Request permission and get token
Notification.requestPermission().then(async permission => {
if (permission === 'granted') {
const token = await getToken(messaging, {
vapidKey: 'YOUR_VAPID_KEY',
});

// 3. Register token with DashClicks backend
await fetch('/v1/e/fcm/web', {
method: 'POST',
headers: {
Authorization: `Bearer ${jwtToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ token }),
});
}
});

External API Dependenciesโ€‹

Provider: Google Firebase
Client SDK: Firebase JavaScript SDK (web) or Firebase Admin SDK (server)
Token Generation: Client-side only (browser or mobile app)
Token Validation: Firebase automatically validates tokens during message delivery


๐Ÿงช Edge Cases & Special Handlingโ€‹

Multiple Device Sessionsโ€‹

Issue: User may have multiple browser tabs or devices open simultaneously

Handling:

  • All tokens stored in web_token array
  • $addToSet prevents duplicates
  • Notifications sent to all registered tokens

Token Array Example:

{
"web_token": [
"token_from_chrome_laptop",
"token_from_firefox_laptop",
"token_from_chrome_mobile"
]
}

Token Regenerationโ€‹

Issue: FCM SDK may generate new token when:

  • Service worker updated
  • Token manually deleted
  • App re-installed

Handling:

  • Client should call token registration endpoint again
  • New token added to array
  • Old tokens remain until manually cleaned up

Expired or Invalid Tokensโ€‹

Issue: Tokens become invalid when:

  • User clears browser data
  • App uninstalled
  • Token manually revoked

Handling:

  • No automatic cleanup mechanism
  • Failed deliveries tracked but tokens not removed
  • Manual cleanup required (not implemented)

Future Enhancement Needed:

// Example cleanup logic (not implemented)
if (fcmResponse.failureCount > 0) {
// Remove invalid tokens based on error codes
fcmResponse.responses.forEach((resp, idx) => {
if (resp.error?.code === 'messaging/invalid-registration-token') {
// Remove tokens[idx] from database
}
});
}

Token Type Extensibilityโ€‹

Issue: System designed for multiple token types (web, mobile, etc.)

Current Implementation:

  • Only web_token field used
  • type parameter supports different field names
  • Schema allows adding mobile_token, ios_token, etc.

Schema Flexibility:

// Current schema (strict: false allows dynamic fields)
{
"web_token": ["token1"],
// Could add in future:
// "mobile_token": ["token2"],
// "ios_token": ["token3"]
}

Concurrent Token Registrationโ€‹

Issue: Same user might register tokens from multiple devices simultaneously

Handling:

  • MongoDB $addToSet atomic operation
  • No race conditions
  • All tokens preserved

โš ๏ธ Important Notesโ€‹

  • ๐Ÿ“ฑ Client SDK Required: Tokens must be generated by Firebase client SDK
  • ๐Ÿ”„ Duplicate Prevention: $addToSet ensures no duplicate tokens in array
  • ๐Ÿ‘ฅ Multi-Device Support: Single user can have many tokens simultaneously
  • ๐Ÿงน No Auto-Cleanup: Invalid tokens remain in database indefinitely
  • ๐Ÿ” Account Scoped: Tokens tied to account_id and user_id pair
  • ๐Ÿ“ Token Type: Currently only web_token used, system extensible for mobile
  • โšก Immediate Availability: Registered tokens available for notifications immediately
  • ๐Ÿ”’ Authentication Required: All endpoints require valid JWT token

๐Ÿ’ฌ

Documentation Assistant

Ask me anything about the docs

Hi! I'm your documentation assistant. Ask me anything about the docs!

I can help you with:
- Code examples
- Configuration details
- Troubleshooting
- Best practices

Try asking: How do I configure the API?
09:30 AM