Blogs

Blogs

Part-3:​​​​​​​ Web Push Notification

Setting up Push Notification on the Server side

Welcome back to the 3rd post of the notification series. In the previous post, we will understand how to work with client-side in push notification i.e Register Service worker, Service worker implementation, creating Subscription  Object and send it to the backend to save in the DB for future use.

Now we will build portlet to get the subscription object and save to the database and by using that info we will send the notification to the browser. Before going into deep dive let's go through the JAVA API which we are going to use to accomplish web push notification.  

The following API we are going to use for sending the push notification to the browser.

1.webpush-libs(API to send the notification).

2.bouncy castle(I have mentioned how to registered first part of this series ).

3.Apache HTTP-Client (For Sending the request to the servlet application which we will be going to create).

First we need to create a service where we need to store the data in DB. I am not covering that part into depth but will give a basic service.xml which I have used.

Service.xml

<entity local-service="true" name="PushSubscription"
        remote-service="true" uuid="true">
        <column name="pSubscriptionId" primary="true" type="long" />
        <column name="userId" type="long" primary="true"/>
        <column name="browserUrl" type="String" />
        <column name="auth" type="String" />
        <column name="subscriptionType" type="int" />
        <column name="p256Key" type="String" />
        <column name="groupId" type="long" />
        <column name="companyId" type="long" />
        <column name="createDate" type="Date" />
        <column name="modifiedDate" type="Date" />
        <finder name="userId" return-type="PushSubscription">
            <finder-column name="userId" />
        </finder>
        <finder name="SubscriptionType" return-type="Collection">
            <finder-column name="subscriptionType"/>
        </finder>    
    </entity>

Build your Own Service.xml according to your requirement.

Assume that we have read all the data which were sent from the browser and save into DB now lets we code for sending the notification.

The main method to send to forwarding the request to the notification application is insendRequest which we need JSON object which needs to be formatted in a format which we are formatting in andgetSubscribingObject JSON format Payload which contains the data which is going to be displayed in the notification popup. Server URL you can build by your own I have used a generic way to find it.

/**
     * This method will use to send the welcome notification when user will First Subscribe.<br/>
     * 
     * @param PushSubscription pushSubscription, PortletRequest actionRequest
     * @throws ExecutionException 
     * @throws InterruptedException 
     */
    private void sendWelcomeNotification(PushSubscription pushSubscription, PortletRequest actionRequest)
            throws IOException, InterruptedException, ExecutionException {
        String subscriptionJson = getSubscribingObject(pushSubscription);
        String serverUrl = getServerUrl(actionRequest);
        int statusCode = sendRequest(subscriptionJson, getWelcomePayload(actionRequest), serverUrl, "welcome");
        if (statusCode > 199 && statusCode < 300) {
            log.info(() -> "Sent Welcome Message with response code " + statusCode);
        }
    }
/**
     * Returns the subscription object from pushSubscription Object<br/>
     * The JSON String string will contains<br/>
     * <b>browserEndpointurl</b>,<b>p256dh key</b>,<b>auth key</b>.
     * <br/>
     * <br/>
     * This JSON will only contain the browser information.
     * 
     * @param pushSubscription
     * @return String jsonString
     */
    public String getSubscribingObject(PushSubscription pushSubscription) {
        JsonObject endpointJson = new JsonObject();
        endpointJson.addProperty(ENDPOINT, pushSubscription.getBrowserUrl());
        JsonObject apiKeyJson = new JsonObject();
        apiKeyJson.addProperty(PARAM_P256KEY, pushSubscription.getP256Key());
        apiKeyJson.addProperty(PARAM_AUTH, pushSubscription.getAuth());
        endpointJson.add("keys", apiKeyJson);
        return endpointJson.toString();
    }

/**
     * Send request to the browser for push notification.<br/>
     * If url is not present then it Will prepare own 
     * @see getServerUrl
     * 
     * @param subscriptionJson
     * @param payload
     * @param url
     * @param nType
     * @return int httpStatusCode
     * @throws IOException
     * @throws ExecutionException 
     * @throws InterruptedException 
     */
    
    public int sendRequest(String subscriptionJson, JSONObject payload, String url, String nType)
            throws IOException, InterruptedException, ExecutionException {
        CloseableHttpAsyncClient asyncClient=HttpAsyncClients.createDefault();
        asyncClient.start();
        HttpPost httpPost = null;
        if(Validator.isNotNull(url))
            httpPost=new HttpPost(url);
        else
            httpPost=new HttpPost(getServerUrl(null));
        List<NameValuePair> postParms = new ArrayList<>();
        postParms.add(new BasicNameValuePair("subscriptionJson", subscriptionJson));
        postParms.add(new BasicNameValuePair("payload", payload.toString()));
        postParms.add(new BasicNameValuePair("notificationType", nType));
        httpPost.setEntity(new UrlEncodedFormEntity(postParms));
        Future<HttpResponse> httpResponse=asyncClient.execute(httpPost, null);
        return httpResponse.get().getStatusLine().getStatusCode();
    }

Here we are sending a payload that what we need to be displayed to the user.You can have your own.I am having my own URL. 

/**
     * Returns the payload for Welcome Notification.
     * 
     * @return PortletRequest portletRequest
     */
    public JSONObject getWelcomePayload(PortletRequest portletRequest){
        JSONObject payload = JSONFactoryUtil.createJSONObject();
        payload.put("title", "Thank you for subscription.");
        payload.put("body", "Thanks for subscribing you will get all updated notification.");
        String redirectUrl=getRedirectUrl(portletRequest);
        payload.put("imageUrl", getDefaultImageUrl());
        payload.put("redirectUrl", redirectUrl);
        return payload;
    }
    public String getServerUrl(PortletRequest portletRequest){
        String url=getRedirectUrl(portletRequest);
        url=url.concat(PUSHSERVERURL);
        return url;
    }
    
    public String getRedirectUrl(PortletRequest portletRequest){
        StringBuilder url=new StringBuilder();
        if(portletRequest==null){
            url.append("http://");
            url.append(PortalUtil.getPortalServerInetAddress(false).getHostName());
            url.append(":");
            url.append(PortalUtil.getPortalServerPort(false));
        }else{
            url.append(portletRequest.getScheme());
            url.append("://");
            url.append(portletRequest.getServerName());
            url.append(":");
            url.append(portletRequest.getServerPort());
        }
        return url.toString();
    }

 

Now we are going to create a servlet application to send the request to the browser by using web push library.

Note: Here I am creating servlet application because when we are trying to send the notification from the portlet itself from webpush -libs which using internally using bouncy castle jar when we build it then it will download the bouncy castle jar into the module because webpush-libs internally using it.  we can have only one jar either on JDK or on Application. But In Liferay, ROOT/WEB-INF/libwe already have bouncy castle jar. Due to this BC jars is conflicting with each other and result to UnKnown BC Provider and No Such Key Algorithm Found.

The main Advantage of Creating a Servlet Application for sending push notification is preparing An independent API for sending notification for generic purpose.we can use it for multiple application on we can have a separate server for notification because nowadays and in the future notification is the key to engage the users even in offline also. It won't affect our portal performance because notification should separately.

Assume we have 10000 users and we want to notify them in multiple events Some the event needs to viewed in same day.notifiaction should be sent to every user and we just sent asynchronously in the same server so it will consume Liferay portal resource which may lead to drastically decrease the performance of the portal.

So far we have gone through how to register service worker and sends the browser info to the backend for future use. we saw the client as well as server-side code but still, we need to build an application which is responsible to send the notification.

We will learn in the next series of push notification post.

Thanks for reading enjoy coding time...............::))With a cup of coffee or team or beer I would prefer to have the last one:))))