Will Kru
Will Kru

Reputation: 5212

Send notifications via FCM HTTP v1 API through node.js

As all npm modules for FCM still seem to use the legacy HTTP API, I am trying to implement FCM messaging through the latest HTTP v1 API on node myself.

While the docs refer to the Google Client API, requiring it seems overkill as I only need the JWT oAuth2Client which is part of the Google Auth Library.

The module I came up with so far:

var request = require('request');
var googleAuth = require('google-auth-library');

module.exports.Sender = function(projectId, key) {

  var _projectId = projectId;
  var _key = key;
  var _tokens = {};

  var _jwtClient = new googleAuth.JWT(
    key.client_email,
    null,
    key.private_key,
    ['https://www.googleapis.com/auth/firebase.messaging'],
    null
  );

  var _getAccessToken = function() {

    return new Promise(function(resolve, reject) {
      _jwtClient.authorize(function(error, tokens) {
        if (error) {
          reject(error);
          return;
        }
        _tokens = tokens;
        resolve(tokens.access_token);
      });
    });
  };

  var _sendMessage = function(message) {

    return new Promise(function(resolve, reject) {
      request({
        method: 'POST',
        url: 'https://fcm.googleapis.com/v1/projects/' + _projectId + '/messages:send',
        headers: {
          'Content-type': 'application/json',
          'Authorization': 'Bearer ' + _tokens.access_token
        },
        body: message,
        json: true
      }, function(error, response, body) {
        if(error) reject(error);
        else {
          resolve(response);
        }
      });
    });
  };

  return {
    getAccessToken: _getAccessToken,
    sendMessage: _sendMessage
  }
}

as for using it:

var fcm = require('./index.js');

var projectId = 'someProjectId';
var key = require('./someServiceAccount.json');

var sender = new fcm.Sender(projectId, key);

var message = {
  validate_only: false,
  message: {
    token: 'someDeviceToken',
    android: {
      collapse_key: 'someCollapseKey',
      priority: 'HIGH',
      restricted_package_name: 'com.some.domain',
      data: {
        title: 'someTitle',
        body: 'someBody',
        sound: 'default',
        action: 'someAction'
      }
    },
    apns: {
      headers: {
        'apns-priority': '10'
      },
      payload: {
        aps: {
          alert: {
            title: 'someTitle',
            body: 'someBody'
          },
          sound: 'default',
          badge: 2
        },
        action: 'someAction'
      }
    }
  }
};


sender.getAccessToken() //TODO: should be done once in sender
.then(function(accessToken) {
  return sender.sendMessage(message);
})
.then(function(response) {
  console.log(response.body);
})
.catch(function(error) {
  console.log(error);
});

All fine and dandy; I am receiving the notification on my device using cordova-plugin-firebase.

The API calls are authenticated using oAuth2, where the access token periodically (seems like 1 hour) needs to be refreshed. The docs state:

Note that the call to refresh the token is idempotent. After your token expires, the token refresh method is called automatically to retrieve an updated token.

I can't seem to figure out however how this is implemented. The JWT authorize call leaves me only with a generic object holding the access token as a string. As the JWT client extends oAuth2Client, I browsed through its source code and it does seem to have a request method that automatically refreshes the access token.

Also the refresh_token of the object returned by JWT.authorize always seems to be 'jwt-placeholder'.

Leaving me with questions:

While I keep on researching and coding, I was wondering if someone could give me some pointers or helpful docs in the meantime.

Upvotes: 5

Views: 3901

Answers (1)

Will Kru
Will Kru

Reputation: 5212

I actually got it to work using the request method. This also eliminates the need for the request module. If I understand correctly, the oAuth2Client should refresh the access token automatically, as well as doing a single retry when a request fails.

My updated Sender module:

var googleAuth = require('google-auth-library');

module.exports.Sender = function(projectId, key) {

  var _projectId = projectId;
  var _jwtClient = new googleAuth.JWT(
    key.client_email,
    null,
    key.private_key,
    ['https://www.googleapis.com/auth/firebase.messaging'],
    null
  );

  var _init = function() {

    return new Promise(function(resolve, reject) {
      _jwtClient.authorize(function(error, tokens) {
        if (error) {
          reject(error);
          return;
        }
        resolve();
      });
    });
  };

  var _sendMessage = function(message) {

    return _jwtClient.request({
      method: 'post',
      url: 'https://fcm.googleapis.com/v1/projects/' + _projectId + '/messages:send',
      data: message
    });
  }

  return {
    init: _init,
    sendMessage: _sendMessage
  }
}

and usage:

sender.init()
.then(function(accessToken) {
  return sender.sendMessage(message);
})
.then(function(response) {
  console.log(response.data);
})
.catch(function(error) {
  console.log(error);
});

Still, the question about the refresh_token remains, as the JWT client overrides it. How is this to be implemented correctly?

Upvotes: 4

Related Questions