Skip to content

PWA : Push Notifications

Mark Kevin Baldemor edited this page Dec 4, 2017 · 3 revisions

Introduction

Implementing Push Notification on GMD is very easy, we provided a Manager Class to easily control your Push Notification feature called PushNotificationManager which provided the subscription of the Client's app to your Web-Push Server later on we will discuss how we can build our Web-Push Server. We marry the PushNotification directly to the Service Worker to listen any push messages thrown by our Server. This will allow the browser also to receive push notifications even it was already closed (That's the benefit of wiring the Service Worker + Push Notification).

Some links for Basic Introduction of Service Worker can be found here.

Guidelines

1. Setting Up your AppServiceWorkerManager that extends DefaultServiceWorkerManager

In your ServiceWorkerManager#onRegistered lifecycle setup your PushNotificationManager that accepts your ServiceWorkerRegistration. Load the PushNotificationManager and will detect if the user subscription was already subscribed or not.

    @Override
    public void onRegistered(ServiceWorkerRegistration registration) {
        pushNotificationManager = new PushNotificationManager(registration);
        pushNotificationManager.load(subscription -> {
            if (subscription == null) {
                MaterialToast.fireToast("Not subscribed to Push Notifications");
            } else {
                MaterialToast.fireToast("Subscribed to Push Notifications");
            }
        });
    }

This public method will be used later to subscribe the user. Take note we provided a public key (VAPID) here to be used later in our web-push server.

 
    public void subscribe(Functions.Func callback) {
        pushNotificationManager.subscribe(true, "BAvr2GL1EQdLnxgQDVeZSXnsWYNSaBbIkq4DsWQXwnpGrqXoGp_7YK0CiSPvszzPnAj-D49Ne-zKDBRWHHXBL1c", subscription -> {
            if (subscription != null) {
                sendSubscriptionToServer(subscription);
                callback.call();
            }
        });
    }

Here we will need to subscribe the client to the server, the PushNotificationAPI will return a PushSubscription which contains all the necessary parameters to be used later on Web-Push plugin. We created also an RPC Service subscribeUser(endpoint, auth, key) for communicating to the server.

    protected void sendSubscriptionToServer(PushSubscription subscription) {
        endpoint = subscription.endpoint;
        key = PushCryptoHelper.arrayBufferToBase64(subscription.getKey("p256dh"));
        auth = PushCryptoHelper.arrayBufferToBase64(subscription.getKey("auth"));
        messageService.subscribeUser(endpoint, auth, key, new AsyncCallback<Void>() {
            @Override
            public void onFailure(Throwable throwable) {
                MaterialToast.fireToast(throwable.getMessage());
            }

            @Override
            public void onSuccess(Void aVoid) {
                MaterialToast.fireToast("Subscribed user to Server Web Push. Ready for receiving push notifications.");
            }
        });
    }

Also in your RPCService please create a subscribeUser(endpoint, auth, key) for storing the subscription to the server, later on we can store it to database or file for dynamic storing.

    @Override
    public void subscribeUser(String endpoint, String auth, String key) {
        subscriptions.add(new Subscription(endpoint, auth, key));
    }

This method will automatically unsubscribe the user from the PushNotification subscriptions.

    public void unsubscribe(Functions.Func callback) {
        pushNotificationManager.unsubscribe(() -> callback.call());
    }

2. Adding web-push api to our pom.xml

For more details about the web-push api see here. Also refer to that doc on how we will generate our VAPID (Voluntary Application Server Identification) Keys.

<dependency>
    <groupId>nl.martijndwars</groupId>
    <artifactId>web-push</artifactId>
    <version>3.0.1</version>
</dependency>

3. Create our WebPushImpl

Here we will need our custom implementation of the Web-Push api to send our Notification using pushService.send(notification). Also note that Notification needs our subscription params : endpoint, auth and key variables.

public class WebPushImpl {

    static Logger logger = Logger.getLogger(WebPushAPI.class.getSimpleName());

    public void sendPushMessage(Subscription sub, byte[] payload) throws GeneralSecurityException, InterruptedException, JoseException, ExecutionException, IOException {
        // Create a notification with the endpoint, userPublicKey from the subscription and a custom payload
        Notification notification = new Notification(sub.getEndpoint(), sub.getKey(), sub.getAuth(), payload);
        PushService pushService = new PushService();
        pushService.setSubject(PushConfig.SUBJECT);
        pushService.setPublicKey(Utils.loadPublicKey(PushConfig.ENCODED_PUBLIC_KEY));
        pushService.setPrivateKey(Utils.loadPrivateKey(PushConfig.ENCODED_PRIVATE_KEY));
        HttpResponse httpResponse = pushService.send(notification);

        logger.info(String.valueOf(httpResponse.getStatusLine().getStatusCode()));
        logger.info(IOUtils.toString(httpResponse.getEntity().getContent(), StandardCharsets.UTF_8));
    }
}

4. Create and RPC Service to wire the WebPushImpl.

This will notify all the users that had subscribed already to our List<Subscription> objects.

    @Override
    public void notifyAllUser(NotificationDTO notification) {
        WebPushAPI webPushAPI = new WebPushAPI();
        try {
            for (Subscription subscription : subscriptions) {
                webPushAPI.sendPushMessage(subscription, generatePayload(notification));
            }
        } catch (GeneralSecurityException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (JoseException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

As a return object on our push service we will need it to return byte[] for security purposes. And based on the example, we used JsonObject to be passed to our service worker instances.

    protected byte[] generatePayload(NotificationDTO notification) {
        JsonObject object = new JsonObject();
        object.addProperty("title", notification.getTitle());
        object.addProperty("description", notification.getDescription());
        object.addProperty("image", notification.getImage());
        return object.toString().getBytes();
    }

5. Final Step is to add push event listener to our service worker.

So in here we listen any Push events thrown by our server and parsed the data as a JSON Object and display as a notification.

self.addEventListener('push', function(event) {
    console.log('[Service Worker] Push Received.');
    console.log(`[Service Worker] Push had this data: "${event.data.text()}"`);

    var jsonObject = JSON.parse(event.data.text());

    const title = jsonObject.title;
    const options = {
        body: jsonObject.description,
        icon: jsonObject.image
    };

    event.waitUntil(self.registration.showNotification(title, options));
});

Will response to notification click listener and opens a new window.

self.addEventListener('notificationclick', function(event) {
    console.log('[Service Worker] Notification click Received.');

    event.notification.close();

    event.waitUntil(
        clients.openWindow('https://www.google.com.ph')
    );
});