Skip to content

index.html cache issue #2299

@ketysek

Description

@ketysek

Library Affected:
workbox-webpack-plugin@4.2.0 with workbox-build@4.3.1

Browser & Platform:
all browsers

Issue or Feature Request Description:
Hi there, we've got service worker enabled for caching purposes and we also have notification bar in app which informs user about its new version. This notification bar also includes button to update app by click :-)

registerServiceWorker.js of CRA framework with little modifications has following code:

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

export default 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)
    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; see https://github.com/facebookincubator/create-react-app/issues/2374
      return
    }

    window.addEventListener("load", () => {
      let isInitialServiceWorker = false
      if (!navigator.serviceWorker.controller) {
        isInitialServiceWorker = true
      }
      const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`

      if (isLocalhost) {
        // This is running on localhost. Lets check if a service worker still exists or not.
        checkValidServiceWorker(swUrl, config)
      } else {
        // Is not local host. Just register service worker
        registerValidSW(swUrl, config)
      }

      navigator.serviceWorker.addEventListener("controllerchange", () => {
        if (navigator.serviceWorker.controller) {
          if (!isInitialServiceWorker) {
            window.location.reload()
          }
        }
      })
    })
  }
}

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 old content will have been purged and
              // the fresh content will have been added to the cache.
              // It's the perfect time to display a "New content is
              // available; please refresh." message in your web app.
              console.log("New content is available; please refresh.")
              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.")
            }
          }
        }
      }
      if (registration.waiting === null) {
        setInterval(() => registration.update(), 3600000)
      } else {
        // this needs to be done because of firefox, is it doesn't have
        // serviceWorker in installing state before app load
        const waitingWorker = registration.waiting
        if (waitingWorker.state === "installed") {
          if (navigator.serviceWorker.controller) {
            console.log("New content is available; please refresh.")
            if (config && config.onUpdate) {
              config.onUpdate(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)
    .then(response => {
      // Ensure service worker exists, and that we really are getting a JS file.
      if (
        response.status === 404 ||
        response.headers.get("content-type").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.")
    })
}

and I append this to resulted serviceWorker.js file for "update" app functionality

self.addEventListener('message', function(event) {
  // Force SW upgrade (activation of new installed SW version)
  if ( event.data === 'skipWaiting' ) {
    /* eslint-disable-next-line no-restricted-globals */
    self.skipWaiting();
  }
});

From time to time, users are reporting app unavailability with following error in console:
SyntaxError: expected expression, got '<'

The whole problem is that sometimes index.html file is not replaced with the newest one, so it points to not-existing css/js files. How can it be possible?

Metadata

Metadata

Assignees

No one assigned

    Labels

    Needs More InfoWaiting on additional information from the community.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions