Skip to content

Conversation

caalador
Copy link
Contributor

@caalador caalador commented Jun 9, 2023

Add API to use initialize web push on the client from the server.

Related-to vaadin/platform#4037

Fixes #14606

@github-actions
Copy link

github-actions bot commented Jun 9, 2023

Test Results

   998 files  ±0     998 suites  ±0   1h 2m 44s ⏱️ +3s
6 342 tests ±0  6 301 ✔️ ±0  41 💤 ±0  0 ±0 
6 601 runs  +1  6 553 ✔️ +1  48 💤 ±0  0 ±0 

Results for commit c94cdeb. ± Comparison against base commit 98803fa.

♻️ This comment has been updated with latest results.

@mshabarov mshabarov self-requested a review June 12, 2023 09:06
@mshabarov
Copy link
Contributor

This implementation doesn't have an API for sending messages yet.
We agreed on some of the meetings few weeks back that we integrate the functionality given in POC https://github.com/marcushellberg/vaadin-flow-web-push-notifications to the Flow.

Here are the criteria:

  • Flow provides an API for subscribing/unsubscribing users for/from getting notifications, e.g. user click on the "Subscribe" button and server subscribed him to notification service with this API. - Done in WebPushRegistration
  • Flow should provide an information (e.g. subscription ID) of the subscribed user and provide API for getting this info, so that developers can persist it on their backend. - Done in WebPushSubcriptionResponse
  • Flow provides an API to send push notifications from server to browsers to a certain users by a given IDs. - Absent here, we need to integrate PushService
  • Flow provides an API to set the following information to the notification: title and body message. - Absent

We can maybe introduce a single point for subscribing/unsubscribing and sending notifications.
Here below I put some very quick examples how API can looks like:

Main class:

public class WebPush {

        // singleton instances of WebPush and PushService
        public static WebPush getInstance(Keys keys) {
            // initialises PushService and WebPush lazily and return WebPush instance
        }

        public void sendNotification(WebPushRegistration.WebPushSubscription subscription, String messageJson) {
            // delegates to pushService.send()
        }

        public static WebPushRegistration subscribe(UI ui,
                                                    WebPushRegistration.WebPushSubcriptionResponse callback) {
            return new WebPushRegistration(ui, callback);
        }
    }

"record" class for keys:

    public class Keys {
        public static Keys of(String publicKey, String privateKey, String subject) {
            return new Keys(publicKey, privateKey, subject);
        }
    }

Example how to send messages from admin page:

    @Route("admin")
    public class AdminView extends VerticalLayout {

        private final WebPush webPush;

        public AdminView(SubscriptionService subscriptionService) {
            Keys keys = Keys.of("public key", "private key",
                    "[mailto:mikhail@vaadin.com]");

            webPush = WebPush.getInstance(keys);

            NativeButton broadcast = new NativeButton("Broadcast", click -> {
                Collection<WebPushRegistration.WebPushSubscription> subscriptions =
                        subscriptionService.getAll();

                subscriptions.forEach(s -> {
                    webPush.sendNotification(s, "Hello all");
                });
            });

            NativeButton sendToUser = new NativeButton("Send to user",
                    click -> {
                WebPushRegistration.WebPushSubscription subscription =
                        subscriptionService.getByUsername(user);

                if (subscription != null) {
                    webPush.sendNotification(subscription, "Hello User " + user);
                }
            });

            add(broadcast, sendToUser);
        }
    }

Example for subscription/unsubscription on the user page:

    // inside some view class

    private SubscriptionService subscriptionService;
    private WebPushRegistration registration;

    @Override
    protected void onAttach(AttachEvent attachEvent) {
        super.onAttach(attachEvent);

        NativeButton subscribeButton = new NativeButton("Subscribe", click -> {
            registration = WebPush.subscribe(attachEvent.getUI(), subscription -> {
                subscriptionService.subscribe(subscription);
            });
        });

        NativeButton unsubscribeButton = new NativeButton("Unsubscribe",
                click -> {
            if (registration != null) {
                registration.unsubscribeWebPush(attachEvent.getUI(), subscription -> {
                            subscriptionService.unsubscribe(subscription);
                        });
            }
        });
    }

And finally a subscription service:

    private interface SubscriptionService {
        void subscribe(WebPushRegistration.WebPushSubscription subscription);
        void unsubscribe(WebPushRegistration.WebPushSubscription subscription);
        Collection<WebPushRegistration.WebPushSubscription> getAll();
        WebPushRegistration.WebPushSubscription getByUsername(User user);
    }

The way how Flow stores and provides an instances of WebPush is still discussable.

@mshabarov
Copy link
Contributor

WebPush can be created explicitly as WebPush webPush = new WebPush(keys) and then for DI frameworks it can be made as a singleton managed bean, e.g. for Spring:

    @Bean(name = "WebPush")
    public WebPush getWebPush() { 
        // keys are injected with AtValue
        return new WebPush(keys);
    }

@mshabarov
Copy link
Contributor

mshabarov commented Jun 15, 2023

Pushed an extension for WebPushRegistration regarding sending notifications.

Also updated base web push example here to showcase the API.

@vaadin-bot vaadin-bot added +1.0.0 and removed +0.1.0 labels Jun 15, 2023
Copy link
Contributor

@mshabarov mshabarov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have some further suggestions, let's discuss.

@mshabarov
Copy link
Contributor

Looking into possible further improvements of Web Push API (outside of this pull request), I made a short analysis of what we might need in the future:

  • Notifications might have a link to give users opportunity to navigate to a particular page/content on the web site/mobile app relevant to this notification, so when the user clicks on the notification, browser redirects to a page containing relevant content.

  • Updating or removing pending notifications, so that user can see one notification with: "You have 3 unread messages". The ability to replace a notification: replace "You have 3 unread messages" with "You have 7 unread messages" instead of adding a new notification for each message.

    • doesn't need a new Java API, but could be an implementation change
  • Dropdown component shown on the notification body with customisable list of clickable links.

    • Java API needs a parameter containing a list of links
  • API for seeing whether sending notification is available for the user (feature detection in browsers, allowed or blocked by user)

  • The ability to clear the pending notifications in browser

So it seems like we'll need a new methods in WebPushRegistration or new fields in the Message object, and this can be added iteratively.

@caalador
Copy link
Contributor Author

Permission check is avaliable in the registration script, but doesn't have a server method.

@vaadin-bot vaadin-bot added +0.1.0 and removed +1.0.0 labels Jun 16, 2023
@vaadin-bot vaadin-bot added +1.0.0 and removed +0.0.1 labels Jun 21, 2023
@caalador caalador requested a review from mshabarov June 21, 2023 08:50
@vaadin-bot vaadin-bot added +0.0.1 and removed +1.0.0 labels Jun 21, 2023
@caalador caalador marked this pull request as ready for review June 21, 2023 10:07
Copy link
Contributor

@mshabarov mshabarov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some minor comments about javadoc and feature flags.
About testing: unit tests are not much interesting for this feature, because half of the feature is in the browser.
We need an end-to-end test, but need a discussion how properly set up the testing environment.

@sonarqubecloud
Copy link

Kudos, SonarCloud Quality Gate passed!    Quality Gate passed

Bug A 0 Bugs
Vulnerability A 0 Vulnerabilities
Security Hotspot A 0 Security Hotspots
Code Smell A 0 Code Smells

No Coverage information No Coverage information
No Duplication information No Duplication information

@vaadin-bot
Copy link
Collaborator

This ticket/PR has been released with Vaadin 24.2.0.alpha1 and is also targeting the upcoming stable 24.2.0 version.

mcollovati added a commit that referenced this pull request Apr 9, 2025
This commit is a backport of #16994 (limited to PWA icon handling) and #14607

Fixes #21253
mcollovati added a commit that referenced this pull request Apr 9, 2025
This commit is a backport of #16994 (limited to PWA icon handling)

Fixes #21253
caalador pushed a commit that referenced this pull request Apr 10, 2025
This commit is a backport of #16994 (limited to PWA icon handling)

Fixes #21253
caalador pushed a commit that referenced this pull request Apr 10, 2025
This commit is a backport of #16994 (limited to PWA icon handling) and #14607

Fixes #21253
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

PWARegistry slows down application startup
4 participants