fix(service-worker): correctly serve `ngsw/state` with a non-root SW scope (#37922)

The Angular ServiceWorker can serve requests to a special virtual path,
`ngsw/state`, showing [information about its internal state][1], which
can be useful for debugging.

Previously, this would only work if the ServiceWorker's [scope][2] was
the root directory (`/`). Otherwise, (e.g. when building the app with
`--baseHref=/some/path/`), the ServiceWorker would fail to detect a
request to `/some/path/ngsw/state` as matching `ngsw/state` and would
not serve it with the debugging information.

This commit fixes it by ensuring that the ServiceWorker's scope is taken
into account when detecting a request to `ngsw/state`.

[1]: https://angular.io/guide/service-worker-devops#locating-and-analyzing-debugging-information
[2]: https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerRegistration/scope

Fixes #30505

PR Close #37922
This commit is contained in:
George Kalpakas 2020-07-06 16:55:35 +03:00 committed by atscott
parent b98314e306
commit 2156beed0c
2 changed files with 31 additions and 1 deletions

View File

@ -99,6 +99,8 @@ export class Driver implements Debuggable, UpdateSource {
*/
private loggedInvalidOnlyIfCachedRequest: boolean = false;
private ngswStatePath = this.adapter.parseUrl('ngsw/state', this.scope.registration.scope).path;
/**
* A scheduler which manages a queue of tasks that need to be executed when the SW is
* not doing any other work (not processing any other requests).
@ -184,7 +186,7 @@ export class Driver implements Debuggable, UpdateSource {
}
// The only thing that is served unconditionally is the debug page.
if (requestUrlObj.path === '/ngsw/state') {
if (requestUrlObj.path === this.ngswStatePath) {
// Allow the debugger to handle the request, but don't affect SW state in any other way.
event.respondWith(this.debugger.handleFetch(req));
return;

View File

@ -893,6 +893,34 @@ describe('Driver', () => {
expect(await scope.caches.keys()).not.toEqual([]);
});
describe('serving ngsw/state', () => {
it('should show debug info (when in NORMAL state)', async () => {
expect(await makeRequest(scope, '/ngsw/state'))
.toMatch(/^NGSW Debug Info:\n\nDriver state: NORMAL/);
});
it('should show debug info (when in EXISTING_CLIENTS_ONLY state)', async () => {
driver.state = DriverReadyState.EXISTING_CLIENTS_ONLY;
expect(await makeRequest(scope, '/ngsw/state'))
.toMatch(/^NGSW Debug Info:\n\nDriver state: EXISTING_CLIENTS_ONLY/);
});
it('should show debug info (when in SAFE_MODE state)', async () => {
driver.state = DriverReadyState.SAFE_MODE;
expect(await makeRequest(scope, '/ngsw/state'))
.toMatch(/^NGSW Debug Info:\n\nDriver state: SAFE_MODE/);
});
it('should show debug info when the scope is not root', async () => {
const newScope =
new SwTestHarnessBuilder('http://localhost/foo/bar/').withServerState(server).build();
new Driver(newScope, newScope, new CacheDatabase(newScope, newScope));
expect(await makeRequest(newScope, '/foo/bar/ngsw/state'))
.toMatch(/^NGSW Debug Info:\n\nDriver state: NORMAL/);
});
});
describe('cache naming', () => {
// Helpers
const cacheKeysFor = (baseHref: string) =>