Thursday, 28 November 2013

Creating Azure Notifications Hub Push Notifications using Node Script

Windows Azure Mobile Services has great built-in support for making Push Notifications; however there’s a better more scalable option using the Notifications Hub which is part of the Service Bus group of services.
We can call the hub from external backend services using the Windows Azure Service Bus SDK (NuGet PM> Install-Package WindowsAzure.ServiceBus) and this is fairly well documented; however if you want to do a direct replacement for the build-in Push Notifications using Notifications Hub, it’s not so well documented. I did some digging in the source to work out how to use it: https://github.com/WindowsAzure/azure-sdk-for-node
In our Mobile Services scripts we can make use of NPM packages which we install ourselves and pre-installed packages like the Windows Azure SDK for Node which we will use for calling the Hub.

Accessing the Notifications Hub

We need two things to allow us to connect to the hub and those are the DefaultFullSharedAccessSignature which you can get from the Notifications Hub Dashboard and the Hub name. We can declare these as variables like this:

var CONNECTION_STRING = "Endpoint=sb://myapp.servicebus.windows.net/;SharedAccessKeyName=DefaultFullSharedAccessSignature;SharedAccessKey=XXXXXXXXXXXXXXXXXXXXXxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=";
var HUB_NAME = "MyHubName";

Now to interact with the Notifications hub, we get a reference to the Azure package like this:

var azure = require("azure");

Then create a NotificationHubService like this:

var notificationHubService = azure.createNotificationHubService(HUB_NAME, CONNECTION_STRING);

All the native PNS send methods have prototypes similar to this:

MpnsService.prototype.send = function (tags, payload, targetName, notificationClass, optionsOrCallback, callback)

The Notifications class is the batching interval (how quick it’s sent).

Windows Phone MPNS Notofications

Following are examples of sending toast and tiles to Windows Phone apps using the MPNS provider:   

function sendToastHubMpns(text1, text2, tagExpression)
{
    var azure = require("azure");
    var notificationHubService = azure.createNotificationHubService(HUB_NAME, CONNECTION_STRING);
   
    var toast = "<?xml version=\"1.0\" encoding=\"utf-8\"?>" +
    "<wp:Notification xmlns:wp=\"WPNotification\">" +
        "<wp:Toast>" +
            "<wp:Text1>" + text1 + "</wp:Text1>" +
            "<wp:Text2>" + text2 + "</wp:Text2>" +
        "</wp:Toast> " +
    "</wp:Notification>";
   
    notificationHubService.mpns.send(tagExpression, toast, "toast", 2, function(error) {
    if (error) {
        console.error(error);
    }});
}

function sendTileHubMpns(backTitle, backContent, tagExpression)
{
  var azure = require("azure");
  var notificationHubService = azure.createNotificationHubService(HUB_NAME, CONNECTION_STRING);
   
  var tile = "<?xml version=\"1.0\" encoding=\"utf-8\"?>" +
      "<wp:Notification xmlns:wp=\"WPNotification\" Version=\"2.0\">" +
         "<wp:Tile Template=\"FlipTile\">" +
              "<wp:BackTitle>" + backTitle + "</wp:BackTitle>" +
              "<wp:BackContent>" + backContent + "</wp:BackContent>" +
              "<wp:WideBackContent>" + backContent + "</wp:WideBackContent>" +
         "</wp:Tile> " +
      "</wp:Notification>";
 
    notificationHubService.mpns.send(tagExpression, tile, "token", 1, function(error) {
    if (error) {
        console.error(error);
    }});
}

Windows 8 WNS Notifications

Following are examples of sending toast, tile and badge notifications to Windows 8 apps using the WNS provider:   

function sendToastHubWns(text1, text2, text3, tagExpression)
{
  var azure = require("azure");
  var notificationHubService = azure.createNotificationHubService(HUB_NAME, CONNECTION_STRING);
 
  var toast = "<toast>" +
    "<visual>" +
      "<binding template=\"ToastText04\">" +
        "<text id=\"1\">" + text1 + "</text>" +
        "<text id=\"2\">" + text2 + "</text>" +
        "<text id=\"3\">" + text3 + "</text>" +
        "</binding>" +
      "</visual>" +
    "</toast>";

    notificationHubService.wns.send(tagExpression, toast, "wns/toast", 2, function(error) {
    if (error) {
        console.error(error);
    }});
}

function sendTileHubWns(text1, text2, text3, tagExpression)
{
    var azure = require("azure");
    var notificationHubService = azure.createNotificationHubService(HUB_NAME, CONNECTION_STRING);
   
    var tile = "<tile>" +
    "<visual>" +
      "<binding template=\"TileSquareText01\">" +
        "<text id=\"1\">" + text1 + "</text>" +
        "<text id=\"2\">" + text2 + "</text>" +
        "<text id=\"3\">" + text3 + "</text>" +
      "</binding>" +
    "</visual>" +
    "</tile>";
 
    notificationHubService.wns.send(tagExpression, tile, "wns/tile", 2, function(error) {
    if (error) {
        console.error(error);
    }});
}

function sendBadgeHubWns(value, tagExpression)
{
    var azure = require("azure");
    var notificationHubService = azure.createNotificationHubService(HUB_NAME, CONNECTION_STRING);
   
    var badge = "<badge value=\"" + value + "\" />";
 
    notificationHubService.wns.send(tagExpression, badge, "wns/badge", 2, function(error) {
    if (error) {
        console.error(error);
    }});
}

These functions can be called like this:

function sendAllHubNotifications(message)
{
  sendToastHubMpns("My Application", message, null);
  sendTileHubMpns("My Application ", message, null);

  sendToastHubWns("My Application ", "Here’s a notification", message, null);
  sendTileHubWns("My Application ", " Here’s a notification ", message, null;
  sendBadgeHubWns("alert",  null);
}

Notice there is a tagExpression variable which we have set to null. We can use this to target notifications at user’s interests which are registered in the app:

function sendAllHubNotifications(message)
{
  sendToastHubWns("My Application ", "Here’s a notification", message, "NEWS");
  sendTileHubWns("My Application ", " Here’s a notification ", message, "NEWS");
  sendBadgeHubWns("alert", "NEWS");
}

I managed to get Windows 8 to work with this but not Windows Phone 8

Registering with the Hub from a Windows 8 App

This example shows how to register a Windows 8 Store App with the DefaultListenSharedAccessSignature connection string this time (from the Notifocations Hub Dashboard). This registers two tags, but you can use null to receive everything:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.WindowsAzure.MobileServices;
using Newtonsoft.Json.Linq;
using Microsoft.WindowsAzure.Messaging;

namespace MyApp
{
    internal class MyAppPush
    {
        private const string HUB_NAME = "myapp";
        private const string CONNECTION_STRING = "Endpoint=sb://myapp.servicebus.windows.net/;SharedAccessKeyName=DefaultListenSharedAccessSignature;SharedAccessKey=/XXXXXXXXXXXXXXXXXXXXXXXXxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=";

        public async static void UploadChannel()
        {
            var channel = await Windows.Networking.PushNotifications.PushNotificationChannelManager.CreatePushNotificationChannelForApplicationAsync();

            var token = Windows.System.Profile.HardwareIdentification.GetPackageSpecificToken(null);
            string installationId = Windows.Security.Cryptography.CryptographicBuffer.EncodeToBase64String(token.Id);

            try
            {
                // Register with hub
                var hub = new NotificationHub(HUB_NAME, CONNECTION_STRING);
                var result = await hub.RegisterNativeAsync(channel.Uri, new string[] { "NEWS", "SUPPORT" });
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.WriteLine(ex.ToString());
            }
        }
    }
}


Windows Phone 8 apps are registered in the same way, but they have a slightly different mechanism as they have an event when the URI changes which needs to be used for registration.

13 comments:

  1. Hi! Thank you for article.
    Toasts notifications works correctly. If you use null instead 'toast' or 'token' your device will get notification as raw.
    But the tile notofication doesn't work :/ In Azure Log there's no errors.

    ReplyDelete
    Replies
    1. Maybe the sdk has changed, have a look at the functions here https://github.com/WindowsAzure/azure-sdk-for-node

      Delete
    2. I havent seen any changes. Have you some working client code for Windows Phone?

      Delete
    3. This used to work, with tags null (couldn't get tags to work on mpns):
      // Register with hub
      // var tags = TagHelper.Default.GetTags();
      var hub = new NotificationHub(this.HUB_NAME, this.CONNECTION_STRING);
      var result = await hub.RegisterNativeAsync(this._pushChannel.ChannelUri.AbsoluteUri, null);

      Delete
    4. Tagging seems to work OK with a .Net backend now so it should work with Node.js backend too as the hub is unrelated from the client-side, however there may be a difference between how the JS lib interacts with the hub

      Delete
    5. try "tile" instead of "token" they've probably updated that bit, I remember it taking ages to figure out it was token in the first place

      Delete
    6. I've tried multimple times and result is the same : The HTTP header 'X-WindowsPhone-Target'.
      Only with 'token' there is no errors. Toast works corectly, raw too. (Javascript backend)

      Delete
  2. Looks like the prototype has changed: NotificationHubService.prototype.send = function (tags, payload, optionsOrCallback, callback) {
    Try this:
    notificationHubService.mpns.send(tagExpression, tile, function(error) {
    if (error) {
    console.error(error);
    }});

    ReplyDelete
    Replies
    1. Where has changed? MpnsService.prototype.send = function (tags, payload, targetName, notificationClass, optionsOrCallback, callback) {

      in https://github.com/WindowsAzure/azure-sdk-for-node/blob/master/lib/services/serviceBus/mpnsservice.js

      Delete
    2. This comment has been removed by the author.

      Delete
    3. Have you tried a v2 template?
      http://blogs.msdn.com/b/thunbrynt/archive/2014/04/10/windows-phone-8-1-for-developers-live-tiles.aspx

      Delete
    4. Yes, it the same. Thanks for ur help. I'll let you know when it will work. :)

      Delete
  3. Oops sorry I was looking at the wrong one. I'm reading the comments in the code and it looks like there are comments for future functions which aren't implemented like this:
    /**
    * @name MpnsService#sendFlipTile
    * @function
    *
    * @description
    * Sends a FlipTile notification (Only Windows Phone version 7.8 or later). For more information see http://msdn.microsoft.com/en-us/library/windowsazure/jj206971.aspx.
    *
    * @param {string} tags A single tag or tag expression.
    * @param {object} payload The message's payload.
    * @param {string} payload.backgroundImage The URI of the background image for the tile.
    * @param {string} payload.count The number that appears on the tile.
    * @param {string} payload.title The title text of the tile.
    * @param {string} payload.backBackgroundImage The URI of the image that is shown on the backside of the tile.
    * @param {string} payload.backTitle Title for the backside side of the tile.
    * @param {string} payload.backContent Text for the backside of the tile.
    * @param {string} payload.id ID of a related, secondary tile.
    * @param {string} payload.smallBackgroundImage The URI for the background image for the tile when it is reduced to its small size.
    * @param {string} payload.wideBackgroundImage The URI for the background image for the tile when it is expanded to its wide size.
    * @param {string} payload.wideBackContent Content for the back tile when the tile is expanded to its wide size.
    * @param {string} payload.wideBackBackgroundImage The URI for the image to be on the backside of the tile when the tile is expanded to its wide size.
    * @param {object} [options] The request options.
    * @param {object} [options.headers] The mpns headers.
    * @param {Function(error, response)} callback The callback function.
    * @return {undefined}
    */



    Could be worth asking someone on the Mobile Services team if they can shed any light on the issue. The mpns tile payload definitely used to work, but tags did not at the time

    ReplyDelete