Skip to content

Commit

Permalink
feat(service-worker): support bypassing SW with specific header/query…
Browse files Browse the repository at this point in the history
… 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 6200732
Show file tree
Hide file tree
Showing 6 changed files with 90 additions and 8 deletions.
9 changes: 9 additions & 0 deletions aio/content/guide/service-worker-devops.md
Expand Up @@ -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

Expand Down
4 changes: 2 additions & 2 deletions packages/service-worker/worker/src/adapter.ts
Expand Up @@ -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};
}

/**
Expand Down
4 changes: 4 additions & 0 deletions packages/service-worker/worker/src/driver.ts
Expand Up @@ -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.
Expand Down
68 changes: 68 additions & 0 deletions packages/service-worker/worker/test/happy_spec.ts
Expand Up @@ -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;
Expand Down
10 changes: 5 additions & 5 deletions packages/service-worker/worker/testing/fetch.ts
Expand Up @@ -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(); }
}
Expand Down
3 changes: 2 additions & 1 deletion packages/service-worker/worker/testing/scope.ts
Expand Up @@ -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 || '',
};
}

Expand Down

0 comments on commit 6200732

Please sign in to comment.