Blogs

Blogs

Part-4:​​​​​​​ Web Push Notification

Setting Up the External Servlet Application for sending the push notification.

If you are here then Hopefully you will learn a lot and curious to know to complete the push notification. Kindly Give a huge round of applause to yourself. Till our previous post we all set up for subscription users data and payload(Data which is display in form of push notification). 

In the 3rd partof this series, I had already explained what is the actual need for this external application.

In this POST we will get all the required information which we have to send through Apache Client API.H ere is the list what we need to send the notification properly.

  • Browser endpoint Url.
  • Browser auth key.
  • Browser p256dh generated key.
  • Payload(Data).
  • VAPIDS Key(public and private key).

This we will achieve through webpush  libs for java you can explore from  GIT Hub

Now here are we with the final code for sending the push notification from the webpush library.

Here Custom implementation of Subscription class for generating custom key pairs with algorithms.

public class Subscription
{
  private String endpoint;
  private Subscription.Keys keys;
  
  public Subscription()
  {
    if (Security.getProvider("BC") == null) {
      Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
    }
  }
  
  public Subscription(String endpoint, Subscription.Keys keys) {
    this.endpoint = endpoint;
    this.keys = keys;
  }
  
  public String getEndpoint() {
    return endpoint;
  }
  
  public void setEndpoint(String endpoint) {
    this.endpoint = endpoint;
  }
  
  public Subscription.Keys getKeys() {
    return keys;
  }
  
  public void setKeys(Subscription.Keys keys) {
    this.keys = keys;
  }

  public byte[] getAuthAsBytes()
  {
    return Base64.getDecoder().decode(keys.getAuth());
  }
  
  public byte[] getKeyAsBytes() {
    return Base64.getDecoder().decode(keys.getP256dh());
  }
  /** * Returns the base64 encoded public key as a PublicKey object */
  public PublicKey getUserPublicKey()
  {
    try {
      KeyFactory factory = KeyFactory.getInstance("ECDH", "BC");
      ECNamedCurveParameterSpec curveParameterSpec = 
        org.bouncycastle.jce.ECNamedCurveTable.getParameterSpec("prime256v1");
      org.bouncycastle.math.ec.ECPoint ecPoint = curveParameterSpec.getCurve().decodePoint(getKeyAsBytes());
      ECPublicKeySpec ecPublicKeySpec = new ECPublicKeySpec(ecPoint, curveParameterSpec);
      System.out.println("factory generated key is : " + factory.generatePublic(ecPublicKeySpec).toString());
      return factory.generatePublic(ecPublicKeySpec);
    } catch (NoSuchAlgorithmException e) {
      e.printStackTrace();
    } catch (NoSuchProviderException e) {
      e.printStackTrace();
    } catch (InvalidKeySpecException e) {
      e.printStackTrace();
    }
    return null;
  }
 
}

Now the implementation of our Push Services API.

From Subscription Object, We build the Notification Object Which is responsible to configure notification for the browser. By using vapids Key we are creating object of  PushService by providing  VAPIDS public and private keys for authentication, it is mainly for Google Chrome, For Firefox we don't need authentication. From pushService .send (bypassing notification Object) we send the push notification to the client.

Note: For Security no need to worry because web push libs internally converting all data into the encrypted format by using Bouncy Castle API.

Here we are adding provider if it is not registered the Security Provider by  Security.addProvider(new BouncyCastleProvider())

getSubscription  is responsible to replace \\ and \ (at the start)from the JSON object because when we are retrieving the JSON string from request object it will convert append \\ and \ where it is needed(because in a request it is only a string). 

  private static final int TTL = 255;
  private static final String PUBLICKEY = "your vapids public keys";
  private static final String PRIVATEKEY = "your vapids private     keys";
  private static final String SUBSCRIPTIONJSON = "subscriptionJson";
  
  public PushNotificationServlet() {}
  
  public void sendPushMessage(Subscription subscription, String payload) throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchProviderException, IOException, JoseException
  {
    try
    {
      if(Validator.isNull(Security.getProvider("BC"))){
           Security.addProvider(new BouncyCastleProvider());
      }
      Notification notification = null;
      PushService pushService = null;
      if (shouldUseGcm(subscription.getEndpoint())) {
        notification = new Notification(subscription.getEndpoint(), subscription.getUserPublicKey(), 
          subscription.getAuthAsBytes(), payload.getBytes(), 255);
        pushService = new PushService();
        pushService.setPrivateKey(Utils.loadPrivateKey(PRIVATEKEY));
        pushService.setPublicKey(Utils.loadPublicKey(PUBLICKEY));
        pushService.setSubject("mailto:sudhanshu.shukla@infoaxon.com");
      } else {
        notification = new Notification(subscription.getEndpoint(), subscription.getUserPublicKey(), 
          subscription.getAuthAsBytes(), payload.getBytes());
        pushService = new PushService();
      }
      HttpResponse httpResponse = null;
      httpResponse = pushService.send(notification, Encoding.AESGCM);
      System.out.println("Content : " + httpResponse);
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
  
  private static boolean shouldUseGcm(String endpoint)
  {
    if (endpoint.contains("mozilla")) {
      return false;
    }
    return true;
  }
  
  protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
  {
    try {
      String nType = req.getParameter("notificationType");
      if ((nType != null) && (!nType.trim().equals(""))) {
        String payload = req.getParameter("payload");
        JsonObject payloadJson = (JsonObject)new Gson().fromJson(payload, JsonObject.class);
        if (nType.equalsIgnoreCase("welcome")) {
          String subscriptionJson = req.getParameter("subscriptionJson");
          sendPushMessage(PushNotificationUtil.getSubscription(subscriptionJson, true), payloadJson.toString());
        } else if (nType.equalsIgnoreCase("allUsersNotify")) {
          String subUsersList = req.getParameter("subscriptionJson");
          JsonArray subscriptionArray = (JsonArray)new Gson().fromJson(subUsersList, JsonArray.class);
          notifyAllUsers(subscriptionArray, payload);
        }
      }
    } catch (NoSuchAlgorithmException|InvalidKeySpecException|NoSuchProviderException|JoseException e) {
      e.printStackTrace();
    }
  }
  
  private void notifyAllUsers(JsonArray array, String payload)
    throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchProviderException, IOException, JoseException
  {
    for (JsonElement jsonElement : array) {
      sendPushMessage(PushNotificationUtil.getSubscription(jsonElement.toString(), false), payload);
   }

   private Subscription getSubscription(String subscriptionJson, boolean isWelcome) {
    if (!isWelcome) {
      subscriptionJson = subscriptionJson.replaceAll("\\\\", "");
      subscriptionJson = subscriptionJson.substring(1, subscriptionJson.lastIndexOf("\""));
    }
    return (Subscription)new com.google.gson.Gson().fromJson(subscriptionJson, Subscription.class);
  }
  }

Shortly after this code is run, the message should be delivered to the user. Congratulations, you have just implemented push notifications in a progressive way!

For More Information refer webpush  libs for java for push library implementation.

Thanks for your patient to read all the parts of this series and you will be appreciated if you have any suggestion or any correction to do in this kindly feel free to comment and share. In future Blogs will explore more features of PWA with Liferay.


Till then Enjoy Coding and kindly share the knowledge may that help someone. I have struggled one and a half month to work this completely in Liferay. May this save someone's time.