Previously, when the ServiceWorker entered a degraded mode
(`EXISTING_CLIENTS_ONLY` or `SAFE_MODE`) it would remain in that mode
for the rest of the lifetime of ServiceWorker instance. Note that
ServiceWorkers are stopped by the browser after a certain period of
inactivity and a new instance is created as soon as the ServiceWorker
needs to handle an event (such as a request from the page). Those new
instances would start from the `NORMAL` mode.
The reason for this behavior is to err on the side of caution: If we
can't be sure why the ServiceWorker entered the degraded mode, it is
risky to try recovering on the same instance and might lead to
unexpected behavior.
However, it turns out that the `EXISTING_CLIENTS_ONLY` mode can only be
a result of some error happening with the latest version (e.g. a hash
mismatch in the manifest). Therefore, it is safe to recover from that
mode once a new, valid update is successfully installed and to start
accepting new clients.
This commit ensures that the mode is set back to `NORMAL`, when (a) an
update is successfully installed and (b) the current mode is
`EXISTING_CLIENTS_ONLY`.
Besides making the behavior more predictable (instead of relying on the
browser to decide when to terminate the current ServiceWorker instance
and create a new one), this change can also improve the developer
experience:
When people notice the error during debugging and fix it by deploying a
new version (either to production or locally), it is confusing that the
ServiceWorker will fetch and install the update (as seen by the requests
in the Network panel in DevTools) but not serve it to clients. With this
change, the update will be served to new clients as soon as it is
installed.
Fixes#31109
PR Close#31865
Previously, when the latest version was invalidated (e.g. due to a hash
mismatch), the SW entered a degraded `EXISTING_CLIENTS_ONLY` mode and
removed _all_ clients from its client-version map (essentially stopping
to serve any clients). Based on the code and surrounding comments, the
intention seems to have been to only remove clients that were on the
invalidated version, but keep other clients on older versions.
This commit fixes it by only unassigning clients what were on the latest
version and keep clients assigned to older versions.
PR Close#31865
The `latest` argument was only ever set to the value of comparing
`this.latestHash` with the `appVersion` hash, which is already computed
inside `versionFailed()`, so there is no reason to pass it as an
argument as well.
This doesn't have any impact on the current behavior of the SW.
PR Close#31865
Previously, (presummably due to a typo) the `okToCacheOpaque` argument
of `DataGroup#cacheResponse()` was essentially never taken into account
(since opaque responses have a non-200 status code and thus `res.ok` is
always false).
This commit fixes the typo, which allows opaque responses to be cached
when `okToCacheOpaque` is true (i.e. in data groups using the
`freshness` strategy).
Fixes#30968
PR Close#30977
This commit doesn't change the behavior wrt caching, but it makes it
more explicit that only non-timed-out responses are cached. In case of a
timeout, `res` would be set to a programmatically created 504
`Response`, so `cacheResponse()` (which checks for `res.ok`) would not
have cached it anyway, but this makes change makes it more explicit (and
more similar to the equivalent part in [handleFetchWithFreshness()][1]).
[1]: https://github.com/angular/angular/blob/2b4d5c754/packages/service-worker/worker/src/data.ts#L379-L388
PR Close#30977
Since Angular v8, and commit b3dda0e, `parseUrl()` can be called without
`relativeTo`, thus `new URL()` can be called with `relativeTo = undefined`.
Safari does not like it and the service worker registration fails:
```js
new URL('https://angular.io/') // OK
new URL('https://angular.io/', undefined) // TypeError
```
Closes#31061
PR Close#31140
Previously, it was not possible to have multiple apps (using
`@angular/service-worker`) on different subpaths of the same domain,
because each SW would overwrite the caches of the others (even though
their scope was different).
This commit fixes it by ensuring that the cache names created by the SW
are different for each scope.
Fixes#21388
PR Close#27080
Previously, if an app version contained the same files as an older
version (e.g. making a change, then rolling it back), the SW would not
detect it as the latest version (and update clients).
This commit fixes it by adding a `timestamp` field in `ngsw.json`, which
makes each build unique (with sufficiently high probability).
Fixes#24338
PR Close#26006
- Format JSDoc for notificationClicks
- Add comment on why handleClick does not use hasOwnProperty
- Add additional test that uses handleClick without action
PR Close#25860
- Serialize notification object before using postMessage
- Close notification on click
- Focus browser if it is not already focused on click
PR Close#25860
The previous version did not support the 'notificationclick' event.
Add event handler for the event and provide an observable of
clicked notifications in the SwPush service.
Closes#20956, #22311
PR Close#25860
Since the SW immediately takes over all clients, it is safe to delete
caches used by older (e.g. beta) `@angular/service-worker` versions to
avoid running into browser storage quota limitations.
PR Close#26319
In some cases, example when the user clears the caches in DevTools but
the SW remains active on another tab and keeps references to the deleted
caches, trying to write to the cache throws errors (e.g.
`Entry was not found`).
When this happens, the SW can no longer work correctly and should enter
a degraded mode allowing requests to be served from the network.
Possibly related:
- https://github.com/GoogleChrome/workbox/issues/792
- https://bugs.chromium.org/p/chromium/issues/detail?id=639034
This commits remedies this situation, by ensuring the SW can enter the
degraded `EXISTING_CLIENTS_ONLY` mode and forward requests to the
network.
PR Close#26042
At the moment `cacheAge` can we undefined when having `Cache-Control` set to `no-cache` due the mapping method in `needToRevalidate`
Closes#25442
PR Close#25408
The ServiceWorker will redirect navigation requests that don't match any
`asset` or `data` group to the specified index file. The rules for a
request to be classified as a navigation request are as follows:
1. Its `mode` must be `navigation`.
2. It must accept a `text/html` response.
3. Its URL must match certain criteria (see below).
By default, a navigation request can have any URL except for:
1. URLs containing `__`.
2. URLs to files (i.e. containing a file extension in the last path
segment).
While these rules are fine in many cases, sometimes it is desirable to
configure different rules for the URLs of navigation requests (e.g.
ignore specific URLs and pass them through to the server).
This commit adds support for specifying an optional `navigationUrls`
list in `ngsw-config.json`, which contains URLs or simple globs
(currently only recognizing `!`, `*` and `**`).
Only requests whose URLs match any of the positive URLs/patterns and
none of the negative ones (i.e. URLs/patterns starting with `!`) will be
considered navigation requests (and handled accordingly by the SW).
(This is an alternative implementation to #23025.)
Fixes#20404
PR Close#23339
Under some circumstances (possibly related to opening Chrome DevTools),
requests are made with `cache: 'only-if-cached'` and `mode: 'no-cors'`.
These request will eventually fail, because `only-if-cached` is only
allowed to be used with `mode: 'same-origin'`.
This is likely a bug in Chrome DevTools.
This commit avoids errors related to such requests by not handling them.
Fixes#22362
PR Close#22883
Previously, when trying to fetch `ngsw.json` (e.g. during
`checkForUpdate()`) while either the client or the server were offline,
the ServiceWorker would enter a degrade mode, where only existing
clients would be served. This essentially meant that the ServiceWorker
didn't work offline.
This commit fixes it by differentiating offline errors and not entering
degraded mode. The ServiceWorker will remain in the current mode until
connectivity to the server is restored.
Fixes#21636
PR Close#22883
'remove' method not removing url from state.map
'accessed' method not removing 'previous' reference from existing node when it becomes the head
Fixes#22218Fixes#22768
PR Close#22769
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
Currently the Service Worker checks for updates only on SW startup,
an event which happens frequently but also nondeterministically. This
makes it hard for developers to observe the update process or reason
about how updates will be delivered to users. This problem is
exacerbated by the DevTools behavior of keeping the SW alive
indefinitely while opened, effectively preventing the page from
updating at all.
This change causes the SW to additionally check for updates on
navigation requests (app page reloads). This creates deterministic
update behavior, and is much easier for developers to reason about.
It does leave the old update-on-SW-startup behavior in place, as
removing that would be a breaking change.
Fixes#20877
Not every application is served from the domain root. The Service
Worker made a bad assumption that it would be, and so requested
/ngsw.json from the domain root.
This change corrects this assumption, and requests ngsw.json without
the leading slash. This causes the request to be interpreted
relative to the SW origin, which will be the application root.
PushEvent.data is not the data object itself, but an instance representing
the data in wire format, with methods to synchronously decode it to JSON,
ArrayBuffer, etc. NGSW assumes all push data is in JSON format.
PR Close#19764
When Cache.put() is called with a Response, it consumes the response. If
the Response is used for any other purpose (such as satisfying the
original FetchEvent) it must be cloned first.
A bug exists in the mocks used for SW tests, where this condition is not
validated. The bodies of MockResponses can be utilized repeatedly without
erroring in the same way that a real browser would. This bug is fixed by
this commit, which causes tests for the freshness strategy of data caching
to start failing.
The cause of this failure is a second bug in the data caching code, where
the Response is not cloned prior to being passed to Cache.put(). This is
also fixed.
PR Close#19764
This commit fixes several issues discovered through use in real apps.
* The sha1() function operated on text content, causing issues for binary-format files.
A sha1Binary() function which operates on unparsed data now avoids any encoding issues.
* The characters '?' and '+' were not escaped in Glob-to-regex conversion previously, but
are now.
* URLs from the browser contain the full origin, but were checked against the table of
hashes from the manifest which only has the path for URLs from the same origin. Now the
origin is checked and URLs are relativized to the domain root before comparison if
appropriate.
* ngsw: prefix was missing from data groups, is now added.
* Occasionally servers will return a redirected response for an asset, and caching it could
cause errors for navigation requests. The SW now handles this by detecting such responses
and following the redirect manually, to avoid caching a redirected response.
* The request for known assets is now created from scratch from the URL before fetching from
the network, in order to sanitize it and avoid carrying any special modes or headers that
might result in opaque responses.
* Debugging log for troubleshooting.
* Avoid creating errors by returning 504 responses on error.
* Fix bug where idle queue doesn't run in some circumstances.
* Add tests for the above.
This service worker is a conceptual derivative of the existing @angular/service-worker maintained at github.com/angular/mobile-toolkit, but has been rewritten to support use across a much wider variety of applications.
Entrypoints include:
@angular/service-worker: a library for use within Angular client apps to communicate with the service worker.
@angular/service-worker/gen: a library for generating ngsw.json files from glob-based SW config files.
@angular/service-worker/ngsw-worker.js: the bundled service worker script itself.
@angular/service-worker/ngsw-cli.js: a CLI tool for generating ngsw.json files from glob-based SW config files.