Web Push Notifications
This part of this tutorial explains how to make a Vaadin Flow application utilize Web Push. This can enable Web Push for a completed Customer Relationship Management (CRM) application so that users can receive notifications — even when they’re not accessing the application.
Warning
|
Experimental Feature
Web Push Notifications is an experimental feature. It may be removed, altered, or limited to commercial subscribers in future releases.
|
To enable the Web Push Notifications experimental feature, add a new line containing com.vaadin.experimental.webPush=true
to the ./src/main/resources/vaadin-featureflags.properties
file.
Understanding Web Push
Web push notifications can be used to deliver real-time updates, reminders, and other important messages.
When a user registers for web push notifications, their browser contacts a third-party web push server hosted by the browser vendor.
Set Up Web Push
To set up a web push, you’ll have to do a few things: generate proper keys; configure for needed dependencies; generate a service worker (i.e., PWA resources); and create the WebPushService. You’ll also want to add the ability for others to register for the push service, and configure for sending notifications to those subscribers. These steps are covered in the sub-sections here.
VAPID Keys
The first step is to generate a public-private VAPID key pair.
This can be done by executing the following from the command-line:
npx web-push generate-vapid-keys
That should return an output that looks something like this:
=======================================
Public Key:
BO66S__WPMwVXKBXEh1OiQrM--9pSGXwxqAWQudqmcv41RcWgk1ssUeItv4_ArqkJwPBtayUR4
Private Key:
VNlfcVVFB4V50tqKO8WFHHOhx_ZrabUkZ2BYVOnNg9A
=======================================
After you generate a pair of keys, they can be added to the application.properties
file or set as environment variables.
For the examples here, the property names used are public.key
, private.key
and subject
.
Note
|
The subject should be given as a url or mailto: since Apple web push server requires this.
|
Web Push Dependencies
For web push usage, the project needs to have the Flow WebPush
dependency. Add the following to your pom.xml
file:
<dependencies>
<!-- Other application dependencies -->
<dependency>
<groupId>com.vaadin</groupId>
<artifactId>flow-webpush</artifactId>
</dependency>
</dependencies>
Generate Service Worker
Using Web Push methods, Vaadin provides the @PWA
annotations that automatically generate the required PWA resources. Add the @PWA
annotation on Application.java
as follows:
@SpringBootApplication
@PWA(name = "Vaadin CRM", shortName = "CRM")
public class Application extends SpringBootServletInitializer implements AppShellConfigurator {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
@PWA
enables ServiceWorker creation and web push functionality.
Create WebPushService
Next, you’ll have to create WebPushService for initializing WebPush and storing the user subscriptions.
import jakarta.annotation.PostConstruct;
import nl.martijndwars.webpush.Subscription;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.security.GeneralSecurityException;
import java.util.HashMap;
import java.util.Map;
import com.vaadin.flow.server.webpush.WebPush;
import com.vaadin.flow.server.webpush.WebPushMessage;
@Service
public class WebPushService {
@Value("${public.key}")
private String publicKey;
@Value("${private.key}")
private String privateKey;
@Value("${subject}")
private String subject;
private final Map<String, Subscription> endpointToSubscription = new HashMap<>();
WebPush webPush;
/**
* Initialize security and push service for initial get request.
*
* @throws GeneralSecurityException security exception for security complications
*/
public WebPush getWebPush() {
if(webPush == null) {
webPush = new WebPush(publicKey, privateKey, subject);
}
return webPush;
}
/**
* Send a notification to all subscriptions.
*
* @param title message title
* @param body message body
*/
public void notifyAll(String title, String body) {
endpointToSubscription.values().forEach(subscription -> {
webPush.sendNotification(subscription, new WebPushMessage(title, body));
});
}
private Logger getLogger() {
return LoggerFactory.getLogger(WebPushService.class);
}
public void store(Subscription subscription) {
getLogger().info("Subscribed to {}", subscription.endpoint);
/*
* Note, in a real world app you'll want to persist these
* in the backend. Also, you probably want to know which
* subscription belongs to which user to send custom messages
* for different users. In this demo, we'll just use
* endpoint URL as key to store subscriptions in memory.
*/
endpointToSubscription.put(subscription.endpoint, subscription);
}
public void remove(Subscription subscription) {
getLogger().info("Unsubscribed {}", subscription.endpoint);
endpointToSubscription.remove(subscription.endpoint);
}
public boolean isEmpty() {
return endpointToSubscription.isEmpty();
}
}
Adding Push Registration
The last step is to add the ability to register for the push service.
Flow contains the WebPushRegistration
class that can be used to handle registering and deregistering of web push on the client. The WebPushRegistration needs the VAPID public key on construction.
The UI components for this can be two buttons: one for registering; and one for deregistering notifications.
WebPush webpush = webPushService.getWebPush();
Button subscribe = new Button("Subscribe");
Button unsubscribe = new Button("UnSubscribe");
subscribe.setEnabled(false);
subscribe.addClickListener(e -> {
webpush.subscribe(subscribe.getUI().get(), subscription -> {
webPushService.store(subscription);
subscribe.setEnabled(false);
unsubscribe.setEnabled(true);
});
});
unsubscribe.setEnabled(false);
unsubscribe.addClickListener(e -> {
webpush.unsubscribe(unsubscribe.getUI().get(), subscription -> {
webPushService.remove(subscription);
subscribe.setEnabled(true);
unsubscribe.setEnabled(false);
});
});
In cases where there exists a subscription on the client for the application, but it’s been lost on the server, it can be obtained from the service worker.
@Override
protected void onAttach(AttachEvent attachEvent) {
UI ui = attachEvent.getUI();
pushApi.subscriptionExists(ui, registered -> {
subscribe.setEnabled(!registered);
unsubscribe.setEnabled(registered);
if(registered && webPushService.isEmpty()) {
pushApi.fetchExistingSubscription(ui, webPushService::store);
}
});
}
Sending Notifications
The WebPushService
had the methods sendNotification(subscription, messageJson)
and notifyAll(title, body)
.
Sending a message to all registered subscribers using the notifyAll()
method would look like this:
TextField message = new TextField("Message");
Button broadcast = new Button("Broadcast message");
broadcast.addClickListener(e ->
webPushService.notifyAll("Message from administration", message.getValue())
);
For using sendNotification
, the correct user subscription is needed. You can find source code for the examples on GitHub.
You can also find source code for a CRM example with database usage on crm-tutorial.
Caution
|
Brave Browser Support
For the Brave browser, web push notifications may work by default, when the browser is first installed. If not, notifications need to be enabled in the browser. Inform the user to open their browser privacy settings (i.e., |
Caution
|
iOS & iPadOS Support
Mobile Web Push for iOS and iPadOS requires the following:
For iOS and iPadOS, the registration needs to happen in the installed web application. Also, Safari needs the web push notification features enabled. To do this, go to Settings → Safari → Advanced → Experimental Features. There you can enable |
Note
|
Mobile Notifications
Mobile devices require the site to be served through |
AA0C567E-EEC6-4CEB-95FA-D9D96666D98F