Background Sync API: Powering Superior Offline Web App Interactions

As a web developer, I understand how frustrating it can be when users struggle with unreliable network connections. You invest countless hours building an amazing web app, only for users to encounter slow loading times or lose their data due to poor connectivity. It's disheartening, to say the least.

You're not alone in this struggle, and it's a problem that affects many web applications. But imagine if there was a way to make your web app run smoothly, regardless of network conditions, and provide a consistent user experience that keeps your users engaged and satisfied.

Thankfully, there's a solution that addresses these issues—the Background Sync API. This powerful browser feature allows your web app to synchronize data in the background, making sure your users enjoy a seamless experience, even with intermittent network connections. For example, think about a note-taking app that automatically saves users' notes in the background whenever network connectivity is restored, ensuring that no data is lost.

Over the past few years I have had countless requests to build applications that can work offline and synchronize. To accomplish this I have used a combination of the Background Sync API and custom caching and synchronization solutions.

The Web Background Sync API

By leveraging the Background Sync API and its capabilities, you'll be able to create web apps that perform better and provide a more reliable user experience, no matter the network conditions. And in the end, that's what matters most—happy, satisfied users who keep coming back to your app.

Understanding the Background Sync API

The Background Sync API is a browser feature that allows web applications to defer actions, such as sending data to a server, until the device has a stable network connection. This is especially useful for progressive web apps (PWAs), where users expect a native-like experience regardless of their network conditions. Like many other APIs, Background Sync does require HTTPS.

There are two types of background sync:

  • One-off sync: This is a single synchronization event triggered once the device regains network connectivity.
  • Periodic sync: This type of sync occurs at specific intervals, even when the app is not in use.

Getting Started: Registering a Service Worker

Before using the Background Sync API, you need to register a service worker, which is a JavaScript file that runs in the background, separate from your web app's main thread. Service workers enable advanced features like background synchronization and push notifications. To register a service worker, add the following code to your main JavaScript file:


if ('serviceWorker' in navigator) {

  try {

    const registration = await navigator.serviceWorker.register('/service-worker.js');

    console.log('Service Worker registered with scope:', registration.scope);

  } catch (error) {

    console.log('Service Worker registration failed:', error);

  }

}

Implementing One-Off Sync

To implement one-off sync, follow these steps:

  • Request permission: Before using the Background Sync API, you must request permission from the user. Use the following code to request permission:

async function requestBackgroundSyncPermission() {

  const permission = await navigator.permissions.query({name: 'background-sync'});

  if (permission.state === 'granted') {
    return true;
  }

  return false;

}
  • Register a sync event: Once permission is granted, register a sync event in your service worker file:

self.addEventListener('sync', event => {

  if (event.tag === 'mySyncEvent') {

    event.waitUntil(syncData());

  }

});
  • Define the syncData function: Create a function called syncData that will handle the actual data synchronization when network connectivity is restored:

async function syncData() {
  // Your data synchronization logic goes here.
}

In the syncData function, developers will typically handle tasks such as:

  1. Retrieving stored data: When network connectivity is lost, you'll want to store user data locally (e.g., using IndexedDB, localStorage, or other client-side storage options) until connectivity is restored. In the syncData function, you'll need to retrieve this stored data to prepare it for synchronization.

  2. Making API calls: To synchronize the data with your server, you'll likely need to make API calls using the Fetch API or other methods like XMLHttpRequest. For each piece of data you need to sync, you'll send a request to your server, which should handle the data processing and storage on its end.

  3. Handling server responses: After making API calls, you'll want to handle server responses to ensure the synchronization was successful. This might involve checking for specific response codes or examining the response body for errors. If there are issues with the sync, you may want to implement error handling or retries.

  4. Updating local data: Once the data is successfully synced with the server, you may need to update the local data to reflect the server's current state. This could involve marking the data as synced or removing it from local storage.

Here's a more detailed example of what the syncData function might look like:


async function syncData() {

  // Retrieve locally stored data
  const storedData = await getStoredData();

  // Iterate through the data and make API calls to sync with the server
  for (const item of storedData) {

    try {

      const response = await fetch('/api/sync', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json'
        },
        body: JSON.stringify(item)

      });

      if (response.ok) {

        // Update local data if sync is successful
        await markDataAsSynced(item);

      } else {

        console.error('Sync failed for item:', item);

      }

    } catch (error) {

      console.error('Error syncing item:', error);

    }

  }

}

In this example, we first retrieve the locally stored data using the getStoredData function. Then, for each item in the stored data, we make a POST request to the /api/sync endpoint with the item as the request body. If the response is successful (i.e., the response.ok property is true), we mark the data as synced using the markDataAsSynced function. If there's an error or the sync fails, we log the corresponding error message.

In the sendData function, the goal is to initiate the synchronization process when necessary, such as when the user saves data or performs an action that requires data synchronization. The function follows these steps:

  • Check for background sync permission: First, we need to ensure that the user has granted permission to use background sync. This is achieved by calling the requestBackgroundSyncPermission function, which returns true if permission is granted and false otherwise.

  • Get the service worker registration: If background sync permission is granted, we need to obtain the service worker registration to access the sync object. We do this by awaiting the navigator.serviceWorker.ready promise, which resolves to the service worker registration.

  • Register the sync event: With the service worker registration in hand, we can now register the sync event by calling registration.sync.register('mySyncEvent'). This tells the browser to trigger the 'mySyncEvent' sync event when network connectivity is restored. The event is then handled by the service worker, which should have a listener for the 'sync' event, as described in previous sections.
  • Fallback for when permission is not granted: In case the user has not granted background sync permission, you should implement a fallback mechanism to handle data synchronization. This could involve making direct API calls in the absence of background sync or notifying the user about the lack of synchronization functionality.

Here's the expanded sendData function:


async function sendData() {

  if (await requestBackgroundSyncPermission()) {

    const registration = await navigator.serviceWorker.ready;

    registration.sync.register('mySyncEvent');

  } else {
    // Fallback for when background sync permission is not granted.
    // You can implement direct API calls here or notify the user.
  }

}

// Example usage: Trigger sendData when the user clicks a "Save" button
document.getElementById('saveButton').addEventListener('click', () => {

  sendData();

});

In this example, we've also added an event listener to a hypothetical "Save" button that triggers the sendData function when clicked. This demonstrates how you might initiate the synchronization process in response to user actions.

Implementing Periodic Sync

Periodic sync is currently an experimental feature, only supported in Chromium-based browsers (Chrome/Edge/Samsung and a few others). To implement periodic sync, follow these steps:

  1. Request permission: Request permission for both 'background-sync' and 'periodic-background-sync':

async function requestPeriodicSyncPermission() {

  const backgroundSyncPermission = await navigator.permissions.query({name: 'background-sync'});
  const periodicSyncPermission = await navigator.permissions.query({name: 'periodic-background-sync'});

  if (backgroundSyncPermission.state === 'granted' && periodicSyncPermission.state === 'granted') {

    return true;

    }

    return false;

}
  1. Register a periodic sync event: Once permission is granted, register a periodic sync event in your service worker file:

self.addEventListener('periodicsync', event => {

  if (event.tag === 'myPeriodicSyncEvent') {
    event.waitUntil(syncPeriodicData());
  }

});
  • Define the syncPeriodicData function: Create a function called syncPeriodicData that will handle the data synchronization at specified intervals:

async function syncPeriodicData() {
  // Your periodic data synchronization logic goes here.
}
  • Register a periodic sync: Finally, register the periodic sync from your main JavaScript file when needed:

async function registerPeriodicSync() {

  if (await requestPeriodicSyncPermission()) {

    const registration = await navigator.serviceWorker.ready;

    registration.periodicSync.register('myPeriodicSyncEvent', {
      minInterval: 24 * 60 * 60 * 1000 // 24 hours in milliseconds
    });

  } else {

    // Fallback for when periodic sync permission is not granted.

  }

}

Testing and Debugging

To test and debug your implementation of the Background Sync API, you can use the following tools:

  • Chrome/ DevTools: Open the Application panel in Chrome or DevTools to inspect your service worker, background sync events, and periodic sync registrations.
  • Microsoft Edge DevTools: Similar to Chrome DevTools, Microsoft Edge DevTools also offers tools for inspecting service workers and background sync events in the Application panel.

Browser Support for the Background Sync API

Before implementing the Background Sync API, it's essential to be aware of the current browser support landscape. This will help you understand which browsers your users can expect to see the benefits of background sync and where you may need to provide fallback solutions.

As of publishing this article, March 2023, the Background Sync API has varying support across major browsers:

Google Chrome: Chrome has full support for the Background Sync API, starting from version 49 onwards.

Microsoft Edge: Edge, based on the Chromium engine, also supports the Background Sync API. Full support is available from version 79 onwards.

Mozilla Firefox: Unfortunately, Firefox does not support the Background Sync API at this time. You should implement a fallback mechanism to handle data synchronization in Firefox. It looks like they will be shipping support in the near future.

Apple Safari: Safari also lacks support for the Background Sync API, both on macOS and iOS. As with Firefox, you'll need to provide an alternative method for data synchronization in Safari.

Opera: Opera, another browser built on the Chromium engine, supports the Background Sync API. Full support is available from version 36 onwards.

It's important to note that browser support may change over time. To stay up-to-date with the latest information on browser support, you can regularly check the Can I Use website (https://caniuse.com/background-sync) or the MDN Web Docs (https://developer.mozilla.org/en-US/docs/Web/API/Background_Sync_API).

Given the limited browser support for the Background Sync API, it's crucial to implement fallback solutions for users on unsupported browsers. This could involve making direct API calls when the user performs specific actions or providing visual feedback about the lack of synchronization functionality. By offering alternative options, you can ensure that users on all browsers have a consistent and functional experience with your web app.

Conclusion

The Background Sync API is a powerful tool for enhancing the performance and reliability of your web applications. By implementing one-off and periodic sync events, you can provide a seamless experience for users even when they have poor or intermittent network connectivity. With a good understanding of the API and the ability to implement it effectively, you can elevate your web app to a new level of functionality and user satisfaction.

Now test yourself with these FAQs about the Background Sync API:

Q: Are there any limitations or restrictions when using the Background Sync API?

A: Yes, the Background Sync API is subject to certain limitations and restrictions to protect user privacy and conserve device resources. For example, background sync events may be delayed or throttled in situations such as low battery, limited network connectivity, or when the user has not interacted with your web app recently.

Q: How can I check if the Background Sync API is supported by the user's browser?

A: To check if the Background Sync API is supported, you can use feature detection by checking if the SyncManager property exists in the ServiceWorkerRegistration prototype:


if ('SyncManager' in ServiceWorkerRegistration.prototype) {
  // Background Sync API is supported
} else {
  // Background Sync API is not supported
}

Q: Can the Background Sync API be used with other web technologies like IndexedDB or Web Storage?

A: Absolutely! The Background Sync API can be used in conjunction with other web technologies like IndexedDB or Web Storage to manage and store data locally while offline. When network connectivity is restored, the Background Sync API can then be used to synchronize the local data with your server.

Q: Are there any alternatives to the Background Sync API for synchronizing data when network connectivity is unavailable?

A: If the Background Sync API is not supported or suitable for your use case, you can consider implementing your own custom synchronization logic using JavaScript and other web APIs. For example, you can use the Network Information API to detect network connectivity changes and trigger data synchronization when the user regains connectivity.

Q: How can I unregister a sync event if I no longer need it?

A: To unregister a sync event, you first need to obtain a reference to the SyncManager object by calling the sync property on the ServiceWorkerRegistration object:


const registration = await navigator.serviceWorker.ready;
const syncManager = registration.sync;

Then, you can call the getTags() method on the SyncManager object to retrieve an array of registered sync event tags. If the tag you want to unregister is in the array, you can call the unregister() method on the SyncManager object with the desired tag:


const tags = await syncManager.getTags();
const tagToUnregister = 'mySyncEvent';

if (tags.includes(tagToUnregister)) {
  await syncManager.unregister(tagToUnregister);
}


Love2Dev
We specialize in Progressive Web Apps and Web Performance Optimization