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

Add support for bypassing the ServiceWorker for a request by using the
ngsw-bypass header or query parameter.

Fixes angular#21191
  • Loading branch information
petersalomonsen authored and gkalpak committed Apr 25, 2019
1 parent 304a12f commit b42cd81
Show file tree
Hide file tree
Showing 6 changed files with 88 additions and 8 deletions.
7 changes: 7 additions & 0 deletions aio/content/guide/service-worker-devops.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,13 @@ 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 avoid that the service worker is
handling the request. An example is when uploading files, where
you will not see progress events if the request is going through
the service worker. To bypass the serviceworker you can set `ngsw-bypass`
as a request header, or as a query parameter.

## Debugging the Angular service worker

Expand Down
4 changes: 2 additions & 2 deletions packages/service-worker/worker/src/adapter.ts
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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 b42cd81

Please sign in to comment.