From 58532fe36422cafcea75cf9a5e6434e8877a1d6e Mon Sep 17 00:00:00 2001 From: Yenya030 <211584612+Yenya030@users.noreply.github.com> Date: Fri, 6 Mar 2026 11:14:14 -0600 Subject: [PATCH 1/2] fix(service-worker): preserve redirect policy on reconstructed asset requests Preserve the redirect mode when rebuilding asset requests in newRequestWithMetadata(). This keeps explicit redirect:error semantics intact across service-worker redirect handling. Update the worker test mocks to model redirect defaults correctly and add focused regression coverage for redirected lazy assets with redirect:error. --- packages/service-worker/worker/src/assets.ts | 7 +++++-- packages/service-worker/worker/test/happy_spec.ts | 6 ++++++ packages/service-worker/worker/testing/fetch.ts | 6 +++++- packages/service-worker/worker/testing/mock.ts | 6 +++++- 4 files changed, 21 insertions(+), 4 deletions(-) diff --git a/packages/service-worker/worker/src/assets.ts b/packages/service-worker/worker/src/assets.ts index b3d220eb0bcc..f380f56a2286 100644 --- a/packages/service-worker/worker/src/assets.ts +++ b/packages/service-worker/worker/src/assets.ts @@ -501,7 +501,7 @@ 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 are preserved. + * Currently, only headers and redirect policy are preserved. * * NOTE: * Things like credential inclusion are intentionally omitted to avoid issues with opaque @@ -512,7 +512,10 @@ export abstract class AssetGroup { * https://github.com/angular/angular/issues/41931#issuecomment-1227601347 */ private newRequestWithMetadata(url: string, options: RequestInit): Request { - return this.adapter.newRequest(url, {headers: options.headers}); + return this.adapter.newRequest(url, { + headers: options.headers, + redirect: options.redirect, + }); } /** diff --git a/packages/service-worker/worker/test/happy_spec.ts b/packages/service-worker/worker/test/happy_spec.ts index 0b791d021b0a..06bc2c329769 100644 --- a/packages/service-worker/worker/test/happy_spec.ts +++ b/packages/service-worker/worker/test/happy_spec.ts @@ -1686,6 +1686,12 @@ import {envIsSupported} from '../testing/utils'; expect(redirectReq.mode).toBe('cors'); // The default value. expect((redirectReq as any).unknownOption).toBeUndefined(); }); + + it('does not follow redirects when redirect policy is error', async () => { + await expectAsync( + makeRequest(scope, '/lazy/redirected.txt', undefined, {redirect: 'error'}), + ).toBeRejected(); + }); }); }); diff --git a/packages/service-worker/worker/testing/fetch.ts b/packages/service-worker/worker/testing/fetch.ts index b103cfb11c2c..587cde897c6c 100644 --- a/packages/service-worker/worker/testing/fetch.ts +++ b/packages/service-worker/worker/testing/fetch.ts @@ -115,7 +115,7 @@ export class MockRequest extends MockBody implements Request { readonly keepalive: boolean = true; readonly method: string = 'GET'; readonly mode: RequestMode = 'cors'; - readonly redirect: RequestRedirect = 'error'; + readonly redirect: RequestRedirect = 'follow'; readonly referrer: string = ''; readonly referrerPolicy: ReferrerPolicy = 'no-referrer'; readonly signal: AbortSignal = null as any; @@ -153,6 +153,9 @@ export class MockRequest extends MockBody implements Request { if (init.method !== undefined) { this.method = init.method; } + if (init.redirect !== undefined) { + this.redirect = init.redirect; + } if (init.destination !== undefined) { this.destination = init.destination; } @@ -167,6 +170,7 @@ export class MockRequest extends MockBody implements Request { mode: this.mode, credentials: this.credentials, headers: this.headers, + redirect: this.redirect, }); } } diff --git a/packages/service-worker/worker/testing/mock.ts b/packages/service-worker/worker/testing/mock.ts index d3ae6c682797..ae51c4033811 100644 --- a/packages/service-worker/worker/testing/mock.ts +++ b/packages/service-worker/worker/testing/mock.ts @@ -165,7 +165,11 @@ export class MockServerState { } const url = req.url.split('?')[0]; if (this.resources.has(url)) { - return this.resources.get(url)!.clone(); + const response = this.resources.get(url)!.clone(); + if ((response as any).redirected && req.redirect === 'error') { + throw new Error('Redirect disallowed by request policy.'); + } + return response; } if (this.errors.has(url)) { throw new Error('Intentional failure!'); From 1e0880a98594873515571dc7c5c5e8560e5945ba Mon Sep 17 00:00:00 2001 From: Yenya030 <211584612+Yenya030@users.noreply.github.com> Date: Tue, 17 Mar 2026 10:50:49 -0500 Subject: [PATCH 2/2] refactor(service-worker): remove unnecessary cast in mock redirect check Use the typed Response.redirected property directly in the service-worker test mock instead of casting to any. --- packages/service-worker/worker/testing/mock.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/service-worker/worker/testing/mock.ts b/packages/service-worker/worker/testing/mock.ts index ae51c4033811..f16d0ec2913c 100644 --- a/packages/service-worker/worker/testing/mock.ts +++ b/packages/service-worker/worker/testing/mock.ts @@ -166,7 +166,7 @@ export class MockServerState { const url = req.url.split('?')[0]; if (this.resources.has(url)) { const response = this.resources.get(url)!.clone(); - if ((response as any).redirected && req.redirect === 'error') { + if (response.redirected && req.redirect === 'error') { throw new Error('Redirect disallowed by request policy.'); } return response;