Send Multicast Notifications using Node.js, HTTP/2 and the FCM HTTP v1 API

Jun 30, 2023

With the deprecation and planned decommission of both the Legacy HTTP FCM API and the Firebase Admin SDK Batch Send API (sendMulticast() method), it has become a challenge to send multicast notifications at scale and with low latency to large numbers of devices simultaneously without relying on the deprecated APIs.

Since the new FCM HTTP v1 API only accepts a single device token per request, we would essentially have to send an entire HTTP request per device token, per notification. At scale, sending to large numbers of devices (1M) would be extremely slow over HTTP 1.0 / 1.1 and require lots of TCP connections.

I was surprised to discover that the official Firebase Admin Node.js SDK had been updated to include a new method sendEachForMulticast(), and developers are urged to migrate to using it instead of the now deprecated sendMulticast(). However, if you were to use this method with any large number of device tokens, your server might crash or grind to a halt, as the Firebase Admin SDK opens a new HTTP 1.0 connection for every single device token simultaneously.

Thankfully, the new FCM HTTP v1 API also supports HTTP/2 connectivity. With HTTP/2, a single connection (referred to as a session) can be kept open and multiple requests (referred to as streams) can be simultaneously sent over this single connection. The FCM HTTP v1 API allows up to 100 simultaneous streams per session, and we are free to open multiple HTTP/2 sessions to improve concurrency and deliver the notification ASAP to as many devices as possible.

Strangely enough, the Firebase Admin Node.js SDK has not been updated to utilize HTTP/2 under the hood for its multicast functionality. So I took matters into my own hands and published a package to npm that does just that.

First, install the package using npm:

npm install fcm-v1-http2 --save  

Then, start using the package by importing and instantiating it:

const fcmV1Http2 = require('fcm-v1-http2');

// Create a new client
const client = new fcmV1Http2({  
  // Pass in your service account JSON private key file (https://console.firebase.google.com/u/0/project/_/settings/serviceaccounts/adminsdk)
  serviceAccount: require('./service-account.json'),
  // Max number of concurrent HTTP/2 sessions (connections)
  maxConcurrentConnections: 10,
  // Max number of concurrent streams (requests) per session
  maxConcurrentStreamsAllowed: 100
});

// Populate array with any number of FCM device tokens
const tokens = ['ccw_syAXSNOY9ml-Kqh9wo:APA91bHAEQccW1ZpbPvsGc0LFyjEthAt_GZO7HkBGiKounM................uIDEHijb4UR5f3dhyjhO5IbiWhJAA7RVp63KSFCg384PR7nfKADReWUONEJlCnHo15WwZagVTmFcgW'];

// Set FCM API v1 message params
// https://firebase.google.com/docs/reference/fcm/rest/v1/projects.messages#Message
const message = {  
    data: {
        // Set custom payload
        message: 'Hello World'
    },
    android: {
        // Burst through Doze mode
        priority: 'high'
    }
};

// Send the notification
client.sendMulticast(message, tokens).then((unregisteredTokens) => {  
    // Sending successful
    console.log('Message sent successfully');

    // Remove unregistered tokens from your database
    if (unregisteredTokens.length > 0) {
        console.log('Unregistered device token(s): ', unregisteredTokens.join(', '));
    }
}).catch((err) => {
    // Sending failed
    // Log error to console
    console.error('Sending failed:', err);
});

Let me know what you think, and if you faced any issues using the package!

Written by Elad Nava
Tagged under
Posted on