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:
parent
304a12f027
commit
6200732e23
|
@ -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,
|
service worker requires the invalidation of old caches. In this case,
|
||||||
the app will be refreshed transparently from the network.
|
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
|
## Debugging the Angular service worker
|
||||||
|
|
||||||
|
|
|
@ -52,9 +52,9 @@ export class Adapter {
|
||||||
/**
|
/**
|
||||||
* Extract the pathname of a URL.
|
* 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);
|
const parsed = new URL(url, relativeTo);
|
||||||
return {origin: parsed.origin, path: parsed.pathname};
|
return {origin: parsed.origin, path: parsed.pathname, search: parsed.search};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -179,6 +179,10 @@ export class Driver implements Debuggable, UpdateSource {
|
||||||
const scopeUrl = this.scope.registration.scope;
|
const scopeUrl = this.scope.registration.scope;
|
||||||
const requestUrlObj = this.adapter.parseUrl(req.url, scopeUrl);
|
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.
|
// The only thing that is served unconditionally is the debug page.
|
||||||
if (requestUrlObj.path === '/ngsw/state') {
|
if (requestUrlObj.path === '/ngsw/state') {
|
||||||
// Allow the debugger to handle the request, but don't affect SW state in any other way.
|
// Allow the debugger to handle the request, but don't affect SW state in any other way.
|
||||||
|
|
|
@ -711,6 +711,74 @@ import {async_beforeEach, async_fit, async_it} from './async';
|
||||||
serverUpdate.assertNoOtherRequests();
|
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() => {
|
async_it('unregisters when manifest 404s', async() => {
|
||||||
expect(await makeRequest(scope, '/foo.txt')).toEqual('this is foo');
|
expect(await makeRequest(scope, '/foo.txt')).toEqual('this is foo');
|
||||||
await driver.initialized;
|
await driver.initialized;
|
||||||
|
|
|
@ -61,21 +61,21 @@ export class MockHeaders implements Headers {
|
||||||
|
|
||||||
[Symbol.iterator]() { return this.map[Symbol.iterator](); }
|
[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(); }
|
entries() { return this.map.entries(); }
|
||||||
|
|
||||||
forEach(callback: Function): void { this.map.forEach(callback as any); }
|
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(); }
|
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(); }
|
values() { return this.map.values(); }
|
||||||
}
|
}
|
||||||
|
|
|
@ -184,7 +184,7 @@ export class SwTestHarness implements ServiceWorkerGlobalScope, Adapter, Context
|
||||||
}, new MockHeaders());
|
}, 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') ?
|
const parsedUrl: URL = (typeof URL === 'function') ?
|
||||||
new URL(url, relativeTo) :
|
new URL(url, relativeTo) :
|
||||||
require('url').parse(require('url').resolve(relativeTo || '', url));
|
require('url').parse(require('url').resolve(relativeTo || '', url));
|
||||||
|
@ -192,6 +192,7 @@ export class SwTestHarness implements ServiceWorkerGlobalScope, Adapter, Context
|
||||||
return {
|
return {
|
||||||
origin: parsedUrl.origin || `${parsedUrl.protocol}//${parsedUrl.host}`,
|
origin: parsedUrl.origin || `${parsedUrl.protocol}//${parsedUrl.host}`,
|
||||||
path: parsedUrl.pathname,
|
path: parsedUrl.pathname,
|
||||||
|
search: parsedUrl.search || '',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue