简体   繁体   中英

ServiceWorker Update loop - Create react app

I'm trying to implement a popup for the user that tells them that an update is available.

I'm using Create React App and it's default configuration. I'm dispatching an action when it founds an update like so:

index.js

serviceWorker.register({
    onSuccess: () => store.dispatch({ type: SW_UPDATE_FINISHED }),
    onUpdate: reg => store.dispatch({ type: SW_UPDATE, payload: reg })
});

and I have a little component that should take care of showing the message and triggering the update:

const SwUpdate = () => {
  const swUpdate = useSelector(state => state.app.swUpdate);
  const swReg = useSelector(state => state.app.swReg);
  const dispatch = useDispatch();

  const updateSw = () => {
    const swWaiting = swReg && swReg.waiting;
    if (swWaiting) {
      swWaiting.postMessage({ type: 'SKIP_WAITING' });
      dispatch({ type: SW_UPDATE_FINISHED });
      window.location.reload();
    } else {
      dispatch({ type: SW_UPDATE_FINISHED });
    }
  }

  return (
    <Snackbar
      open={swUpdate}
      color="primary"
      message="New version available! 🎊"
      action={
        <Button color="primary" size="small" onClick={updateSw}>
          UPDATE
        </Button>
      }
    />
  )
}

export default SwUpdate;

It does what I would think it should do, reloads the page... but...

Again... you have an update! and everytime a do it manually from the inspector is the same story, I get a new SW and I'm not getting why that happens

在此处输入图片说明

I cannot seem to find an example of this happening to another person and I'm worried is something that I'm doing wrong, or something wrong with the CRA service worker, I only added the callbacks to the register's config argument and manage it on the specific component.

Edited: Add the service-worker code

I have the serviceWorker.js AS IS from Create react App:

// This optional code is used to register a service worker.
// register() is not called by default.

// This lets the app load faster on subsequent visits in production, and gives
// it offline capabilities. However, it also means that developers (and users)
// will only see deployed updates on subsequent visits to a page, after all the
// existing tabs open on the page have been closed, since previously cached
// resources are updated in the background.

const isLocalhost = Boolean(
  window.location.hostname === 'localhost' ||
    // [::1] is the IPv6 localhost address.
    window.location.hostname === '[::1]' ||
    // 127.0.0.0/8 are considered localhost for IPv4.
    window.location.hostname.match(
      /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
    )
);

export function register(config) {
  if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
    // The URL constructor is available in all browsers that support SW.
    const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
    if (publicUrl.origin !== window.location.origin) {
      // Our service worker won't work if PUBLIC_URL is on a different origin
      // from what our page is served on. This might happen if a CDN is used to
      // serve assets;
      return;
    }

    window.addEventListener('load', () => {
      const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;

      if (isLocalhost) {
        // This is running on localhost. Let's check if a service worker still exists or not.
        checkValidServiceWorker(swUrl, config);

        // Add some additional logging to localhost, pointing developers to the
        // service worker/PWA documentation.
        navigator.serviceWorker.ready.then(() => {
          console.log(
            'This web app is being served cache-first by a service ' +
              'worker. To learn more, visit <LINK>'
          );
        });
      } else {
        // Is not localhost. Just register service worker
        registerValidSW(swUrl, config);
      }
    });
  }
}

function registerValidSW(swUrl, config) {
  navigator.serviceWorker
    .register(swUrl)
    .then(registration => {
      registration.onupdatefound = () => {
        const installingWorker = registration.installing;
        if (installingWorker == null) {
          return;
        }

        installingWorker.onstatechange = () => {
          if (installingWorker.state === 'installed') {
            if (navigator.serviceWorker.controller) {
              // At this point, the updated precached content has been fetched,
              // but the previous service worker will still serve the older
              // content until all client tabs are closed.
              console.log(
                'New content is available and will be used when all ' +
                  'tabs for this page are closed. See '
              );

              // Execute callback
              if (config && config.onUpdate) {
                config.onUpdate(registration);
              }
            } else {
              // At this point, everything has been precached.
              // It's the perfect time to display a
              // "Content is cached for offline use." message.
              console.log('Content is cached for offline use.');

              // Execute callback
              if (config && config.onSuccess) {
                config.onSuccess(registration);
              }
            }
          }
        };
      };
    })
    .catch(error => {
      console.error('Error during service worker registration:', error);
    });
}

function checkValidServiceWorker(swUrl, config) {
  // Check if the service worker can be found. If it can't reload the page.
  fetch(swUrl, {
    headers: { 'Service-Worker': 'script' }
  })
    .then(response => {
      // Ensure service worker exists, and that we really are getting a JS file.
      const contentType = response.headers.get('content-type');
      if (
        response.status === 404 ||
        (contentType != null && contentType.indexOf('javascript') === -1)
      ) {
        // No service worker found. Probably a different app. Reload the page.
        navigator.serviceWorker.ready.then(registration => {
          registration.unregister().then(() => {
            window.location.reload();
          });
        });
      } else {
        // Service worker found. Proceed as normal.
        registerValidSW(swUrl, config);
      }
    })
    .catch(() => {
      console.log(
        'No internet connection found. App is running in offline mode.'
      );
    });
}

export function unregister() {
  if ('serviceWorker' in navigator) {
    navigator.serviceWorker.ready.then(registration => {
      registration.unregister();
    });
  }
}

I know that they use Workbox for the configuration, I'm testing all of this by running npm run build and serve -s build and reloading to try it out.

ALSO, here's the service-worker file generated by Workbox's configuration of Create react app when building the app:

/**
 * Welcome to your Workbox-powered service worker!
 *
 * You'll need to register this file in your web app and you should
 * disable HTTP caching for this file too.
 *
 * The rest of the code is auto-generated. Please don't update this file
 * directly; instead, make changes to your Workbox build configuration
 * and re-run your build process.
 */

importScripts("https://storage.googleapis.com/workbox-cdn/releases/4.3.1/workbox-sw.js");

importScripts(
  "/precache-manifest.a4724df64b745797c25a9173550ba2d3.js"
);

self.addEventListener('message', (event) => {
  if (event.data && event.data.type === 'SKIP_WAITING') {
    self.skipWaiting();
  }
});

workbox.core.clientsClaim();

/**
 * The workboxSW.precacheAndRoute() method efficiently caches and responds to
 * requests for URLs in the manifest.
 */
self.__precacheManifest = [].concat(self.__precacheManifest || []);
workbox.precaching.precacheAndRoute(self.__precacheManifest, {});

workbox.routing.registerNavigationRoute(workbox.precaching.getCacheKeyForURL("/index.html"), {
  
  blacklist: [/^\/_/,/\/[^/?]+\.[^/]+$/],
});

Apparently, having another service-worker required by Firebase messaging for push notifications were triggering an update everytime.

I honestly didn't mentioned it in the original question because I thought it didn't have anything to do with my problem.

I ended up setting up FCM to use my service worker instead of having it own, and it all started to work properly :)

I can't say much more since I'm not 100% sure WHAT was the problem really, probably the FCM serviceworker wasn't updating itself and made the other one try to update itself everytime? serviceworkers are weird

I faced the exact same issue, whenever updating the pop up would show to update the page, and new service worker would be in waiting state. I believe the issue was coming from the checkbox Update on Reload (on Application tab) being ticked on. Once I turned it off, everything seemed to work as expected.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM