-
Notifications
You must be signed in to change notification settings - Fork 124
PWA : Push Notifications
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.
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());
}
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>
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));
}
}
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();
}
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')
);
});