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

@ -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 || '',
};
}