Meteo02 Meteo02 - 10 days ago 6
Node.js Question

Cannot receive specific user push notification using azure notification hub

I am trying to learn to use azure mobile app, but I am having serious problems in using the NotificationHub. I have an Imagine subscription to Azure. I creating an android mobile app with azure backend. I have created a notification hub associated to the azure mobile app on the azure portal.
To register the app on the notification hub I used the code in this tutorial:

https://docs.microsoft.com/en-gb/azure/notification-hubs/notification-hubs-android-push-notification-google-fcm-get-started

The users are authenticated on the azure backend previuosly by using their google account, microsoft account or facebook account. New users are inserted into the table Users by the following node js code written for the table script Users.js. I want a push notification to Welcome the new User.

var azureMobileApps = require('azure-mobile-apps');
var logger = require('azure-mobile-apps/src/logger');
var table = azureMobileApps.table();

table.access = 'authenticated';

/**
* Adds the email address from the claims to the context item - used for
* insert operations
* @param {Context} context the operation context
* @returns {Promise} context execution Promise
*/
function addEmailToContext(context) {
/*
* Getting claim fields
*/
return context.user.getIdentity().then((data) => {
if( data.microsoftaccount != undefined){
context.item.email = data.microsoftaccount.claims.emailaddress;
context.item.name = data.microsoftaccount.claims.givenname;
context.item.surname = data.microsoftaccount.claims.surname;
}
if( data.google != undefined){
context.item.email = data.google.claims.emailaddress;
context.item.name = data.google.claims.givenname;
context.item.surname = data.google.claims.surname;
context.item.picture_url = data.google.claims.picture;
}
if( data.facebook != undefined){
context.item.email = data.facebook.claims.emailaddress;
context.item.name = data.facebook.claims.givenname;
context.item.surname = data.facebook.claims.surname;
}

logger.info('[tables/Users.js] --> NEW USER REGISTERED:'
+'\n\t Name:'+context.item.name
+'\n\t Surname:'+context.item.surname
+'\n\t Email:'+context.item.email);


// Execute the insert. The insert returns the results as a Promise,
// Do the push as a post-execute action within the promise flow.
return context.execute()
.then(function (results) {
// Only do the push if configured
if (context.push) {
// Mobile Apps adds a user tag when registering for push notifications

// Define the GCM payload.
var payload = {
"data": {
"message": 'Welcome '+context.item.username
}
};

context.push.gcm.send(context.user.id, payload, function (error) {
if (error) {
logger.error('Error while sending push notification: ', error);
} else {
logger.info('Push notification sent successfully!');
}
});
}
// Don't forget to return the results from the context.execute()
return results;
})
.catch(function (error) {
logger.error('Error while running context.execute: ', error);
});
});
}

// CREATE - add or overwrite the authenticated user
table.insert(addEmailToContext);

module.exports = table;


According to "How to: Send push notifications to an authenticated user using tags" in the tutorial on How to use the Azure Mobile Apps Node.js SDK

"When an authenticated user registers for push notifications, a user ID tag is automatically added to the registration. "
So in the Users.js, as suggested in this tutorial I wrote the following code to send the push notification to the user.

context.push.gcm.send(context.user.id, payload, function (error) {
if (error) {
logger.error('Error while sending push notification: ', error);
} else {
logger.info('Push notification sent successfully!');
}
});


With this code the push notification results to be sent successfully, but the device doesn't receive any notifications. If I use null instead of context.user.id then all devices receive the push notification correctly:

context.push.gcm.send(null, payload, function (error) {
if (error) {
logger.error('Error while sending push notification: ', error);
} else {
logger.info('Push notification sent successfully!');
}
});


I also tried to invoke the following custom API to create tag when the user is registered to the hub. The invoked API is the following:

var logger = require('azure-mobile-apps/src/logger');

exports.post = function(req, res) {
logger.info('[api/registerTag.js] --> Invoked');

// Get the notification hub used by the mobile app.
var push = req.azureMobile.push,
installationId = req.get('X-ZUMO-INSTALLATION-ID'),
tags = req.body.tag.toString();



// Define an update tags operation.
var updateOperation = [{
"op": "add",
"path": "/tags",
"value": tags
}];

// Update the installation to add the new tags.
push.patchInstallation(installationId, updateOperation, function(error) {
if(error){
logger.error('[api/registerTag.js] --> An error occurred while adding'
+'the following tags: \n\t'+tags, error);
res.status(error.statusCode).send(error.detail);
} else {
logger.info('[api/registerTag.js] --> The following tags have been added'
+'to the Notification Hub: \n\t'+tags, error);
res.status(200).send(tags);
}
});
};


On the console it is printed that the tag has been added successfully. But if I then modify the Users.js code like this:

...
// Only do the push if configured
if (context.push) {
// Mobile Apps adds a user tag when registering for push notifications
var userTag = '_UserId:' + context.user.id;
logger.info("TAG "+userTag);

// Define the GCM payload.
var payload = {
"data": {
"message": 'Welcome '+context.item.username
}
};

context.push.gcm.send(userTag, payload, function (error) {
if (error) {
logger.error('Error while sending push notification: ', error);
} else {
logger.info('Push notification sent successfully!');
}
});
}
...


again nothing is received. I have also tried whitelisting tags or adding them automatically using the Push section of the mobile app like shown in the image:

IMAGE LINK: i.stack.imgur.com/KBvQI.png

But the problem is still there. Hope someone can help me. Thanks.

Answer

After several times of testing, I succeeded in reproducing your issue and got the same problem. To achieve your requirement I did some modification in Android client-end:

1, Cache authentication user in the MainActivity class. Following is my code snippet. For more details you can refer here.

public static final String SHAREDPREFFILE = "temp";    
public static final String USERIDPREF = "uid";    
public static final String TOKENPREF = "tkn";

@Override
protected void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    try {
        // Create the Mobile Service Client instance, using the provided Mobile Service URL and key
        mClient = new MobileServiceClient(
                "https://aaronandroidstack.azurewebsites.net",
                this).withFilter(new ProgressFilter());

        // Extend timeout from default of 10s to 20s
        mClient.setAndroidHttpClientFactory(new OkHttpClientFactory() {
            @Override
            public OkHttpClient createOkHttpClient() {
                OkHttpClient client = new OkHttpClient();
                client.setReadTimeout(20, TimeUnit.SECONDS);
                client.setWriteTimeout(20, TimeUnit.SECONDS);
                return client;
            }
        });

        authenticate();

    } catch (MalformedURLException e) {
        createAndShowDialog(new Exception("There was an error creating the Mobile Service. Verify the URL"), "Error");
    } catch (Exception e){
        createAndShowDialog(e, "Error");
    }
}

private void authenticate() {
    // We first try to load a token cache if one exists.
    if (loadUserTokenCache(mClient)) {
        createTable();
        register();
    }
    // If we failed to load a token cache, login and create a token cache
    else {
        // Login using the Google provider.
        ListenableFuture<MobileServiceUser> mLogin = mClient.login(MobileServiceAuthenticationProvider.Google);

        Futures.addCallback(mLogin, new FutureCallback<MobileServiceUser>() {
            @Override
            public void onFailure(Throwable exc) {
                createAndShowDialog("You must log in. Login Required", "Error");
            }
            @Override
            public void onSuccess(MobileServiceUser user) {
                createAndShowDialog(String.format("You are now logged in - %1$2s", user.getUserId()), "Success");
                cacheUserToken(mClient.getCurrentUser());
                createTable();
                register();
            }
        });
    }
}

private void cacheUserToken(MobileServiceUser user) {
    SharedPreferences prefs = getSharedPreferences(SHAREDPREFFILE, Context.MODE_PRIVATE);
    Editor editor = prefs.edit();
    editor.putString(USERIDPREF, user.getUserId());
    editor.putString(TOKENPREF, user.getAuthenticationToken());
    editor.commit();
}

private void register() {
    NotificationsManager.handleNotifications(this, NotificationSettings.SenderId, MyHandler.class);
    registerWithNotificationHubs();
}

2, In RegistrationIntentService class replace regID = hub.register(FCM_token).getRegistrationId(); with the following code:

regID = hub.register(FCM_token, prefs.getString("uid", "")).getRegistrationId();

3, Make sure add the line below to the first line within onHandleIntent method.

SharedPreferences prefs = getSharedPreferences("temp", Context.MODE_PRIVATE);