Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Service Worker] Can't update sw if in SAFE_MODE #22087

Closed
sean-nicholas opened this issue Feb 8, 2018 · 8 comments
Closed

[Service Worker] Can't update sw if in SAFE_MODE #22087

sean-nicholas opened this issue Feb 8, 2018 · 8 comments
Labels
area: service-worker Issues related to the @angular/service-worker package freq2: medium type: bug/fix
Milestone

Comments

@sean-nicholas
Copy link

I'm submitting a...


[ ] Regression (a behavior that used to work and stopped working in a new release)
[x] Bug report  
[ ] Feature request
[ ] Documentation issue or request
[ ] Support request => Please do not submit support request here, instead see https://github.com/angular/angular/blob/master/CONTRIBUTING.md#question

Current behavior

When the service worker is installed / initialized the first time, it fetches the ngsw.json. If the content of ngsw.json is not an valid json the sw goes into SAFE_MODE:

try {
// Wait for initialization.
await this.initialized;
} catch (e) {
// Initialization failed. Enter a safe state.
this.state = DriverReadyState.SAFE_MODE;

From thereon it is impossible to update the service worker. If you call SwUpdate.checkForUpdate() it never resolves or rejects and no new fetch to ngsw.json is performed due to the fact that messages are ignored in SAFE_MODE:

private onMessage(event: ExtendableMessageEvent): void {
// Ignore message events when the SW is in safe mode, for now.
if (this.state === DriverReadyState.SAFE_MODE) {
return;
}

Replacing the wrong ngsw.json with the correct one does only resolve this issue if:

  • the service worker is unregistered (if ngsw.json did not contain json. for example it contains html)
  • or the sw is unregistered and the cache is cleared (if ngsw.json did contain a json. ex: { "test": 123 }).
    This also triggers the following error: Error: Invariant violated (initialize): latest hash null has no known manifest

This issue occurred on our firebase hosted app. It seems a cache sometimes returns a html page with a 2xx status instead of the ngsw.json and broke our service worker. We also needed to clear the cache and unregister the service worker on some mobile clients. It might be that firebase sends sometimes a wrong json file or there might be another way to lock the service worker in the SAFE_MODE with latest hash null error.

Expected behavior

  • It should be possible to update the sw with SwUpdate.checkForUpdate() when the sw is in SAFE_MODE
  • Maybe the sw should try to refetch the ngsw.json? I'm not sure when and if this would be the appropriated way to handle this issue.

Minimal reproduction of the problem with instructions

Repo: https://github.com/sean-nicholas/test-angular-service-worker

  • run npm run build
  • cd into `dist``
  • change the content of ngsw.json to
    • For non json case: asdjknkfdnjgdfkaslkdm
    • For json case: { "test": 123 }
  • run http-server (or something similar) in dist folder
  • goto localhost in incognito mode
  • you get the error
    • For non json case: Unexpected token a in JSON at position 0
    • For json case: TypeError: Cannot convert undefined or null to object
  • sw installation fails and sw goes into SAFE_MODE
  • reload & press the Check for SW Update Button
  • Checking will stay true and not fall back to false after a few ms
  • Replace the content of ngsw.json with the correct data

For non json case:

  • Unregister the sw & reload -> it works again

For json case:

  • Unregister the sw & reload
  • Error: Error: Invariant violated (initialize): latest hash null has no known manifest occures
  • Check for SW Update seems to work but if you reload it fails (does not resolve) again
  • Clear the cache, unregister the sw & reload -> it works again

What is the motivation / use case for changing the behavior?

The service worker should not lock itself into a state were it can not be updated anymore.

Environment


"@angular/animations": "^5.2.4",
"@angular/cdk": "^5.2.0",
"@angular/common": "^5.2.4",
"@angular/compiler": "^5.2.4",
"@angular/core": "^5.2.4",
"@angular/forms": "^5.2.4",
"@angular/http": "^5.2.4",
"@angular/material": "^5.2.0",
"@angular/platform-browser": "^5.2.4",
"@angular/platform-browser-dynamic": "^5.2.4",
"@angular/router": "^5.2.4",
"@angular/service-worker": "^5.2.4"

Browser:
(Only tested in)
- [x] Chrome (desktop) version 64.0.3282.140
- [x] Chrome (Android) version 64.0.3282.137
- [ ] Chrome (iOS) version XX
- [ ] Firefox version XX
- [ ] Safari (desktop) version XX
- [ ] Safari (iOS) version XX
- [ ] IE version XX
- [ ] Edge version XX
 
@mhevery mhevery added the area: service-worker Issues related to the @angular/service-worker package label Feb 13, 2018
@alxhub
Copy link
Member

alxhub commented Feb 15, 2018

Once the SW enters a degraded state (SAFE_MODE), it will stay there - it's not possible to exit such a state from the application.

A restart of the SW should end up in a good state though, provided ngsw.json is valid. I'll investigate whether that's the case.

@sean-nicholas
Copy link
Author

Thanks for investigating 😊.

What do you think about a function on SwUpdate that could get the mode / state from the SW, so the application could un- and reregister the SW?

A current workaround would be to call SwUpdate.checkForUpdate() and check if the promise does not resolve or rejected within a given time and then unregister the SW. Well, this should not be the standard way to do it, because the not resolving / rejecting promise might be another issue.

@petivagyok16
Copy link

petivagyok16 commented Feb 19, 2018

Im experiencing the same issue. swUpdate.checkForUpdate() promise never gets resolved/rejected, so that now i cannot trigger manually the refreshing in my app.

  • TLDR: Everything works fine, if i close chrome devtools! Opened devtools screws up service worker update mechanism. Check if you can manage to work sw update after closing devtools! Hope this helps

My logic looks like this:

public checkUpdates() {

    this.swUpdate$ = this.swUpdate.available.subscribe(event => {

      console.log('[App] Update available: current version is', event.current, 'available version is', event.available);
      if (event.current.hash !== event.available.hash) {
        const snackbarRef = this.snackbar.open('New version available!', 'Refresh');

        snackbarRef.onAction().pipe(take(1)).subscribe(() => {
          this.winRef.nativeWindow.location.reload();
        });
      } else {
        this.snackbar.open('Your version is the newest!');
      }

    });

    this.activateUpdate$ = this.swUpdate.activated.subscribe(event => {
      console.log('[App] Update activated: old version was', event.previous, 'new version is', event.current);
    });

  }

  public checkForUpdate() {
    console.log('[App] checkForUpdate started');
    this.swUpdate.checkForUpdate()
      .then(() => {
        console.log('[App] checkForUpdate completed');
      })
      .catch(err => {
        console.error(err);
      });
  }

  public activateUpdate() {
    console.log('[App] activateUpdate started');
    this.swUpdate.activateUpdate()
      .then(() => {
        console.log('[App] activateUpdate completed');
        this.winRef.nativeWindow.location.reload();
      })
      .catch(err => {
        console.error(err);
      });
  }

In this way i can make a button in my app which can be tapped by the user for check updates checkForUpdates() and it triggers the this.swUpdate.available.subscribe(event => { ... } which will open a snackbar. But currently its not possible due to the never resolving/rejecting promise. Am i right?

UPDATE: Everything works fine, if i close chrome devtools! Opened devtools screws up service worker update mechanism. Check if you can manage to work sw update after closing devtools! Hope this helps

@ngbot ngbot bot modified the milestones: needsTriage, Backlog Feb 26, 2018
@gkalpak
Copy link
Member

gkalpak commented Mar 20, 2018

Hopefully the DevTools related issues will be fixed by #22883, @petivagyok16.

But it does seem that after entering SAFE_MODE, you are unnecessarily stuck in there for good only until the SW is restarted 😁
Theoretically, you should be able to cause the SW to unregister itself by removing ngsw.json from the server (i.e. return 404) which doesn't seem to work, since SW ignores all messages/requests (as @sean-nicholas said).

Also, I can't think of any reason not to process checkForUpdates() when in SAFE_MODE (and exit SAFE_MODE if an update is successfully activated).

@alxhub, wdyt? Also what does "a restart of the SW" mean? NVM, see #31109 (comment).

@k-schneider
Copy link

I was just hit by this on my dev machine and had a hell of a time figuring out what happened. Unregistering the service worker wasn't enough... had to unregister AND clear my cache in Chrome. If this were to ever affect an end user they wouldn't stand a chance.

@freefred81
Copy link

Hi .. same problem here :

ngsw-worker.js:2270 Uncaught (in promise) Error: Invariant violated (assignVersion): want AppVersion for null but not loaded
at Driver.lookupVersionByHash (ngsw-worker.js:2270)
at Driver. (ngsw-worker.js:2287)
at Generator.next ()
at ngsw-worker.js:1783
at new Promise ()
at ngsw-worker.js:1779
at Driver.assignVersion (ngsw-worker.js:2278)
at Driver. (ngsw-worker.js:2124)
at Generator.next ()
at fulfilled (ngsw-worker.js:1780)

If i clear chache and hit CTRL + F5 the service worker throw error and app crash.

@gkalpak
Copy link
Member

gkalpak commented Jul 26, 2019

As mentioned above, the current behavior is by design. The degraded modes will not persist across instances, so once the browser terminates the SW instance after a period of inactivity, the mode will go back to NORMAL again.

See, also, #31109 for a related discussion about the EXISTING_CLIENTS_ONLY mode specifically.
#31865 (if we decide to merge it), will make the EXISTING_CLIENTS_ONLY mode recoverable, since we have more info about the reasons that could have led to entering that mode.

SAFE_MODE on the other side can be a result of any unanticipated issue and will be far more riskier to try and recover from.

So, I am going to close this issue as won't fix.

That said, I think it might be reasonable to expose the driver state as a service (or maybe expose the whole debug info (including the driver state) as JSON).

Free free to open a feature request for that.

An ugly work-around for now, would be to request /ngsw/state and parse the text content to extract the driver state. E.g.:

fetch('/ngsw/state').
  then(res => res.text()).
  then(text => (text.match(/Driver state: (\w+)/) || [, 'UNKNOWN'])[1]);

@gkalpak gkalpak closed this as completed Jul 26, 2019
@angular-automatic-lock-bot
Copy link

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators Sep 15, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area: service-worker Issues related to the @angular/service-worker package freq2: medium type: bug/fix
Projects
None yet
Development

No branches or pull requests

7 participants