diff --git a/packages/service-worker/worker/src/assets.ts b/packages/service-worker/worker/src/assets.ts index f380f56a2286..54fb4f66dc60 100644 --- a/packages/service-worker/worker/src/assets.ts +++ b/packages/service-worker/worker/src/assets.ts @@ -501,21 +501,35 @@ export abstract class AssetGroup { * Create a new `Request` based on the specified URL and `RequestInit` options, preserving only * metadata that are known to be safe. * - * Currently, only headers and redirect policy are preserved. + * Currently, headers, redirect policy, an explicit `credentials: 'omit'`, and the HTTP cache + * mode are preserved. * * NOTE: - * Things like credential inclusion are intentionally omitted to avoid issues with opaque - * responses. - * + * `credentials: 'same-origin'` and `credentials: 'include'` are intentionally not preserved. + * Forwarding `'include'` could leak cookies to cross-origin asset hosts, and forwarding + * `'same-origin'` matches the default `fetch()` behavior so there is nothing to preserve. + * Requests with `cache: 'only-if-cached'` and `mode !== 'same-origin'` are short-circuited + * earlier in `Driver.onFetch()` (they are a known Chrome DevTools quirk), so no special + * handling for that combination is needed here. * TODO(gkalpak): * Investigate preserving more metadata. See, also, discussion on preserving `mode`: - * https://github.com/angular/angular/issues/41931#issuecomment-1227601347 + * https://github.com/angular/angular/issues/41931#issuecomment-1227601347. */ private newRequestWithMetadata(url: string, options: RequestInit): Request { - return this.adapter.newRequest(url, { + const init: RequestInit = { headers: options.headers, redirect: options.redirect, - }); + }; + + if (options.credentials === 'omit') { + init.credentials = 'omit'; + } + + if (options.cache !== undefined) { + init.cache = options.cache; + } + + return this.adapter.newRequest(url, init); } /** diff --git a/packages/service-worker/worker/test/happy_spec.ts b/packages/service-worker/worker/test/happy_spec.ts index 06bc2c329769..9fbf8314f92e 100644 --- a/packages/service-worker/worker/test/happy_spec.ts +++ b/packages/service-worker/worker/test/happy_spec.ts @@ -1652,6 +1652,30 @@ import {envIsSupported} from '../testing/utils'; expect((bazReq as any).unknownOption).toBeUndefined(); }); + it(`passes 'credentials: omit' through to the server`, async () => { + // Request a lazy-cached asset (so that it is fetched from the network) and provide an + // explicit anonymous credentials mode. + const reqInit = {credentials: 'omit'}; + expect(await makeRequest(scope, '/baz.txt', undefined, reqInit)).toBe('this is baz'); + + // Verify that the explicit `'omit'` value was preserved (instead of being replaced by the + // default `'same-origin'`). + const [bazReq] = server.getRequestsFor('/baz.txt'); + expect(bazReq.credentials).toBe('omit'); + }); + + it(`passes 'cache' through to the server`, async () => { + // Request a lazy-cached asset (so that it is fetched from the network) and provide an + // explicit HTTP cache mode. + const reqInit = {cache: 'no-store'}; + expect(await makeRequest(scope, '/baz.txt', undefined, reqInit)).toBe('this is baz'); + + // Verify that the explicit `cache` value was preserved (instead of being replaced by the + // default `'default'`). + const [bazReq] = server.getRequestsFor('/baz.txt'); + expect(bazReq.cache).toBe('no-store'); + }); + describe('for redirect requests', () => { it('passes headers through to the server', async () => { // Request a redirected, lazy-cached asset (so that it is fetched from the network) and @@ -1692,6 +1716,34 @@ import {envIsSupported} from '../testing/utils'; makeRequest(scope, '/lazy/redirected.txt', undefined, {redirect: 'error'}), ).toBeRejected(); }); + + it(`passes 'credentials: omit' through to the server`, async () => { + // Request a redirected, lazy-cached asset (so that it is fetched from the network) and + // provide an explicit anonymous credentials mode. + const reqInit = {credentials: 'omit'}; + expect(await makeRequest(scope, '/lazy/redirected.txt', undefined, reqInit)).toBe( + 'this was a redirect too', + ); + + // Verify that the explicit `'omit'` value was preserved across the redirect + // reconstruction (instead of being replaced by the default `'same-origin'`). + const [redirectReq] = server.getRequestsFor('/lazy/redirect-target.txt'); + expect(redirectReq.credentials).toBe('omit'); + }); + + it(`passes 'cache' through to the server`, async () => { + // Request a redirected, lazy-cached asset (so that it is fetched from the network) and + // provide an explicit HTTP cache mode. + const reqInit = {cache: 'no-store'}; + expect(await makeRequest(scope, '/lazy/redirected.txt', undefined, reqInit)).toBe( + 'this was a redirect too', + ); + + // Verify that the explicit `cache` value was preserved across the redirect + // reconstruction (instead of being replaced by the default `'default'`). + const [redirectReq] = server.getRequestsFor('/lazy/redirect-target.txt'); + expect(redirectReq.cache).toBe('no-store'); + }); }); }); diff --git a/packages/service-worker/worker/testing/fetch.ts b/packages/service-worker/worker/testing/fetch.ts index 587cde897c6c..1f815436aa18 100644 --- a/packages/service-worker/worker/testing/fetch.ts +++ b/packages/service-worker/worker/testing/fetch.ts @@ -167,6 +167,7 @@ export class MockRequest extends MockBody implements Request { } return new MockRequest(this.url, { body: this._body, + cache: this.cache, mode: this.mode, credentials: this.credentials, headers: this.headers,