Reputation: 5212
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:
How to get a valid refresh token?
Can I use the oAuth2Client methods through a JWT client as it would not be using any frontend interaction (generateAuthUrl)
Will using the request method block my code?
Do I need some kind of message queue?
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
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