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!

Glenn Gailey