Patrick Desjardins Blog
Patrick Desjardins picture from a conference

Service Worker, Push Notification and Asp.Net MVC – Part 3 of 3 Server Side

Posted on: 2017-01-23

I previously discussed about how to configure a web push notification from the client side perspective as well as how to send the notification from an ASP.Net code which could be sent from Azure Webjobs. The remaining part is how do you send to multiple devices of one user. If you have a single browser used by a user, the initial solution is good. However, the reality is that users use multiple devices. Users can use not only different browsers on different machines but also jump from computer to phone and so on. The idea is to register subscription by device and not by user.

Google Firebase documentation explains briefly the "Device group messaging" but the page talks more about Topic. I couldn't figure out how to use the device group messaging but could use Topic for the same matter. The idea is to use a single topic per user and send a message to this topic.

The first big change is to add a subscribe method and unsubscribe that work with the topic api.

public bool UnRegisterTopic(string userIdentifierForAllDevices, string singleDeviceNoticationKey) {
  var serverApiKey = ConfigurationManager.AppSettings["FirebaseServerKey"];
  var firebaseGoogleUrl = $"https://iid.googleapis.com/iid/v1/{singleDeviceNoticationKey}/rel/topics/{userIdentifierForAllDevices}";

  var httpClient = new WebClient();
  httpClient.Headers.Add("Content-Type", "application/json");
  httpClient.Headers.Add(HttpRequestHeader.Authorization, "key=" + serverApiKey);

  object data = new { };
  var json = JsonConvert.SerializeObject(data);
  Byte[] byteArray = Encoding.UTF8.GetBytes(json);
  var responsebytes = httpClient.UploadData(firebaseGoogleUrl, "DELETE", byteArray);
  string responsebody = Encoding.UTF8.GetString(responsebytes);
  dynamic responseObject = JsonConvert.DeserializeObject(responsebody);

  return responseObject.success == "1";
}

public bool RegisterTopic(string userIdentifierForAllDevices, string singleDeviceNoticationKey) {
  var serverApiKey = ConfigurationManager.AppSettings["FirebaseServerKey"];
  var firebaseGoogleUrl = $"https://iid.googleapis.com/iid/v1/{singleDeviceNoticationKey}/rel/topics/{userIdentifierForAllDevices}";

  var httpClient = new WebClient();
  httpClient.Headers.Add("Content-Type", "application/json");
  httpClient.Headers.Add(HttpRequestHeader.Authorization, "key=" + serverApiKey);

  object data = new{};
  var json = JsonConvert.SerializeObject(data);
  Byte[] byteArray = Encoding.UTF8.GetBytes(json);
  var responsebytes = httpClient.UploadData(firebaseGoogleUrl, "POST", byteArray);
  string responsebody = Encoding.UTF8.GetString(responsebytes);
  dynamic responseObject = JsonConvert.DeserializeObject(responsebody);

  return responseObject.success == "1";
 }

There is quite repetition in that code and you can improve it easily. The biggest change is the URL. Not only the URL domain is different (before https://fcm.googleapis.com/fcm/send and now https://iid.googleapis.com/), it has different route portions. The first part is the device notification key which is the token generated by the client side from the method "getToken". The second portion of the route is the user identifier which I use as topic. If you really need topic across users, you can just use a string with the category needed. In my case, it is just the unique GUID of the user. This POST HTTP call will register the device for the user by a topic which is the user ID.

To send a message to the user, on all devices, the code needs also to change.

public bool QueueMessage(string to, string title, string message, string urlNotificationClick) {
  if (string.IsNullOrEmpty(to)) {
     return false;
  }
  var serverApiKey = ConfigurationManager.AppSettings["FirebaseServerKey"];
  var firebaseGoogleUrl = "https://fcm.googleapis.com/fcm/send";

var httpClient = new WebClient();
httpClient.Headers.Add("Content-Type", "application/json");
httpClient.Headers.Add(HttpRequestHeader.Authorization, "key=" + serverApiKey);
var timeToLiveInSecond = 24 * 60; // 1 day
var data = new {
  to = "/topics/" + to ,
  data = new {
    notification = new {
      body = message,
      title = title,
      icon = "/Content/Images/Logos/BourseVirtuelle.png",
      url = urlNotificationClick,
      sound = "default"
    }
  },
  time_to_live = timeToLiveInSecond };

  var json = JsonConvert.SerializeObject(data);
  Byte[] byteArray = Encoding.UTF8.GetBytes(json);
  var responsebytes = httpClient.UploadData(firebaseGoogleUrl, "POST", byteArray);
  string responsebody = Encoding.UTF8.GetString(responsebytes);
  dynamic responseObject = JsonConvert.DeserializeObject(responsebody);

return responseObject.success == "1"; }

What has changed from sending to a single user? The field "to" which is sending to topics. The "to" in the method signature is still the user unique identifier, but instead of sending directly to it, we use it has a topic. We do not use the token generated by the front end since a new one got generated per device, we only use the user id which is the topic.