1

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.

Adrien SAULNIER
  • 1,651
  • 1
  • 13
  • 19

1 Answers1

2

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.

NeNaD
  • 18,172
  • 8
  • 47
  • 89
  • Thank you for your answer and your article :) ! About the local hashing, I didn’t find any explanation about it inside the Angular documentation (but maybe I’m wrong) and this is exactly what I was looking for… but why it’s working locally ? I'm supposed to find the same issue but and this is not the case. About the last 3 points you said, we’re not in any of those cases. – Adrien SAULNIER Apr 24 '23 at 13:17
  • Yeah, it's not in the docs. I know this because I had the same issue before and I had to dive deeper into the internal logic. I still didn't quite understand why are you overriding the `appsettings.json` file. You said it will eventually have the same content as before the build, so why do you override it? – NeNaD Apr 24 '23 at 13:51
  • 1
    For the local hashing, I think I saw it in the GitHub inside of some thread. – NeNaD Apr 24 '23 at 13:52
  • As we use the same application build in our CI (which we use it to move from an env into another), we’ve just to override some values inside our appsettings (our API’s url by example).
That prevents multiple ng build (one for each env). When we start the ng build, appsettings contains default values. So, when the ngsw.json will be generated, it will always "hash" the default appsettings content. Later in our CI, we'll replace values according to the targetted env. – Adrien SAULNIER Apr 24 '23 at 14:02
  • Oh, that is the issue because the local hash will be different for that file. You should not apply any post-processing. I would say the best option for you is to create different `env` files, one for each environment, and then to create different build scripts for each environment. So, you would have `ng-build-prod`, `ng-build-stage`... – NeNaD Apr 24 '23 at 14:23