Search code examples
service-workerworkboxquasar-frameworkbackground-sync

How can I detect if client has Background Sync support from the Service Worker?


How can I detect whether the user's browser has Background Sync support from within the service worker?

I know how to detect for this on the app side, but not from within the service worker itself.

Basically I'm using Workbox's Background Sync and it's all working great in Chrome and Chrome Mobile.

I know Workbox has a Background Sync "fallback" for browsers that don't support Background Sync, but this not working very well, so I would rather just disable Background Sync completely for browsers which don't support it.

Here is my service worker file for reference. You can see there are 3 blocks of code that I don't want to execute if the browser doesn't support background sync (I've marked these with *** I ONLY WANT TO RUN THIS CODE IF BACKGROUND SYNC IS SUPPORTED ***):

/*
 * This file (which will be your service worker)
 * is picked up by the build system ONLY if
 * quasar.conf > pwa > workboxPluginMode is set to "InjectManifest"
 */

/*
  config
*/

workbox.setConfig({ debug: false }) // disable workbox console logs
workbox.precaching.precacheAndRoute(self.__precacheManifest || [])

/*
basic routes
*/

workbox.routing.registerRoute(
  /^https:\/\/firestore.googleapis.com/,
  new workbox.strategies.NetworkFirst(),
  'GET'
);

workbox.routing.registerRoute(
  'https://myapp.com/posts',
  new workbox.strategies.NetworkFirst(),
  'GET'
);

workbox.routing.registerRoute(
  'https://myapp.com/favorites',
  new workbox.strategies.NetworkFirst(),
  'GET'
);

workbox.routing.registerRoute(
  /^http/,
  new workbox.strategies.StaleWhileRevalidate(),
  'GET'
);

/*
posts queue
*/

// *** I ONLY WANT TO RUN THIS CODE IF BACKGROUND SYNC IS SUPPORTED ***
const postsQueue = new workbox.backgroundSync.Queue('postsQueue', {
  maxRetentionTime: 24 * 60 * 365, // Retry for max of one year
  onSync: async ({queue}) => {
    let entry;
    while (entry = await queue.shiftRequest()) {
      try {
        await fetch(entry.request);
        // console.log('Replay successful for request', entry.request);

        const clients = await self.clients.matchAll({type: 'window'});
        for (const client of clients) {
          client.postMessage({
            msg: "offline-post-uploaded"
          })
        }        
      } catch (error) {
        console.error('Replay failed for request', entry.request, error);

        // Put the entry back in the queue and re-throw the error:
        await queue.unshiftRequest(entry);
        throw error;
      }
    }
    // console.log('Replay complete!');
  }
})

/*
update post queue
*/

// *** I ONLY WANT TO RUN THIS CODE IF BACKGROUND SYNC IS SUPPORTED ***
const updatePostQueue = new workbox.backgroundSync.Queue('updatePostQueue', {
  maxRetentionTime: 24 * 60 * 365, // Retry for max of one year
  onSync: async ({queue}) => {
    let entry;
    while (entry = await queue.shiftRequest()) {
      try {
        await fetch(entry.request);
        console.log('Replay successful for request', entry.request);     
      } catch (error) {
        console.error('Replay failed for request', entry.request, error);

        // Put the entry back in the queue and re-throw the error:
        await queue.unshiftRequest(entry);
        throw error;
      }
    }
    // console.log('Replay complete!');
  }
})

/*
events
*/

// *** I ONLY WANT TO RUN THIS CODE IF BACKGROUND SYNC IS SUPPORTED ***
self.addEventListener('fetch', (event) => {
  // console.log('event.request.url: ', event.request.url)
  if (event.request.url == 'https://myapp.com/createPost') {
    console.log('SW fetch createPost')
    // Clone the request to ensure it's safe to read when
    // adding to the Queue.
    if (!self.navigator.onLine) {
      console.log('SW device is offline')
      const promiseChain = fetch(event.request.clone()).catch((err) => {
        return postsQueue.pushRequest({request: event.request});
      });

      event.waitUntil(promiseChain);
    }
    else {
      console.log('SW device is online')
    }
  }
  else if (event.request.url.startsWith('https://myapp.com/updatePost')) {
    // Clone the request to ensure it's safe to read when
    // adding to the Queue.

    if (!self.navigator.onLine) {
      const promiseChain = fetch(event.request.clone()).catch((err) => {
        return updatePostQueue.pushRequest({request: event.request});
      }); 
      event.waitUntil(promiseChain);
    }
  }
});

self.addEventListener('notificationclick', event => {
  let notification = event.notification
  let action = event.action

  event.waitUntil(
    clients.matchAll()
      .then(clis => {
        let client = clis.find(cli => {
          return cli.visibilityState === 'visible'
        })

        if (client !== undefined) {
          client.navigate(notification.data.openUrl)
          client.focus()
        }
        else {
          clients.openWindow(notification.data.openUrl)
        }
        // notification.close()
      })
  )
})

self.addEventListener('notificationclose', event => {
  console.log('Notification was closed', event)
})

self.addEventListener('push', event => {
  console.log('Push Notification received: ', event)

  if (event.data) {
    console.log('event.data: ', event.data)
    let data = JSON.parse(event.data.text())
    console.log('data: ', data)

    console.log('showNotificationYo!')
    event.waitUntil(
      self.registration.showNotification(
        data.title,
        {
          body: data.body,
          icon: 'statics/icons/icon-128x128.png',
          badge: 'statics/icons/icon-128x128.png',
          image: data.imageUrl,
          data: {
            openUrl: data.openUrl
          }
        }
      )
    )
  }
})

Solution

  • The feature detection that's used within workbox-background-sync looks like:

    if ('sync' in self.registration) {
      // Background sync is natively supported.
    }
    

    You could do something similar in your own code to determine native support.