Skip to content
Permalink
Browse files

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
  • Loading branch information...
petersalomonsen authored and AndrewKushnir committed Apr 25, 2019
1 parent 304a12f commit 6200732e2360690532686c131add006cd29f3b61
@@ -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,14 +184,15 @@ 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));

return {
origin: parsedUrl.origin || `${parsedUrl.protocol}//${parsedUrl.host}`,
path: parsedUrl.pathname,
search: parsedUrl.search || '',
};
}

0 comments on commit 6200732

Please sign in to comment.
You can’t perform that action at this time.