Skip to content

Commit a33182c

Browse files
committed
fix(service-worker): check for updates on navigation
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
1 parent 66cc2fa commit a33182c

File tree

3 files changed

+55
-8
lines changed

3 files changed

+55
-8
lines changed

aio/content/guide/service-worker-devops.md

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,10 @@ server can ensure that the Angular app always has a consistent set of files.
3939

4040
#### Update checks
4141

42-
Every time the Angular service worker starts, it checks for updates to the
43-
app by looking for updates to the `ngsw.json` manifest.
44-
45-
Note that the service worker starts periodically throughout the usage of
46-
the app because the web browser terminates the service worker if the page
47-
is idle beyond a given timeout.
42+
Every time the user opens or refreshes the application, the Angular service worker
43+
checks for updates to the app by looking for updates to the `ngsw.json` manifest. If
44+
an update is found, it is downloaded and cached automatically, and will be served
45+
the next time the application is loaded.
4846

4947
### Resource integrity
5048

@@ -283,8 +281,8 @@ with service workers. Such tools can be powerful when used properly,
283281
but there are a few things to keep in mind.
284282

285283
* When using developer tools, the service worker is kept running
286-
in the background and never restarts. For the Angular service
287-
worker, this means that update checks to the app will generally not happen.
284+
in the background and never restarts. This can cause behavior with Dev
285+
Tools open to differ from behavior a user might experience.
288286

289287
* If you look in the Cache Storage viewer, the cache is frequently
290288
out of date. Right click the Cache Storage title and refresh the caches.

packages/service-worker/worker/src/driver.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,11 @@ export class Driver implements Debuggable, UpdateSource {
8888

8989
private lastUpdateCheck: number|null = null;
9090

91+
/**
92+
* Whether there is a check for updates currently scheduled due to navigation.
93+
*/
94+
private scheduledNavUpdateCheck: boolean = false;
95+
9196
/**
9297
* A scheduler which manages a queue of tasks that need to be executed when the SW is
9398
* not doing any other work (not processing any other requests).
@@ -327,6 +332,15 @@ export class Driver implements Debuggable, UpdateSource {
327332
return this.safeFetch(event.request);
328333
}
329334

335+
// On navigation requests, check for new updates.
336+
if (event.request.mode === 'navigate' && !this.scheduledNavUpdateCheck) {
337+
this.scheduledNavUpdateCheck = true;
338+
this.idle.schedule('check-updates-on-navigation', async() => {
339+
this.scheduledNavUpdateCheck = false;
340+
await this.checkForUpdate();
341+
});
342+
}
343+
330344
// Decide which version of the app to use to serve this request. This is asynchronous as in
331345
// some cases, a record will need to be written to disk about the assignment that is made.
332346
const appVersion = await this.assignVersion(event);

packages/service-worker/worker/test/happy_spec.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,41 @@ export function main() {
337337
serverUpdate.assertNoOtherRequests();
338338
});
339339

340+
async_it('checks for updates on navigation', async() => {
341+
expect(await makeRequest(scope, '/foo.txt')).toEqual('this is foo');
342+
await driver.initialized;
343+
server.clearRequests();
344+
345+
expect(await makeRequest(scope, '/foo.txt', 'default', {
346+
mode: 'navigate',
347+
})).toEqual('this is foo');
348+
349+
scope.advance(12000);
350+
await driver.idle.empty;
351+
352+
server.assertSawRequestFor('ngsw.json');
353+
});
354+
355+
async_it('does not make concurrent checks for updates on navigation', async() => {
356+
expect(await makeRequest(scope, '/foo.txt')).toEqual('this is foo');
357+
await driver.initialized;
358+
server.clearRequests();
359+
360+
expect(await makeRequest(scope, '/foo.txt', 'default', {
361+
mode: 'navigate',
362+
})).toEqual('this is foo');
363+
364+
expect(await makeRequest(scope, '/foo.txt', 'default', {
365+
mode: 'navigate',
366+
})).toEqual('this is foo');
367+
368+
scope.advance(12000);
369+
await driver.idle.empty;
370+
371+
server.assertSawRequestFor('ngsw.json');
372+
server.assertNoOtherRequests();
373+
});
374+
340375
async_it('preserves multiple client assignments across restarts', async() => {
341376
expect(await makeRequest(scope, '/foo.txt')).toEqual('this is foo');
342377
await driver.initialized;

0 commit comments

Comments
 (0)