DEV Community

Radu Breahna
Radu Breahna

Posted on • Edited on

Browser Notifications API by Example

This post is a part of a series about the ins and outs of chroniker.co

When working on a task, tracking your time is never enough. Pacing is what makes you efficient. An easy way to pace yourself is to periodically check what you've accomplished. Enter the Notification API.

Lately these type of notifications have made their way to almost all major websites. They are easy to implement, they look good, and they grab your attention. On chroniker.co they are used for pacing while also serving as an additional reminder that a timer is turned on.

Alt Text

On KDE they're not as impressive as on macOs or Windows.

While reading the documentation and looking for code samples online I decided to put together a JavaScript class that would wrap the basic functionality and present an easy way to integrate it into any app.
Here is where I'm at:

import Logo from 'assets/img/chroniker.co.png';

class BrowserNotification {
   options = {
     vibrate: [200, 100, 200],
     requireInteraction: false,
     icon: Logo,
     body: 'Sample Text',
   };

  config = {
    title: 'Chroniker',
    timeout: 5000,
  };


  async getRegistration() {
    return navigator.serviceWorker.ready;
  }

  async closeNotifications(timeout = 0) {
    const registration = await this.getRegistration();
    const notifications = await registration.getNotifications();
    setTimeout(() => {
      notifications.map(notification => notification.close());
    }, timeout);
  }

  requestNotificationPermissions = () => {
    const notificationPermissions = async () => {
      const permission = await Notification.requestPermission();
      if (permission === 'granted') {
        await this.closeNotifications();
        const registration = await this.getRegistration();
        await registration.showNotification(this.config.title, this.options);
        await this.closeNotifications(this.config.timeout);
      }
      if (permission === 'denied') {
        alert('Notifications are blocked. Please enable them in your browser settings.');
      }
    };
    notificationPermissions();
  };

  sendNotification = () => {
    const send = async () => {
      if (Notification.permission === 'granted') {
        await this.closeNotifications();
        const registration = await this.getRegistration();
        await registration.showNotification(this.config.title, this.options);
        await this.closeNotifications(this.config.timeout);
      }
    };
    send();
  };
}

export default BrowserNotification;


Enter fullscreen mode Exit fullscreen mode

Now to dissect this piece by piece:

options = {
     vibrate: [200, 100, 200],
     requireInteraction: false,
     icon: Logo,
     body: 'Sample Text',
   };
Enter fullscreen mode Exit fullscreen mode

When you actually push a notification, you can pass an option object as a second argument, here you can specify the notification text, your logo, and some other things, the full list can be found on mdn. I was curious to try the vibrate option which accepts a 'pattern' of impulses that should make some devices vibrate. I haven't gotten this to work yet. Must be doing something wrong.

 config = {
    title: 'Chroniker',
    timeout: 5000,
  };

Enter fullscreen mode Exit fullscreen mode

Next is this attribute that I use to store my app name, and any other values used by this class internally.

async getRegistration() {
    return navigator.serviceWorker.ready;
  }
Enter fullscreen mode Exit fullscreen mode

Everything about these notifications is asynchronous, here we make sure that the service worker has been registered with the browser. It's not strictly necessary to have a registered service worker in order to get these notifications working on a desktop machine, however if you want them to also work on mobile, this seems like the only way now.

Regarding how to implement a service worker, that's a subject for another article.

async closeNotifications(timeout = 0) {
    const registration = await this.getRegistration();
    const notifications = await registration.getNotifications();
    setTimeout(() => {
      notifications.map(notification => notification.close());
    }, timeout);
  }
Enter fullscreen mode Exit fullscreen mode

This method allows us to close all previously deployed notifications. It is advised to do so before sending new ones. This is also the way you would control for how long that popup is displayed on the users screen, hence the timeout.

requestNotificationPermissions = () => {
    const notificationPermissions = async () => {
      const permission = await Notification.requestPermission();
      if (permission === 'granted') {
        await this.closeNotifications();
        const registration = await this.getRegistration();
        await registration.showNotification(this.config.title, this.options);
        await this.closeNotifications(this.config.timeout);
      }
      if (permission === 'denied') {
        alert('Notifications are blocked. Please enable them in your browser settings.');
      }
    };
    notificationPermissions();
  };



//Sample use case 



  const notification = new BrowserNotification();
  notification.options.body = 'Notifications are already enabled. To disable, check your browser settings';

 <Button onClick={notification.requestNotificationPermissions} />



Enter fullscreen mode Exit fullscreen mode

This method is responsible for guiding the user with allowing the app to send notifications, or in case he decided to block them - let him know how to bring them back.

sendNotification = () => {
    const send = async () => {
      if (Notification.permission === 'granted') {
        await this.closeNotifications();
        const registration = await this.getRegistration();
        await registration.showNotification(this.config.title, this.options);
        await this.closeNotifications(this.config.timeout);
      }
    };
    send();
  };
Enter fullscreen mode Exit fullscreen mode

Finally here is the method that actually sends the notification itself. As you can see, first it makes sure to clean the notification stack. Then it sends one out and keeps it on screen until that timeout hits it and it is removed.

Following is the way I use this class on chroniker.co:

import BrowserNotification from 'components/notification';

const notification = new BrowserNotification();

 useEffect(() => {
    if (Math.round(totalTime) !== 0 && (Math.round(totalTime) / 60) % interval === 0) {
      notification.options.body = `${interval} minute(s) passed`;
      notification.sendNotification();
    }
  }, [Math.round(totalTime)]);

Enter fullscreen mode Exit fullscreen mode

On the settings page you will find an input that allows you to specify how often should the app notify you. Above is the actual logic that sends a notification each time the total time can be exactly divided by the users interval. The totalTime and interval variables are extracted from a global state that is not shown here.

That's it for notifications, see you next time!

Further reading:
https://developer.mozilla.org/en-US/docs/Web/API/Notifications_API/Using_the_Notifications_API
https://developer.mozilla.org/en-US/docs/Web/API/Notification

Top comments (0)