Search code examples
angularprogressive-web-appsangular-service-worker

Angular Service Worker validation file process


Context

Angular PWA.

Before explaining the error, I have to specify :

  1. We read the Angular service worker documentation
  2. We read this post, specially the validated answer as it looks like our situation (but that didn't really answer our questions ).
  3. All's working well during local test (ng build + http-server to serve the app).
  4. We don't use Angular's "environment" files. Instead, we're using an "appsettings.json" which is overidding inside our CI to use the same artifact and prevents multiple build.

Globally, inside our CI : Build (ngsw.json is generated) -> overriding appsettings.json -> deploy. Yes, appsettings is overidded after building (so, after ngsw.json generation). But, before or after such a generation : appsettings.json content is the same: the generated hash has the right/same value between each deployment. Also, such a story's working locally without any issues (build -> change appsettings.json content -> serve).

Our ngsw-config.json looks like this :

{
  "$schema": "./node_modules/@angular/service-worker/config/schema.json",
  "index": "/index.html",
  "assetGroups": [
    {
      "name": "app",
      "installMode": "prefetch",
      "resources": {
        "files": [
          "/favicon.ico",
          "/index.html",
          "/appsettings.json", // The added line
          "/manifest.webmanifest",
          "/*.css",
          "/*.js"
        ]
      }
    },
    {
      "name": "assets",
      "installMode": "lazy",
      "updateMode": "prefetch",
      "resources": {
        "files": [
          "/assets/**",
          "/*.(svg|cur|jpg|jpeg|png|apng|webp|avif|gif|otf|ttf|woff|woff2)"
        ]
      }
    }
  ]
}

Problem

After each deployment (made by our CI), we faced with this error :

Failed to install app version '30ba317c7d27a544efb0c7eb47653aa0fa664fb9'
Error: Hash mismatch (cacheBustedFetchFromNetwork): https://....../appsettings.json: expected 92e837ce30053ba552fcf37c8b34b5d1b1f5f06e, got 9756c02e19847ca03c3cf2d677c65e0a4e9665ef (after cache busting)
    at PrefetchAssetGroup.cacheBustedFetchFromNetwork (https://....../ngsw-worker.js:474:21)
    at async PrefetchAssetGroup.fetchFromNetwork (https://....../ngsw-worker.js:449:19)
    at async PrefetchAssetGroup.fetchAndCacheOnce (https://....../ngsw-worker.js:428:21)

It seems service worker found a wrong hash for appsettings. However, when the app's checking an update, the resulting ngsw.json file is correct (we compared with the latest deployed files in our server) : we didn't find the hash that the service worker got (9756c02e19847ca03c3cf2d677c65e0a4e9665ef). Once again : no hash issue locally, even after editing appsettings.

Questions

Of course, there is something we don't understand. Inside the Angular documentation, the "hashed content" part's saying :

If a particular file fails validation, the Angular service worker attempts to re-fetch the content using a "cache-busting" URL parameter to prevent browser or intermediate caching.

What is behind this "validation" ? Is there a "rehash" operation somewhere (which would explain this unknown hash we got) ? We didn't find any explanation about this. If its the case, that doesn't explain why such a story's working locally. As we understood (if we're not goats...), the only "hashing" process is during "ng build"... but maybe we're wrong.


Solution

  • Hashing happens in two occasions:

    Hashing during ng-build

    An algorithm will compute the hash of each file based on the file content. This happens during the build process.

    Service Worker local hashing

    Service Worker will first fetch ngsw.json file to see if a new version exists. If yes, it computes the hash of each file again locally and checks if it matches the hash present in ngsw.json file.

    If at least one hash does not match the hash in ngsw.json file, it will throw hash-mismatch error, and the latest version of the app will not be activated.


    As you already said, you are applying post-processing, so that is probably the reason for hash-mismatch error. However, the hash-mismatch issue can occur for several reasons:

    1. Post-processing after production build: If any post-processing is done on a file after the production build has generated the ngsw.json file, it can result in a hash mismatch when the Service Worker computes it. To avoid this issue, be sure to avoid any post-processing after the production build is generated, such as changing API keys in the index.html file.

    2. Middle layer of caching data: If a middle layer of caching data is present between the client and the server, such as Nginx, it can cause a hash mismatch with the Service Worker.

    3. Deploying the application with Git: When deploying the application with Git using a post-receive git hook, line endings can be replaced by default, causing the files on the server to differ from the original ones, resulting in a hash mismatch.

    4. Serving files through CDN: CDN can apply different optimizations on the files, such as minification or compression, which can change the hash value for these files and result in a hash mismatch for the Service Worker.

    I wrote about all of this in my blog, so you can check the article for more details.