There are scenarios when our users expect an app to react automatically during disconnection. If they do something in the app while they are offline, the system resumes the state when a connection is available. In these cases, Background Sync is useful to us.
Think, for a second, about a chat app. As a user, I hope that, if I have a few spare seconds, I can see the messages I get from friends and have time to answer each message, close the app, then continue on with my life. However, what if I’m in a place with flaky or no connectivity? I probably can’t answer the messages because, if I try, a blank page or a loader or an error message will show up (if I’m using a regular web app). However, with Background Sync in our PWA, users can answer messages and let the app handle them when connection returns to the device.
Using Background Sync
Background Sync, combined with a service worker, grants us great power in building PWAs. Using Background Sync is relatively easy. We simply register our event with SyncManager and react to the sync event:
navigator.serviceWorker.ready
.then(function(registration){
registration.sync.register('my-event-sync');
});
After we register our event, we can use it from our service worker in the sync event:
self.addEventListener("sync",
function(event) {
if (event.tag === "my-event-sync") {
// The logic of our event goes here.
}
}
);
SyncManager
SyncManager is the service worker API that handles and registers synchronized events (Figure 7-1).
A sync event is triggered when
A sync event is registered
When the user goes from offline to online
Every few minutes if there is a registry that has not been completed successfully
To work with SyncManager, we use three things: the event tags, the getTags() method, and the lastChance() method.
Event Tags
The event tags allow us to register events in SyncManager. They are unique and, if they are repeated, SyncManager ignores them.
self.addEventListener('sync', function(event) {
cont tag = event.tag
});
Obtaining the Event List
We can obtain all registered events in SyncManager using the getTags() method, which returns a promise:
In some cases, SyncManager decides to discard an event that has failed multiple times. However, when this occurs, SyncManager allows you to react to this event using the lastChance() method:
self.addEventListener('sync', function(event) {
console.log('I am in sync', event.tag);
if(event.lastChance) {
// Do something; last opportunity.
}
});
As you can see, Background Sync is pretty easy to use, and it opens an infinite world of possibilities to add a positive experience for our users. Also, we can mix it with IndexedDB and make our apps more reliable. Continuing with the chat app example I mentioned at the beginning of the chapter, we can do something like this:
console.log('Service workers are not supported.');
}
</script>
<script src="./indexedDB.js"></script>
<script src="./app.js"></script>
</body>
</html>
Next, we simulate a chat app. We add reliability by saving messages in a queue in IndexedDB, then send some messages to a fake API (see https://jsonplaceholder.typicode.com/guide.html for more information). When the response is successful, we delete the messages from the queue; otherwise, we try sending them later with Background Sync (Figure 7-2).
We need a basic IndexedDB implementation to handle the queue logic.
In the service worker in the previous code, note the inclusion of importScripts() (for more information, go to https://developer.mozilla.org/en-US/docs/Web/API/WorkerGlobalScope/importScripts). With it, we can find libraries and other scripts in the service worker scope—in our case, the indexedDB.js script. We separate our code using syncMessages(), where we get all the messages in our queue, then call to solveMessages(), where we use fetch() to make a request to our API. If this call is successful, we delete the messages from our queue.
Last, we need to add functionality to our app.
App.js
let button = document.getElementById('myButton');
const messagesToSend = [
{
title: 'new Message',
body: 'Hello there',
userId: 1
},
{
title: 'new Message 2',
body: 'Hello there again',
userId: 1
},
{
title: 'new Message 3',
body: 'Hello there again again',
userId: 1
},
{
title: 'new Message 4',
body: 'Hello there again 4 times',
userId: 1
}
];
button.addEventListener('click', function(){
messagesToSend.forEach(
(item) => {
sendMessage(item);
}
);
messageQueueSync();
});
// Background Sync Mechanism Functions
function sendMessage(message) {
let messageItem = {
title: message.title,
body: message.body,
userId: message.userId
};
openDatabase()
.then(() => {
writingObjectStore(messageItem);
})
.catch((e)=>{console.log(e)});
}
function messageQueueSync() {
navigator.serviceWorker.ready.then(
(registration) => {
registration.sync.register('message-queue');
}
);
}
In the previous code, we simulated sending four messages and continuing the process with our queue (Figure 7-3).
Now let’s create a notification that shows up when all the pending POST requests are resolved, which is handled by workbox.backgroundSync.Plugin() in the queueDidReplay callback.
In addition, we need to use the NetworkOnly() method because our API request call and and we are using workbox.routing. registerRoute(//*/, networkWithBackgroundSync, "POST"); to catch all the POST calls.
Then we add addtoApiNote() in Dashboard.vue to simulate a call to an API.
Figure 7-10 shows that Background Sync detects that the API calls were unsuccessful and saves then in IndexedDB.
Now let’s turn on the computer network (Figure 7-11).
Figure 7-12 shows the notification and the empty queue.
Summary
Background Sync, combined with a service worker, grants us the ability to build PWAs. Using Background Sync is relatively easy. We simply register our event with SyncManager and react to the sync event. SyncManager is the service worker API that handles and registers synchronized events.