Skip to content

Commit 6200732

Browse files
petersalomonsenAndrewKushnir
authored andcommittedApr 25, 2019
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
1 parent 304a12f commit 6200732

File tree

6 files changed

+90
-8
lines changed

6 files changed

+90
-8
lines changed
 

‎aio/content/guide/service-worker-devops.md

+9
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,15 @@ normally. However, occasionally a bugfix or feature in the Angular
147147
service worker requires the invalidation of old caches. In this case,
148148
the app will be refreshed transparently from the network.
149149

150+
### Bypassing the service worker
151+
152+
In some cases, you may want to bypass the service worker entirely and let the browser handle the
153+
request instead. An example is when you rely on a feature that is currently not supported in service
154+
workers (e.g.
155+
[reporting progress on uploaded files](https://github.com/w3c/ServiceWorker/issues/1141)).
156+
157+
To bypass the service worker you can set `ngsw-bypass` as a request header, or as a query parameter.
158+
(The value of the header or query parameter is ignored and can be empty or omitted.)
150159

151160
## Debugging the Angular service worker
152161

‎packages/service-worker/worker/src/adapter.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,9 @@ export class Adapter {
5252
/**
5353
* Extract the pathname of a URL.
5454
*/
55-
parseUrl(url: string, relativeTo?: string): {origin: string, path: string} {
55+
parseUrl(url: string, relativeTo?: string): {origin: string, path: string, search: string} {
5656
const parsed = new URL(url, relativeTo);
57-
return {origin: parsed.origin, path: parsed.pathname};
57+
return {origin: parsed.origin, path: parsed.pathname, search: parsed.search};
5858
}
5959

6060
/**

‎packages/service-worker/worker/src/driver.ts

+4
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,10 @@ export class Driver implements Debuggable, UpdateSource {
179179
const scopeUrl = this.scope.registration.scope;
180180
const requestUrlObj = this.adapter.parseUrl(req.url, scopeUrl);
181181

182+
if (req.headers.has('ngsw-bypass') || /[?&]ngsw-bypass(?:[=&]|$)/i.test(requestUrlObj.search)) {
183+
return;
184+
}
185+
182186
// The only thing that is served unconditionally is the debug page.
183187
if (requestUrlObj.path === '/ngsw/state') {
184188
// Allow the debugger to handle the request, but don't affect SW state in any other way.

‎packages/service-worker/worker/test/happy_spec.ts

+68
Original file line numberDiff line numberDiff line change
@@ -711,6 +711,74 @@ import {async_beforeEach, async_fit, async_it} from './async';
711711
serverUpdate.assertNoOtherRequests();
712712
});
713713

714+
async_it('should bypass serviceworker on ngsw-bypass parameter', async() => {
715+
await makeRequest(scope, '/foo.txt', undefined, {headers: {'ngsw-bypass': 'true'}});
716+
server.assertNoRequestFor('/foo.txt');
717+
718+
await makeRequest(scope, '/foo.txt', undefined, {headers: {'ngsw-bypass': 'anything'}});
719+
server.assertNoRequestFor('/foo.txt');
720+
721+
await makeRequest(scope, '/foo.txt', undefined, {headers: {'ngsw-bypass': null}});
722+
server.assertNoRequestFor('/foo.txt');
723+
724+
await makeRequest(scope, '/foo.txt', undefined, {headers: {'NGSW-bypass': 'upperCASE'}});
725+
server.assertNoRequestFor('/foo.txt');
726+
727+
await makeRequest(scope, '/foo.txt', undefined, {headers: {'ngsw-bypasss': 'anything'}});
728+
server.assertSawRequestFor('/foo.txt');
729+
730+
server.clearRequests();
731+
732+
await makeRequest(scope, '/bar.txt?ngsw-bypass=true');
733+
server.assertNoRequestFor('/bar.txt');
734+
735+
await makeRequest(scope, '/bar.txt?ngsw-bypasss=true');
736+
server.assertSawRequestFor('/bar.txt');
737+
738+
server.clearRequests();
739+
740+
await makeRequest(scope, '/bar.txt?ngsw-bypaSS=something');
741+
server.assertNoRequestFor('/bar.txt');
742+
743+
await makeRequest(scope, '/bar.txt?testparam=test&ngsw-byPASS=anything');
744+
server.assertNoRequestFor('/bar.txt');
745+
746+
await makeRequest(scope, '/bar.txt?testparam=test&angsw-byPASS=anything');
747+
server.assertSawRequestFor('/bar.txt');
748+
749+
server.clearRequests();
750+
751+
await makeRequest(scope, '/bar&ngsw-bypass=true.txt?testparam=test&angsw-byPASS=anything');
752+
server.assertSawRequestFor('/bar&ngsw-bypass=true.txt');
753+
754+
server.clearRequests();
755+
756+
await makeRequest(scope, '/bar&ngsw-bypass=true.txt');
757+
server.assertSawRequestFor('/bar&ngsw-bypass=true.txt');
758+
759+
server.clearRequests();
760+
761+
await makeRequest(
762+
scope, '/bar&ngsw-bypass=true.txt?testparam=test&ngSW-BYPASS=SOMETHING&testparam2=test');
763+
server.assertNoRequestFor('/bar&ngsw-bypass=true.txt');
764+
765+
await makeRequest(scope, '/bar?testparam=test&ngsw-bypass');
766+
server.assertNoRequestFor('/bar');
767+
768+
await makeRequest(scope, '/bar?testparam=test&ngsw-bypass&testparam2');
769+
server.assertNoRequestFor('/bar');
770+
771+
await makeRequest(scope, '/bar?ngsw-bypass&testparam2');
772+
server.assertNoRequestFor('/bar');
773+
774+
await makeRequest(scope, '/bar?ngsw-bypass=&foo=ngsw-bypass');
775+
server.assertNoRequestFor('/bar');
776+
777+
await makeRequest(scope, '/bar?ngsw-byapass&testparam2');
778+
server.assertSawRequestFor('/bar');
779+
780+
});
781+
714782
async_it('unregisters when manifest 404s', async() => {
715783
expect(await makeRequest(scope, '/foo.txt')).toEqual('this is foo');
716784
await driver.initialized;

‎packages/service-worker/worker/testing/fetch.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -61,21 +61,21 @@ export class MockHeaders implements Headers {
6161

6262
[Symbol.iterator]() { return this.map[Symbol.iterator](); }
6363

64-
append(name: string, value: string): void { this.map.set(name, value); }
64+
append(name: string, value: string): void { this.map.set(name.toLowerCase(), value); }
6565

66-
delete (name: string): void { this.map.delete(name); }
66+
delete (name: string): void { this.map.delete(name.toLowerCase()); }
6767

6868
entries() { return this.map.entries(); }
6969

7070
forEach(callback: Function): void { this.map.forEach(callback as any); }
7171

72-
get(name: string): string|null { return this.map.get(name) || null; }
72+
get(name: string): string|null { return this.map.get(name.toLowerCase()) || null; }
7373

74-
has(name: string): boolean { return this.map.has(name); }
74+
has(name: string): boolean { return this.map.has(name.toLowerCase()); }
7575

7676
keys() { return this.map.keys(); }
7777

78-
set(name: string, value: string): void { this.map.set(name, value); }
78+
set(name: string, value: string): void { this.map.set(name.toLowerCase(), value); }
7979

8080
values() { return this.map.values(); }
8181
}

‎packages/service-worker/worker/testing/scope.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -184,14 +184,15 @@ export class SwTestHarness implements ServiceWorkerGlobalScope, Adapter, Context
184184
}, new MockHeaders());
185185
}
186186

187-
parseUrl(url: string, relativeTo?: string): {origin: string, path: string} {
187+
parseUrl(url: string, relativeTo?: string): {origin: string, path: string, search: string} {
188188
const parsedUrl: URL = (typeof URL === 'function') ?
189189
new URL(url, relativeTo) :
190190
require('url').parse(require('url').resolve(relativeTo || '', url));
191191

192192
return {
193193
origin: parsedUrl.origin || `${parsedUrl.protocol}//${parsedUrl.host}`,
194194
path: parsedUrl.pathname,
195+
search: parsedUrl.search || '',
195196
};
196197
}
197198

0 commit comments

Comments
 (0)
Please sign in to comment.