Dela via


Push Notifications to PhoneGap Apps using Notification Hubs Integration

PhoneGap (a distribution of the Apache Cordova project) is a free and open source framework that makes it easier to create app for key mobile device platforms using standardized web APIs, HTML and JavaScript. The essence of PhoneGap is that you write your app once in HTML and JavaScript, then deploy this web-based code to native devices (Android, iOS, Windows Phone, etc…). Unlike purely web-driven mobile apps, PhoneGap apps have some access to native resources, such as push notifications, the accelerometer, camera, storage, geolocation, the in-app browser. PhoneGap apps feel a bit like Web apps, but often with the behavior of native device apps.

Azure Mobile Services supports PhoneGap by providing a quickstart project that you can download from the portal. This TodoList sample project supports Android, iOS and Windows Phone 8 devices. To learn how to get started with PhoneGap and Mobile Services, see Get Started with Mobile Services.

This blog post shows how to use Mobile Services and Azure Notification Hubs to send push notifications to a PhoneGap app, particularly the PhoneGap quickstart sample app.

TodoList Notifications Sample

The TodoList Notifications sample project shows you how to work with Notification Hubs from a Mobile Services-enabled PhoneGap app. This sample uses the notification hub associated with the TodoList mobile service. Follow the steps in the readme to make this sample work with a mobile service that belongs to you. At least one device-specific Notification Hubs registration is required to receive notifications. I won’t go into detail on how Notification Hubs works, you can read more about it here.

The sample includes the script NotificationHubs.js, which is a very basic library for registering for notifications and storing registrations on the device to prevent duplicate registrations from being created. Because registrations can become stale, the sample creates or updates a registration each time the app starts. 

The sample uses the following PhoneGap/Cordova plugins:

  • PushPlugin: provides push notification registration functionality.
  • Device: returns device-specific info, like what platform is running
  • (Optional) Console: writes to the platform-specific console—helpful during debugging.

Using the PhoneGap PushPlugin

To register a device with Notification Hubs, the app must get a token from the platform-specific push notification service, in this case APNS (iOS), GCM (Android), MPNS (WP8). To do this, we use the PhoneGap PushPlugin, which knows how to talk to each of these native services to obtain a push token/ID/channel. The following code, from index.js, executes after the app loads to request a registration depending on the platform information returned from the device plugin:

 // Define the PushPlugin.
var pushNotification = window.plugins.pushNotification;

// Platform-specific registrations.
if ( device.platform == 'android' || device.platform == 'Android' ){     
    // Register with GCM for Android apps.
    pushNotification.register(
       app.successHandler, app.errorHandler,
       { 
        "senderID": GCM_SENDER_ID, 
        "ecb": "app.onNotificationGCM" 
        });
} else if (device.platform === 'iOS') {
    // Register with APNS for iOS apps.
    pushNotification.register(
        app.tokenHandler,
        app.errorHandler, {
            "ecb": "app.onNotificationAPN"
        });
}
else if(device.platform === "Win32NT"){    
    // Register with MPNS for WP8 apps.
    pushNotification.register(
        app.channelHandler,
        app.errorHandler,
        {
            "channelName": "MyPushChannel",
            "ecb": "app.onNotificationWP8",
            "uccb": "app.channelHandler",
            "errcb": "app.ErrorHandler"
    });
}

The registered callback function for a specific device type (also in index.js) then takes the token returned by the push notification service and sends it to Notification Hubs for registration. The following is the tokenHandler function that handles iOS registration that returns the APNS device token:

 // Handle the token from APNS and create a new hub registration.
tokenHandler: function (result) {
    if (mobileClient) {
        // Call the integrated Notification Hub client.
        var hub = new NotificationHub(mobileClient);

        // This is a template registration.
        var template = "{\"aps\":{\"alert\":\"$(message)\"}}";

        // (deviceId, ["tag1","tag2"], templateName, templateBody, expiration)
        hub.apns.register(result, null, "myTemplate", template, null).done(function () {
            alert("Registered with hub!");
        }).fail(function (error) {
            alert("Failed registering with hub: " + error);
        });
    }
}

The result passed to the callback is the device token. The registration is a template registration, without any tags (although the register function supports tags).  The register function can also create native registrations, but template registration is definitely the way to go with PhoneGap and it's multiple push platforms.

To prevent duplicate registrations for a given device, registrations are stored in local storage on the device.

Mobile Services REST APIs for Notification Hubs

Both Mobile Services and Notification Hubs provide native client libraries for all major device platforms, .NET, iOS, Android, and even HTML. For example, you can register for notifications from an iOS app by simply calling a register method on the mobile service client. Their is currently one scenario that lacks client library coverage, namely registering for push notifications from an HTML5/JavaScript client. Fortunately, Mobile Services provides its own REST APIs for registering with the associated notification hub. There are three basic REST methods, POST, PUT and DELETE. A new registration is created by a POST request to https://<mobile_service>.azure-mobile.net/push/registrationsids. Success returns an HTTP 201 with the registration ID in the Location header.  This is followed by a PUT request to https://<mobile_service>.azure-mobile.net/push/registrations/<regId> , the message body, which is the actual registration payload being added or updated, looks like the following for a template registration for APNS (iOS):

 {
    platform: "apns" // {"wns"|"mpns"|"apns"|"gcm"}--REQUIRED
    deviceId: "" // unique device token--REQUIRED
    tags: "tag" | ["a","b"] // non-empty string or array of tags (optional)
    templateBody: "{\"aps\":{\"alert\":\"$(message)\"}}" // --OPTIONAL (platform-specific)
    templateName: "myTemplate" // if template registration -- OPTIONAL (REQUIRED if template)
    headers: { } //-- OPTIONAL (used on WNS/MPNS templates)
    expiration: "" // if apns template -- OPTIONAL (used on APNS templates)
}

Success returns an HTTP 204. A response code of 410 indicates the registration ID in the URL is invalid and a new one must be created.

Because the actual registration payload is JSON, the sample basically just stores the registration payload in local storage on the device, retrieve it set or change any fields, and then attach it as the payload in the registration request. The following functions in NotificationHubs.js store and retrieve a registration, respectively:

 var storeInContainer = function (key, object) {
    localStorage.setItem(key, JSON.stringify(object));
};

var getFromContainer = function (key) {

    if (typeof localStorage.getItem(key) === 'string') {
        return JSON.parse(localStorage.getItem(key));
    }
    return undefined;
};

A registration is deleted by sending a DELETE request to https://<mobile_service>.azure-mobile.net/push/registrations/<regId> , and success returns HTTP 200. The following function sends a POST request to create a new registration:

 var createRegistrationId = function (hub) {

    // Variables needed to make the request to the mobile service.
    var method = "POST";
    var uriFragment = "/push/registrationids";

    var deferred = $.Deferred();

    // Send a Notification Hubs registration request to the Mobile Service.
    hub.mobileClient._request(method, uriFragment, null, null, null,
        function (error, response) {
        if (error) {
            console.log("Error: " + error);
            deferred.reject("Error: " + error);
        } else {

            // Get the unique registration ID from the Location header.
            var location = response.getResponseHeader("Location");            
            var regex = /\S+\/registrations\/([^?]+).*/;
            var regId = regex.exec(location)[1];

            // Return the registration ID.
            deferred.resolve(regId);
        }
    });
    return deferred.promise();
}

This function updates an existing registration with the supplied registration payload.

 // Update an existing registration--includes payload.
var updateRegistration = function (hub, regId, registration) {

    // Variables needed to make the request to the mobile service.
    var registrationPayload = buildCreatePayload(registration);
    var method = "PUT";
    var uriFragment = "/push/registrations/" + regId;

    var deferred = $.Deferred();

    // Send a Notification Hubs registration update to the Mobile Service.
    hub.mobileClient._request(method, uriFragment, registrationPayload, null,
        null, function (error, response) {
        if (error) {
            console.log("Error: " + error);
            deferred.reject("Error: " + error);
        } else {
            console.log("Updated registration: " + regId);
            deferred.resolve();
        }
    });
    return deferred.promise();
};

The NotificationHubs.js library also supports deleting a registration, but the sample doesn’t actually use those APIs.

Sending Notifications

Like the other Mobile Services push notification tutorials, this sample sends a notification to all registrations whenever a new item is inserted in the TodoItems table. Here’s the server script code that sends a template notification to all registrations:

 function insert(item, user, request) {
    // Execute the request and send notifications.
   request.execute({
       success: function() {
           // Create a template-based payload.
           var payload = '{ "message" : "New item added: ' + item.text + '" }';
           // Write the default response and send a notification
           // to all platforms.
           push.send(null, payload, {
               success: function(pushResponse){
               console.log("Sent push:", pushResponse);
               // Send the default response.
               request.respond();
               },
               error: function (pushResponse) {
                   console.log("Error Sending push:", pushResponse);
                    // Send the an error response.
                   request.respond(500, { error: pushResponse });
                   }
            });
       }
   });
}

That about covers the interesting parts of the sample. If you have troubles with the registration, check out this handy topic on Debugging Notification Hubs.

Running the App

The complete set of requirements and steps to run the sample are detailed in the sample readme file. Basically, you need to set the Mobile Service URL and application key and the GCM sender ID and then rebuild each platform.

The running program looks like the Mobile Services HTML quickstart app, except that on start an alert is displayed when registration is complete.

image       image

(Shown on a Windows Phone device.)

When a new item is inserted, a notification is raised on each registered device, regardless of the platform.

image

If you have any questions about or find any issues with the sample, please open an issue in GitHub.

Cheers,

Glenn Gailey

Comments

  • Anonymous
    October 07, 2014
    Hello,great article, however I have diffilcuties connecting to the azure mobile service hub from a phonegap application (running on android).Request URL:nomnio-staging.azure-mobile.net/.../registrationidsRequest Method:OPTIONSStatus Code:500 Internal Server ErrorAny idea what am I missing?Thank you

  • Anonymous
    October 27, 2014
    Hi there, this was very helpful however when I include a tag I keep getting the following error:file:///android_asset/www/js/NotificationHub.js: Line 58 : Error: Error: Internal Server Error.Its working fine as long as I don't specify any tags.  As far as I understand, all you have to do is specify a comma separate list of tags?  Even a single tag isn't working:                   hub.gcm.register(e.regid, "tagme", null, null).done(function () {                       alert("Registered with hub!");                   }).fail(function (error) {                       alert("Failed registering with hub: " + error);                   });This works fine:                   hub.gcm.register(e.regid, null, null, null).done(function () {                       alert("Registered with hub!");                   }).fail(function (error) {                       alert("Failed registering with hub: " + error);                   });The error is at:       hub.mobileClient._request(method, uriFragment, registrationPayload, null,           null, function (error, response) {           if (error) {               console.log("Error: " + error);               deferred.reject("Error: " + error);           } else {               console.log("Updated registration: " + regId);               deferred.resolve();           }       });

  • Anonymous
    November 10, 2014
    Is an example of web api registrationids. I used this exampleazure.microsoft.com/.../notification-hubs-aspnet-backend-windows-dotnet-notify-users. The method of the controller is working perfeitamenta RegiterController post, however u dont get herevar location = response.getResponseHeader ("Location");var regex = /S+/registrations/([^?]+).*/;var = RegID regex.exec (location) [1];and the response has only two tags that do not represent the return. Have any idea? Thank you

  • Anonymous
    November 11, 2014
    Not bad but i have found a lot better push notification service called "Bulk Push" people should try it and see by themselves

  • Anonymous
    January 08, 2015
    In which platforms you are using for develop these applicatoins. especially UI side..

  • Anonymous
    January 13, 2015
    The comment has been removed

  • Anonymous
    April 08, 2015
    Will this push service support location based notification to user approaching e.g. coffeeshop or restaurant? I tried to find the document about this but could not find it.ThanksThomas

  • Anonymous
    April 15, 2015
    Great blog.Unfortunately, the sample does not work for Windows Store Apps based on Cordova: wns-registration is not supported in NotificationHub.js. Is there an extended version of this script or another js-sample how to register for WNS?ThanksMartin

  • Anonymous
    May 19, 2015
    @Thomas: you can replicate this location-based behavior by including a location tag each time the client starts and registers with Notification Hubs. The only risk is the location getting stale between app starts. @Martin: Adding support for WNS to this sample is on my todo list (github.com/.../46) but it may be a while before I can get to it.

  • Anonymous
    July 15, 2015
    Hello, I'm using ionic and I also run into the issue that Fabricio reported above, the response I'm getting is empty even though the registrations are created on the server. Do you what may be the issue?

  • Anonymous
    September 17, 2015
    We are using Visual Studio to create an Apache Cordova app that is connected to an Azure Mobile Service . We are using the Cordova Push Plugin to receive Notifications in our app. We have created a JavaScript method in the Azure Mobile Service to send the notifications, this method is triggered by a scheduler. Please note that the intention is to send each Notification to a particular device. The notifications work perfectly for Android devices but not for the iOS devices. In iOS the notification is not received however the method in Azure Mobile Service always returns “Success”. If we set the Device Token to "Null" , then it works for iOS devices (but sends to all devices not just one). Could anyone please help us to resolve this issue?

  • Anonymous
    October 03, 2015
    This blog is outdated. The mentioned Push-Plugin (github.com/.../PushPlugin) is deprecated. The descendant (github.com/.../phonegap-plugin-push) is very different and focuses on Universal Apps and WNS.

  • Anonymous
    January 20, 2016
    hi , we try same but we are not able to get push notification on my iphone app..