Adding push notification tags from the client--Node.js backend version
In my previous post, I showed how you can enable an Azure Mobile Apps client to add tags to a push notification registration using the installation ID available on the client. While fairly popular, my original post was only for a .NET backend mobile app, and I got lots of requests to show an equivalent was to do the same in a Node.js backend.
NOTE: I’m also keeping an eye on an issue that the team is working on to whitelist tags so that clients can supply a set of pre-defined safe tags during registration. When that finally gets done, I will blog about it as it should be a bit easier than this, and not require an additional round-trip to the backend to add tags.
I finally had a chance to create a similar custom API in a Node.js backend Mobile App, with the goal of being able to have an identical API call to the .NET backend, where the POST request looks like this:
POST https://todolist.azurewebsites.net/api/updatetags/f211a45f-6f6d-4189-b106-e534af999fff HTTP/1.1
Host: todolist.azurewebsites.net
Content-Type: application/json
ZUMO-API-VERSION: 2.0.0
Content-Length: 20
["test","broadcast"]
In the express.js app, the following POST method implementation defines a custom router that handles this request and adds tags to the given installation (remember that an installation is a push registration tagged with the client’s installation ID):
// Define a POST operation that updates installation tags.
router.post('/:id', function (request, response) {
// Get the notification hub used by the mobile app.
var push = request.azureMobile.push;
var installationId = request.params.id;
// Get the tags array from the request message.
var tags = request.body;
// Validate for and block any SID tags.
for (i = 0; i < tags.length; i++) {
if (tags[i].search("sid:") !== -1) {
response.status(403)
.send("You cannot set '" + tags[i] + "' as a tag.");
return;
}
}
// Define an update tags operation.
var updateOperation = [{
"op": "add",
"path": "/tags",
"value": tags.toString()
}];
// Update the installation to add the new tags.
push.patchInstallation(installationId, updateOperation, function(error, res){
if(error){
logger.error('An error occurred when adding tags', error);
response.status(error.statusCode).send(error.detail);
}
else{
response.status(200).send(tags);
}
});
});
Note that this code validates the client-supplied tags to make sure a user ID isn’t being supplied, to prevent a user from getting push data for another user.
Just for completeness, here’s also the GET method that returns tags for a given registration:
// Define GET operation that returns tags for an installation.
router.get('/:id', function (request, response) {
// Get the notification hub used by the mobile app.
var push = request.azureMobile.push;
var installationId = request.params.id;
push.getInstallation(installationId, function(error, installation, res){
if (error){
// Log the error and send an error response.
logger.error('An error occurred when retrieving installation', error);
response.status(error.statusCode).send(error.detail);
}
else{
// Return an array of current tags.
response.json(installation.tags);
}
});
});
In particular, I wanted to keep the parameterized routes that I had in the .NET backend mobile app. Because of this, I wasn’t able to use the nice convenient JSON-based pattern for defining custom APIs in the /api subfolder. Instead, I ended-up having to use an express.js Router object to define my parameterized routes. Because I did this in a separate file in the ./api subfolder, I needed to expose my router as a module, which I consumed in the main app. Here’s how my router module is defined, without all the function code from above:
var express = require('express'); // Create an express.js router for our custom API. var router = express.Router(); // Define a POST operation that updates installation tags. router.post('/:id', function (request, response) { … }); // Define GET operation that returns tags for an installation. router.get('/:id', function (request, response) { …
}); module.exports = router;
Then, in the app itself, I added this app.use()
reference to hook-up to the ./api/udatetags
route:
app.use(mobileApp);
// Add the router module on the custom API endpoint.
app.use('/api/updatetags', require('./api/UpdateTags.js'));
app.listen(process.env.PORT || 3000); // Listen for requests.
That’s all there is. I am planning on including this custom API and routing in the Node.js version of the quickstart completed project, which I hope to publish this week. Note that I’m not a Node.js guru, so if you have suggested improvements to my code, please let me know in the comments.
Cheers!