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,
|
||||
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
|
||||
|
||||
|
|
|
@ -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};
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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(); }
|
||||
}
|
||||
|
|
|
@ -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 || '',
|
||||
};
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue