Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Timer with notifications using service worker

I want to create a timer, which starts the countdown on notification click.

My sample works well as long as the break length is less than one minute. When the break length is longer than a minute, service worker stops working.

index.html

<!DOCTYPE html>
<html>
<body>
<button onclick="notify();">notify</button>
</body>
<script>
  function notify() {
    const items = ["First", "End of the break", "Second"];
    const options = { data: items };

    if ('serviceWorker' in navigator) {
      Notification.requestPermission()
        .then(() => navigator.serviceWorker.register('sw.js'))
        .then(() => navigator.serviceWorker.ready
        .then((s) => {
          s.showNotification('Click to begin', options);
        }))
      .catch(e => console.log(e.message));
    }
  }
</script>
</html>

sw.js

let data = [];
let num = 0;

self.onnotificationclick = (event) => {
  event.notification.close();
  if (event.notification.data) {
    ({ data } = event.notification);
  }

  if (num < data.length) {
    setTimeout(() => {
      self.registration.showNotification(data[num]);
      num += 1;
    }, num % 2 === 0 ? 1 : 3000);
  }
};

I am aware that there should be no setTimeout in sw.js, but it's the closest to the point I want to achieve.

Is there a way to build a timer that starts counting down when you click a notification and notifies you about the end of countdown?

like image 974
JakubKus Avatar asked Nov 06 '18 19:11

JakubKus


2 Answers

(This is a long answer as there are a few things to discuss with your question)

The way service workers are designed is that you should do your work as quickly as possible and return control over to the browser. This is done by via the event.waitUntil(<promise>) function where you tell the browser you are busy by not resolving the promise you pass in.

In your above example, you aren't using event.waitUntil() which could prolong the live of a service worker.

For example, you could try:

self.onnotificationclick = (event) => {
  event.notification.close();
  if (event.notification.data) {
    ({ data } = event.notification);
  }

  if (num < data.length) {
    event.waitUntil(new Promise(function(resolve, reject) {
        setTimeout(() => {
            self.registration.showNotification(data.num);
            resolve();
        }, 3000);
    })
  }
};

However if you set a timeout that is deemed too long (and it's up to the browser to decide what is too long), the browser can terminate the service worker, resulting in an unpredictable experience for your users.

This is because your intended use case is generally not what the service worker API is designed for. There were proposals for alternative API's, but not of them gained traction (or at least not that I'm aware of - although that was a while a go).

Another note: The use of global variables in a service worker should be used with caution. Like I said, a service worker can be started and terminated frequently, so reading / writing from indexDB may be more appropriate for storing information.

like image 163
Matt Gaunt Avatar answered Nov 06 '22 23:11

Matt Gaunt


Notification Trigger API

With respect to longer duration timers, there is currently (April 2021) work being done on the Notification Trigger API.

Comments for the Google developers can be left as an issue in the dedicated GitHub repository.

Feedback from the Chrome M80-M83 Origin Trial is also available.

Currently, there is a need to set the #enable-experimental-web-platform-features flag in chrome://flags for it to work. As soon as this API becomes stable and mainstream, I will update this answer with code.

Finally, there are more examples and tests in this excellent overview about task scheduling in the service worker. This also deals with the Periodic Background Sync API.

like image 43
Serge Stroobandt Avatar answered Nov 07 '22 00:11

Serge Stroobandt