feat(service-worker): support bypassing SW with specific header/query param (#30010)

Add support for bypassing the ServiceWorker for a request by using the
ngsw-bypass header or query parameter.

Fixes #21191

PR Close #30010
This commit is contained in:
Peter Johan Salomonsen 2019-04-25 22:51:10 +03:00 committed by Andrew Kushnir
parent 304a12f027
commit 6200732e23
6 changed files with 90 additions and 8 deletions

View File

@ -147,6 +147,15 @@ normally. However, occasionally a bugfix or feature in the Angular
service worker requires the invalidation of old caches. In this case,
the app will be refreshed transparently from the network.
### Bypassing the service worker
In some cases, you may want to bypass the service worker entirely and let the browser handle the
request instead. An example is when you rely on a feature that is currently not supported in service
workers (e.g.
[reporting progress on uploaded files](https://github.com/w3c/ServiceWorker/issues/1141)).
To bypass the service worker you can set `ngsw-bypass` as a request header, or as a query parameter.
(The value of the header or query parameter is ignored and can be empty or omitted.)
## Debugging the Angular service worker

View File

@ -52,9 +52,9 @@ export class Adapter {
/**
* Extract the pathname of a URL.
*/
parseUrl(url: string, relativeTo?: string): {origin: string, path: string} {
parseUrl(url: string, relativeTo?: string): {origin: string, path: string, search: string} {
const parsed = new URL(url, relativeTo);
return {origin: parsed.origin, path: parsed.pathname};
return {origin: parsed.origin, path: parsed.pathname, search: parsed.search};
}
/**

View File

@ -179,6 +179,10 @@ export class Driver implements Debuggable, UpdateSource {
const scopeUrl = this.scope.registration.scope;
const requestUrlObj = this.adapter.parseUrl(req.url, scopeUrl);
if (req.headers.has('ngsw-bypass') || /[?&]ngsw-bypass(?:[=&]|$)/i.test(requestUrlObj.search)) {
return;
}
// The only thing that is served unconditionally is the debug page.
if (requestUrlObj.path === '/ngsw/state') {
// Allow the debugger to handle the request, but don't affect SW state in any other way.

View File

@ -711,6 +711,74 @@ import {async_beforeEach, async_fit, async_it} from './async';
serverUpdate.assertNoOtherRequests();
});
async_it('should bypass serviceworker on ngsw-bypass parameter', async() => {
await makeRequest(scope, '/foo.txt', undefined, {headers: {'ngsw-bypass': 'true'}});
server.assertNoRequestFor('/foo.txt');
await makeRequest(scope, '/foo.txt', undefined, {headers: {'ngsw-bypass': 'anything'}});
server.assertNoRequestFor('/foo.txt');
await makeRequest(scope, '/foo.txt', undefined, {headers: {'ngsw-bypass': null}});
server.assertNoRequestFor('/foo.txt');
await makeRequest(scope, '/foo.txt', undefined, {headers: {'NGSW-bypass': 'upperCASE'}});
server.assertNoRequestFor('/foo.txt');
await makeRequest(scope, '/foo.txt', undefined, {headers: {'ngsw-bypasss': 'anything'}});
server.assertSawRequestFor('/foo.txt');
server.clearRequests();
await makeRequest(scope, '/bar.txt?ngsw-bypass=true');
server.assertNoRequestFor('/bar.txt');
await makeRequest(scope, '/bar.txt?ngsw-bypasss=true');
server.assertSawRequestFor('/bar.txt');
server.clearRequests();
await makeRequest(scope, '/bar.txt?ngsw-bypaSS=something');
server.assertNoRequestFor('/bar.txt');
await makeRequest(scope, '/bar.txt?testparam=test&ngsw-byPASS=anything');
server.assertNoRequestFor('/bar.txt');
await makeRequest(scope, '/bar.txt?testparam=test&angsw-byPASS=anything');
server.assertSawRequestFor('/bar.txt');
server.clearRequests();
await makeRequest(scope, '/bar&ngsw-bypass=true.txt?testparam=test&angsw-byPASS=anything');
server.assertSawRequestFor('/bar&ngsw-bypass=true.txt');
server.clearRequests();
await makeRequest(scope, '/bar&ngsw-bypass=true.txt');
server.assertSawRequestFor('/bar&ngsw-bypass=true.txt');
server.clearRequests();
await makeRequest(
scope, '/bar&ngsw-bypass=true.txt?testparam=test&ngSW-BYPASS=SOMETHING&testparam2=test');
server.assertNoRequestFor('/bar&ngsw-bypass=true.txt');
await makeRequest(scope, '/bar?testparam=test&ngsw-bypass');
server.assertNoRequestFor('/bar');
await makeRequest(scope, '/bar?testparam=test&ngsw-bypass&testparam2');
server.assertNoRequestFor('/bar');
await makeRequest(scope, '/bar?ngsw-bypass&testparam2');
server.assertNoRequestFor('/bar');
await makeRequest(scope, '/bar?ngsw-bypass=&foo=ngsw-bypass');
server.assertNoRequestFor('/bar');
await makeRequest(scope, '/bar?ngsw-byapass&testparam2');
server.assertSawRequestFor('/bar');
});
async_it('unregisters when manifest 404s', async() => {
expect(await makeRequest(scope, '/foo.txt')).toEqual('this is foo');
await driver.initialized;

View File

@ -61,21 +61,21 @@ export class MockHeaders implements Headers {
[Symbol.iterator]() { return this.map[Symbol.iterator](); }
append(name: string, value: string): void { this.map.set(name, value); }
append(name: string, value: string): void { this.map.set(name.toLowerCase(), value); }
delete (name: string): void { this.map.delete(name); }
delete (name: string): void { this.map.delete(name.toLowerCase()); }
entries() { return this.map.entries(); }
forEach(callback: Function): void { this.map.forEach(callback as any); }
get(name: string): string|null { return this.map.get(name) || null; }
get(name: string): string|null { return this.map.get(name.toLowerCase()) || null; }
has(name: string): boolean { return this.map.has(name); }
has(name: string): boolean { return this.map.has(name.toLowerCase()); }
keys() { return this.map.keys(); }
set(name: string, value: string): void { this.map.set(name, value); }
set(name: string, value: string): void { this.map.set(name.toLowerCase(), value); }
values() { return this.map.values(); }
}

View File

@ -184,7 +184,7 @@ export class SwTestHarness implements ServiceWorkerGlobalScope, Adapter, Context
}, new MockHeaders());
}
parseUrl(url: string, relativeTo?: string): {origin: string, path: string} {
parseUrl(url: string, relativeTo?: string): {origin: string, path: string, search: string} {
const parsedUrl: URL = (typeof URL === 'function') ?
new URL(url, relativeTo) :
require('url').parse(require('url').resolve(relativeTo || '', url));
@ -192,6 +192,7 @@ export class SwTestHarness implements ServiceWorkerGlobalScope, Adapter, Context
return {
origin: parsedUrl.origin || `${parsedUrl.protocol}//${parsedUrl.host}`,
path: parsedUrl.pathname,
search: parsedUrl.search || '',
};
}