Skip to content

fix(service-worker): properly handle invalid hashes in all scenarios - #21288

Closed
alxhub wants to merge 1 commit into
angular:masterfrom
alxhub:sw-mismatch-bug
Closed

alxhub wants to merge 1 commit into
angular:masterfrom
alxhub:sw-mismatch-bug

Conversation

@alxhub

@alxhub alxhub commented Jan 3, 2018

Copy link
Copy Markdown
Member

When the SW fetches URLs listed in a manifest with hashes, it checks
the content hash against the manifest to make sure it has the correct
version of the URL. In the event of a mismatch, the SW is supposed to
consider the manifest invalid, and avoid using it. There are 3 cases
to consider by which this can happen.

Case 1: during the initial SW installation, a manifest is activated
without waiting for every URL to be fully loaded. In the background,
every prefetch URL listed by the manifest is requested and cached.
One such prefetch request could fail the hash test, and cause the
manifest to be treated as invalid. In such a case, the SW should
enter a state of EXISTING_CLIENTS_ONLY, as the latest manifest is
invalid.

This case works today.

Case 2: during the initial SW installation, as in Case 1, a manifest
is activated without waiting for each URL to fully load. However,
it's possible that the application could request a URL with a bad
hash before background initialization tries to load that URL. This
happens if, for example, the application has a broken index.html.

In this case, the SW should enter a state of EXISTING_CLIENTS_ONLY,
and serve the request from the network instead.

What happens today is that the internal error escapes the SW and
is returned as a rejected Promise to respondWith(), causing a
browser-level error that the site cannot be loaded, breaking the
site.

This change allows the SW to detect the error and enter the correct
state, falling back on the network if needed.

Case 3: during checkForUpdate(), the SW will try to fully cache the
new update before making it the latest version. Failure here is
complicated - if the page fails to load due to transient network
conditions (timeouts, 500s, etc), then it makes sense to continue
serving the existing cached version, and attempt to activate the
update on the next cycle.

If the page fails due to non-transient conditions though (400 error,
hash mismatch, etc), then the SW should consider the updated
manifest invalid, and enter a state of EXISTING_CLIENTS_ONLY.

Currently, all errors are treated as transient.

This change causes the SW to treat all errors during updates as
non-transient, which can cause the SW to unnecessarily enter a
safe mode. A future change can allow the SW to remain in normal mode
if the error is provably transient.

@mary-poppins

Copy link
Copy Markdown

You can preview 66c89d6 at https://pr21288-66c89d6.ngbuilds.io/.

@mary-poppins

Copy link
Copy Markdown

You can preview c18158a at https://pr21288-c18158a.ngbuilds.io/.

@alxhub
alxhub requested a review from gkalpak January 3, 2018 21:41
@mhevery mhevery added the area: service-worker Issues related to the @angular/service-worker package label Jan 4, 2018

@gkalpak gkalpak left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it intentional that this is an Error and not a SwCriticalError?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing semicolon 😃

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

@alxhub

alxhub commented Jan 8, 2018

Copy link
Copy Markdown
Member Author

And yes, it's intentional that the other one is an error. I actually don't think that condition can be triggered, because a not-ok response would have already been converted to an error further down in the depths of fetching.

When the SW fetches URLs listed in a manifest with hashes, it checks
the content hash against the manifest to make sure it has the correct
version of the URL. In the event of a mismatch, the SW is supposed to
consider the manifest invalid, and avoid using it. There are 3 cases
to consider by which this can happen.

Case 1: during the initial SW installation, a manifest is activated
without waiting for every URL to be fully loaded. In the background,
every prefetch URL listed by the manifest is requested and cached.
One such prefetch request could fail the hash test, and cause the
manifest to be treated as invalid. In such a case, the SW should
enter a state of EXISTING_CLIENTS_ONLY, as the latest manifest is
invalid.

This case works today.

Case 2: during the initial SW installation, as in Case 1, a manifest
is activated without waiting for each URL to fully load. However,
it's possible that the application could request a URL with a bad
hash before background initialization tries to load that URL. This
happens if, for example, the application has a broken index.html.

In this case, the SW should enter a state of EXISTING_CLIENTS_ONLY,
and serve the request from the network instead.

What happens today is that the internal error escapes the SW and
is returned as a rejected Promise to respondWith(), causing a
browser-level error that the site cannot be loaded, breaking the
site.

This change allows the SW to detect the error and enter the correct
state, falling back on the network if needed.

Case 3: during checkForUpdate(), the SW will try to fully cache the
new update before making it the latest version. Failure here is
complicated - if the page fails to load due to transient network
conditions (timeouts, 500s, etc), then it makes sense to continue
serving the existing cached version, and attempt to activate the
update on the next cycle.

If the page fails due to non-transient conditions though (400 error,
hash mismatch, etc), then the SW should consider the updated
manifest invalid, and enter a state of EXISTING_CLIENTS_ONLY.

Currently, all errors are treated as transient.

This change causes the SW to treat all errors during updates as
non-transient, which can cause the SW to unnecessarily enter a
safe mode. A future change can allow the SW to remain in normal mode
if the error is provably transient.
@mary-poppins

Copy link
Copy Markdown

You can preview 12de6ca at https://pr21288-12de6ca.ngbuilds.io/.

@gkalpak

gkalpak commented Jan 9, 2018

Copy link
Copy Markdown
Member

LGTM 👍
@alxhub, leaving it to you to apply the appropriate target and action labels when ready 😃

@alxhub alxhub added action: merge The PR is ready for merge by the caretaker target: patch This PR is targeted for the next patch release labels Jan 9, 2018
@alexeagle

Copy link
Copy Markdown
Contributor

@kara no targets affected in google3 🍏

* found in the LICENSE file at https://angular.io/license
*/

export class SwCriticalError extends Error { readonly isCritical: boolean = true; }

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please check with @mhevery that this is ok? in the past we had problems when we tried to extend from the Error class.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In ES5 extending Error is broken and should not be done. In ES6 extending error works fine as long as the browser is executing ES6 class construct.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the SW runs entirely in ES6, I think this is safe to do.

@IgorMinar

Copy link
Copy Markdown
Contributor

Otherwise this LGTM

@kara kara added action: cleanup The PR is in need of cleanup, either due to needing a rebase or in response to comments from reviews and removed action: merge The PR is ready for merge by the caretaker labels Jan 10, 2018
@alxhub alxhub added action: merge The PR is ready for merge by the caretaker and removed action: cleanup The PR is in need of cleanup, either due to needing a rebase or in response to comments from reviews labels Jan 10, 2018
alexeagle pushed a commit that referenced this pull request Jan 10, 2018
…21288)

When the SW fetches URLs listed in a manifest with hashes, it checks
the content hash against the manifest to make sure it has the correct
version of the URL. In the event of a mismatch, the SW is supposed to
consider the manifest invalid, and avoid using it. There are 3 cases
to consider by which this can happen.

Case 1: during the initial SW installation, a manifest is activated
without waiting for every URL to be fully loaded. In the background,
every prefetch URL listed by the manifest is requested and cached.
One such prefetch request could fail the hash test, and cause the
manifest to be treated as invalid. In such a case, the SW should
enter a state of EXISTING_CLIENTS_ONLY, as the latest manifest is
invalid.

This case works today.

Case 2: during the initial SW installation, as in Case 1, a manifest
is activated without waiting for each URL to fully load. However,
it's possible that the application could request a URL with a bad
hash before background initialization tries to load that URL. This
happens if, for example, the application has a broken index.html.

In this case, the SW should enter a state of EXISTING_CLIENTS_ONLY,
and serve the request from the network instead.

What happens today is that the internal error escapes the SW and
is returned as a rejected Promise to respondWith(), causing a
browser-level error that the site cannot be loaded, breaking the
site.

This change allows the SW to detect the error and enter the correct
state, falling back on the network if needed.

Case 3: during checkForUpdate(), the SW will try to fully cache the
new update before making it the latest version. Failure here is
complicated - if the page fails to load due to transient network
conditions (timeouts, 500s, etc), then it makes sense to continue
serving the existing cached version, and attempt to activate the
update on the next cycle.

If the page fails due to non-transient conditions though (400 error,
hash mismatch, etc), then the SW should consider the updated
manifest invalid, and enter a state of EXISTING_CLIENTS_ONLY.

Currently, all errors are treated as transient.

This change causes the SW to treat all errors during updates as
non-transient, which can cause the SW to unnecessarily enter a
safe mode. A future change can allow the SW to remain in normal mode
if the error is provably transient.

PR Close #21288
@alexeagle alexeagle closed this in 3951098 Jan 10, 2018
@angular-automatic-lock-bot

Copy link
Copy Markdown

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 13, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

action: merge The PR is ready for merge by the caretaker area: service-worker Issues related to the @angular/service-worker package cla: yes target: patch This PR is targeted for the next patch release

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants