Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular service worker is in SAFE_MODE

I have an Angular PWA. Its service worker was working flawlessly until I upgraded from Angular 5.0 to 7.2

After the upgrade, I see the following error in /ngsw/state

Driver state: SAFE_MODE (Initialization failed due to error: Invariant violated (initialize): latest hash null has no known manifest initialize/<@https://{{domain}}.com/ngsw-worker.js:2227:27 fulfilled@https://{{domain}}.com/ngsw-worker.js:1772:52 ) Latest manifest hash: none Last update check: never 

App background

  • Angular version is 7.2.0
  • Service worker version is 7.2.0
  • I am manually checking for updates using SwUpdate service.

Code to check for updates

if (this.updates.isEnabled) {       const appIsStable$ = this.appRef.isStable.pipe(first(isStable => isStable === true));       const every21600Ms$ = interval(6 * 60 * 60);       const every21600MsOnceAppIsStable$ = concat(appIsStable$, every21600Ms$);       // poll the service worker to check for updates       every21600MsOnceAppIsStable$.subscribe(() =>         this.updates.checkForUpdate()       );     } 

My research and troubleshooting steps so far:

In Chrome

  • Service worker is registered without any error
  • I looked into the network tab and found service worker never downloads 'ngsw.json'. I have seen 'ngsw.json' being downloaded when the app was in Angular 5.0

Screenshot1

  • I see the same issue has been reported here. I don't see any proper solution here.
  • I have tried selecting 'Update on reload' which fixes the problem temporarily. Same error shows up again after I unselect it.

Screenshot2

In Microsoft edge

  • Surprisingly, everything works in edge after the upgrade.

My thoughts and expectations

  • Since the application is working fine in MS edge, I doubt that there is anything wrong with service worker config or with the way I poll for updates
  • My expectation is to see Drive state as Normal in /ngsw/state
like image 307
shobhit vaish Avatar asked Jan 31 '19 07:01

shobhit vaish


People also ask

What does service worker do in Angular?

At its simplest, a service worker is a script that runs in the web browser and manages caching for an application. Service workers function as a network proxy. They intercept all outgoing HTTP requests made by the application and can choose how to respond to them.

How do I bypass Angular service worker?

To bypass the service worker, set ngsw-bypass as a request header, or as a query parameter. The value of the header or query parameter is ignored and can be empty or omitted.

What is NGSW worker JS?

This is the runtime configuration file, that the Angular Service worker will use. This file is built based on the ngsw-config. json file, and contains all the information needed by the Angular Service Worker to know at runtime about which files it needs to cache, and when.

What is NGSW cache bust?

ngsw-cache-bust=0.7064947087867681. It's trying to get the service worker configuration to see what needs updated, but can't access the file. However, this should not impede the service worker from serving up anything it's already cached, and operate normally until back online.


1 Answers

Update

This will be fixed as of Angular version 8.2.14. Below workarounds are only required with older versions.

 


 

Old workarounds for < 8.2.14

It seems this is a well known bug in the Angular service worker and that we may expect a patch / working version at some point. Most information can be found in issue 25611 as shobhit vaish already stated.

However, if you are looking for a solution now or you are going to stick with an older Angular 7/8 version longer, which maybe will not get patched soon, I will try to sum up the currently known workarounds. (Also they may not qualify as "proper solution"...)

Patching ngsw-worker.js

You can patch your ngsw-worker.js file either after your ng run app:build:production where it will typically reside in the www folder inside your project (if you haven't changed the Angular outputPath). Or you can patch it before your build inside node_modules/@angular/service-worker.

Depending on how often you build your app you could do it simply manually (i.e. with a text editor of your choice) or you write a script for example with the help of sed.

Now you have two options:

1. Patching handleMessage()

Search for

        handleMessage(msg, from) {             return __awaiter$5(this, void 0, void 0, function* () { 

And replace it with

        handleMessage(msg, from) {             return __awaiter$5(this, void 0, void 0, function* () {                 if (this.initialized === null) {                     this.initialized = this.initialize();                 }                 try {                     yield this.initialized;                 }                 catch (e) {                     this.state = DriverReadyState.SAFE_MODE;                     this.stateMessage = `Initialization failed due to handleMessage() error: ${errorToString(e)}`;                 } 

Depending on your @angular/service-worker version the signature of handleMessage() may look different for you and you could write a more sophisticated catch but hopefully / probably it would never reach the catch block anyways.
This patch basically is what is suggested in a comment by gkalpak and most likely the official fix will also be something like this.

2. Patching initialize()

Search for

                try {                     // Read them from the DB simultaneously.                     [manifests, assignments, latest] = yield Promise.all([                         table.read('manifests'),                         table.read('assignments'),                         table.read('latest'),                     ]); 

And replace it with

                try {                     // Read them from the DB simultaneously.                     [manifests, assignments, latest] = yield Promise.all([                         table.read('manifests'),                         table.read('assignments'),                         table.read('latest'),                     ]);                     if (latest.latest === null) throw new Error('Error latest.latest is null for what ever reason...'); 

With this you will ensure that the catch block of initialize() will actually run and prevent the service worker from entering SAFE_MODE. However this may not be as reliable as the first patch but could also work for service workers which are already in SAFE_MODE due to this problem. It was proposed in a comment by hsta.

Working around the problem in your Angular app

If you don't want to mess around with the ngsw-worker.js you can try to detect the state of the service worker by fetching your-app.domain/ngsw/state from within your Angular app and if it is in SAFE_MODE (could be checked for example with a simple regex search) you can try to reset it by deleting all items from the cache storage and then unregistering the service worker. The idea to this was proposed in a comment by mattlewis92 and approved as probably working hack in a comment by gkalpak.

Temporary working around the problem manually

As shobhit vaish already found out, you can also manually resolve the issue. Next to the possibility which is already stated in the original post you can also do this in the dev tools of chrome based browser under "Application" -> "Clear storage", selecting "Unregister service workers" and hit "Clear site data". Obviously the problem can and probably will come back on future updates and will not help much for normal users. But it may be handy if you as developer just want to resolve it quickly at the moment.

like image 54
Jey DWork Avatar answered Sep 28 '22 03:09

Jey DWork