Skip to content

Commit 0984b30

Browse files
SkyZeroZxthePunderWoman
authored andcommitted
feat(http): Add redirected property to HttpResponse and HttpErrorResponse (#62675)
Add support for the Fetch API's redirected property in HttpResponse and HttpErrorResponse when using HttpClient with the withFetch provider. The redirected property indicates whether the response was the result of an HTTP redirect, providing valuable information for security, debugging, and conditional logic. PR Close #62675
1 parent 6d01168 commit 0984b30

File tree

4 files changed

+39
-1
lines changed

4 files changed

+39
-1
lines changed

goldens/public-api/common/http/index.api.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2508,6 +2508,7 @@ export class HttpErrorResponse extends HttpResponseBase implements Error {
25082508
status?: number;
25092509
statusText?: string;
25102510
url?: string;
2511+
redirected?: boolean;
25112512
});
25122513
// (undocumented)
25132514
readonly error: any | null;
@@ -2931,6 +2932,7 @@ export class HttpResponse<T> extends HttpResponseBase {
29312932
status?: number;
29322933
statusText?: string;
29332934
url?: string;
2935+
redirected?: boolean;
29342936
});
29352937
readonly body: T | null;
29362938
// (undocumented)
@@ -2941,6 +2943,7 @@ export class HttpResponse<T> extends HttpResponseBase {
29412943
status?: number;
29422944
statusText?: string;
29432945
url?: string;
2946+
redirected?: boolean;
29442947
}): HttpResponse<T>;
29452948
// (undocumented)
29462949
clone<V>(update: {
@@ -2949,6 +2952,7 @@ export class HttpResponse<T> extends HttpResponseBase {
29492952
status?: number;
29502953
statusText?: string;
29512954
url?: string;
2955+
redirected?: boolean;
29522956
}): HttpResponse<V>;
29532957
// (undocumented)
29542958
readonly type: HttpEventType.Response;
@@ -2961,9 +2965,11 @@ export abstract class HttpResponseBase {
29612965
status?: number;
29622966
statusText?: string;
29632967
url?: string;
2968+
redirected?: boolean;
29642969
}, defaultStatus?: number, defaultStatusText?: string);
29652970
readonly headers: HttpHeaders;
29662971
readonly ok: boolean;
2972+
readonly redirected?: boolean;
29672973
readonly status: number;
29682974
readonly statusText: string;
29692975
readonly type: HttpEventType.Response | HttpEventType.ResponseHeader;

packages/common/http/src/fetch.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,8 @@ export class FetchBackend implements HttpBackend {
271271
// asked for JSON data and the body cannot be parsed as such.
272272
const ok = status >= 200 && status < 300;
273273

274+
const redirected = response.redirected;
275+
274276
if (ok) {
275277
observer.next(
276278
new HttpResponse({
@@ -279,6 +281,7 @@ export class FetchBackend implements HttpBackend {
279281
status,
280282
statusText,
281283
url,
284+
redirected,
282285
}),
283286
);
284287

@@ -293,6 +296,7 @@ export class FetchBackend implements HttpBackend {
293296
status,
294297
statusText,
295298
url,
299+
redirected,
296300
}),
297301
);
298302
}

packages/common/http/src/response.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,13 @@ export abstract class HttpResponseBase {
185185
*/
186186
readonly type!: HttpEventType.Response | HttpEventType.ResponseHeader;
187187

188+
/**
189+
* Indicates whether the HTTP response was redirected during the request.
190+
* This property is only available when using the Fetch API using `withFetch()`
191+
* When using the default XHR Request this property will be `undefined`
192+
*/
193+
readonly redirected?: boolean;
194+
188195
/**
189196
* Super-constructor for all responses.
190197
*
@@ -197,6 +204,7 @@ export abstract class HttpResponseBase {
197204
status?: number;
198205
statusText?: string;
199206
url?: string;
207+
redirected?: boolean;
200208
},
201209
defaultStatus: number = 200,
202210
defaultStatusText: string = 'OK',
@@ -207,6 +215,7 @@ export abstract class HttpResponseBase {
207215
this.status = init.status !== undefined ? init.status : defaultStatus;
208216
this.statusText = init.statusText || defaultStatusText;
209217
this.url = init.url || null;
218+
this.redirected = init.redirected;
210219

211220
// Cache the ok value to avoid defining a getter.
212221
this.ok = this.status >= 200 && this.status < 300;
@@ -244,7 +253,12 @@ export class HttpHeaderResponse extends HttpResponseBase {
244253
* given parameter hash.
245254
*/
246255
clone(
247-
update: {headers?: HttpHeaders; status?: number; statusText?: string; url?: string} = {},
256+
update: {
257+
headers?: HttpHeaders;
258+
status?: number;
259+
statusText?: string;
260+
url?: string;
261+
} = {},
248262
): HttpHeaderResponse {
249263
// Perform a straightforward initialization of the new HttpHeaderResponse,
250264
// overriding the current parameters with new ones if given.
@@ -282,6 +296,7 @@ export class HttpResponse<T> extends HttpResponseBase {
282296
status?: number;
283297
statusText?: string;
284298
url?: string;
299+
redirected?: boolean;
285300
} = {},
286301
) {
287302
super(init);
@@ -296,13 +311,15 @@ export class HttpResponse<T> extends HttpResponseBase {
296311
status?: number;
297312
statusText?: string;
298313
url?: string;
314+
redirected?: boolean;
299315
}): HttpResponse<T>;
300316
clone<V>(update: {
301317
body?: V | null;
302318
headers?: HttpHeaders;
303319
status?: number;
304320
statusText?: string;
305321
url?: string;
322+
redirected?: boolean;
306323
}): HttpResponse<V>;
307324
clone(
308325
update: {
@@ -311,6 +328,7 @@ export class HttpResponse<T> extends HttpResponseBase {
311328
status?: number;
312329
statusText?: string;
313330
url?: string;
331+
redirected?: boolean;
314332
} = {},
315333
): HttpResponse<any> {
316334
return new HttpResponse<any>({
@@ -319,6 +337,7 @@ export class HttpResponse<T> extends HttpResponseBase {
319337
status: update.status !== undefined ? update.status : this.status,
320338
statusText: update.statusText || this.statusText,
321339
url: update.url || this.url || undefined,
340+
redirected: update.redirected ?? this.redirected,
322341
});
323342
}
324343
}
@@ -352,6 +371,7 @@ export class HttpErrorResponse extends HttpResponseBase implements Error {
352371
status?: number;
353372
statusText?: string;
354373
url?: string;
374+
redirected?: boolean;
355375
}) {
356376
// Initialize with a default status of 0 / Unknown Error.
357377
super(init, 0, 'Unknown Error');

packages/common/http/test/response_spec.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,15 @@ describe('HttpResponse', () => {
2020
status: HttpStatusCode.Created,
2121
statusText: 'Created',
2222
url: '/test',
23+
redirected: true,
2324
});
2425
expect(resp.body).toBe('test body');
2526
expect(resp.headers instanceof HttpHeaders).toBeTruthy();
2627
expect(resp.headers.get('Test')).toBe('Test header');
2728
expect(resp.status).toBe(HttpStatusCode.Created);
2829
expect(resp.statusText).toBe('Created');
2930
expect(resp.url).toBe('/test');
31+
expect(resp.redirected).toBe(true);
3032
});
3133
it('uses defaults if no args passed', () => {
3234
const resp = new HttpResponse({});
@@ -36,6 +38,7 @@ describe('HttpResponse', () => {
3638
expect(resp.body).toBeNull();
3739
expect(resp.ok).toBeTruthy();
3840
expect(resp.url).toBeNull();
41+
expect(resp.redirected).toBeUndefined();
3942
});
4043
it('accepts a falsy body', () => {
4144
expect(new HttpResponse({body: false}).body).toEqual(false);
@@ -59,31 +62,36 @@ describe('HttpResponse', () => {
5962
status: HttpStatusCode.Created,
6063
statusText: 'created',
6164
url: '/test',
65+
redirected: false,
6266
}).clone();
6367
expect(clone.body).toBe('test');
6468
expect(clone.status).toBe(HttpStatusCode.Created);
6569
expect(clone.statusText).toBe('created');
6670
expect(clone.url).toBe('/test');
6771
expect(clone.headers).not.toBeNull();
72+
expect(clone.redirected).toBe(false);
6873
});
6974
it('overrides the original', () => {
7075
const orig = new HttpResponse({
7176
body: 'test',
7277
status: HttpStatusCode.Created,
7378
statusText: 'created',
7479
url: '/test',
80+
redirected: true,
7581
});
7682
const clone = orig.clone({
7783
body: {data: 'test'},
7884
status: HttpStatusCode.Ok,
7985
statusText: 'Okay',
8086
url: '/bar',
87+
redirected: false,
8188
});
8289
expect(clone.body).toEqual({data: 'test'});
8390
expect(clone.status).toBe(HttpStatusCode.Ok);
8491
expect(clone.statusText).toBe('Okay');
8592
expect(clone.url).toBe('/bar');
8693
expect(clone.headers).toBe(orig.headers);
94+
expect(clone.redirected).toBe(false);
8795
});
8896
});
8997
});

0 commit comments

Comments
 (0)