@angular/common 20.2.3 → 21.0.0-next.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/common_module.d.d.ts +3 -7
- package/fesm2022/common.mjs +2 -2
- package/fesm2022/common.mjs.map +1 -1
- package/fesm2022/common_module.mjs +2 -11
- package/fesm2022/common_module.mjs.map +1 -1
- package/fesm2022/http/testing.mjs +1 -1
- package/fesm2022/http.mjs +2 -2
- package/fesm2022/location.mjs +1 -1
- package/fesm2022/module.mjs +1297 -1309
- package/fesm2022/module.mjs.map +1 -1
- package/fesm2022/platform_navigation.mjs +1 -1
- package/fesm2022/testing.mjs +1 -1
- package/fesm2022/upgrade.mjs +1 -1
- package/fesm2022/xhr.mjs +1 -1
- package/http/index.d.ts +32 -26
- package/http/testing/index.d.ts +1 -1
- package/index.d.ts +1 -1
- package/module.d.d.ts +2 -2
- package/package.json +2 -2
- package/platform_location.d.d.ts +1 -1
- package/testing/index.d.ts +1 -1
- package/upgrade/index.d.ts +1 -1
- package/xhr.d.d.ts +1 -1
package/fesm2022/module.mjs
CHANGED
|
@@ -1,42 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @license Angular
|
|
2
|
+
* @license Angular v21.0.0-next.1
|
|
3
3
|
* (c) 2010-2025 Google LLC. https://angular.io/
|
|
4
4
|
* License: MIT
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import * as i0 from '@angular/core';
|
|
8
|
-
import { ɵRuntimeError as _RuntimeError,
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
8
|
+
import { ɵRuntimeError as _RuntimeError, InjectionToken, inject, NgZone, DestroyRef, Injectable, ɵformatRuntimeError as _formatRuntimeError, runInInjectionContext, PendingTasks, ɵConsole as _Console, DOCUMENT, Inject, makeEnvironmentProviders, NgModule } from '@angular/core';
|
|
9
|
+
import { switchMap, finalize, concatMap, filter, map } from 'rxjs/operators';
|
|
10
|
+
import { Observable, from, of } from 'rxjs';
|
|
11
11
|
import { XhrFactory, parseCookieValue } from './xhr.mjs';
|
|
12
12
|
|
|
13
|
-
/**
|
|
14
|
-
* Transforms an `HttpRequest` into a stream of `HttpEvent`s, one of which will likely be a
|
|
15
|
-
* `HttpResponse`.
|
|
16
|
-
*
|
|
17
|
-
* `HttpHandler` is injectable. When injected, the handler instance dispatches requests to the
|
|
18
|
-
* first interceptor in the chain, which dispatches to the second, etc, eventually reaching the
|
|
19
|
-
* `HttpBackend`.
|
|
20
|
-
*
|
|
21
|
-
* In an `HttpInterceptor`, the `HttpHandler` parameter is the next interceptor in the chain.
|
|
22
|
-
*
|
|
23
|
-
* @publicApi
|
|
24
|
-
*/
|
|
25
|
-
class HttpHandler {
|
|
26
|
-
}
|
|
27
|
-
/**
|
|
28
|
-
* A final `HttpHandler` which will dispatch the request via browser HTTP APIs to a backend.
|
|
29
|
-
*
|
|
30
|
-
* Interceptors sit between the `HttpClient` interface and the `HttpBackend`.
|
|
31
|
-
*
|
|
32
|
-
* When injected, `HttpBackend` dispatches requests directly to the backend, without going
|
|
33
|
-
* through the interceptor chain.
|
|
34
|
-
*
|
|
35
|
-
* @publicApi
|
|
36
|
-
*/
|
|
37
|
-
class HttpBackend {
|
|
38
|
-
}
|
|
39
|
-
|
|
40
13
|
/**
|
|
41
14
|
* Represents the header configuration options for an HTTP request.
|
|
42
15
|
* Instances are immutable. Modifying methods return a cloned
|
|
@@ -288,6 +261,106 @@ function assertValidHeaders(headers) {
|
|
|
288
261
|
}
|
|
289
262
|
}
|
|
290
263
|
|
|
264
|
+
/**
|
|
265
|
+
* A token used to manipulate and access values stored in `HttpContext`.
|
|
266
|
+
*
|
|
267
|
+
* @publicApi
|
|
268
|
+
*/
|
|
269
|
+
class HttpContextToken {
|
|
270
|
+
defaultValue;
|
|
271
|
+
constructor(defaultValue) {
|
|
272
|
+
this.defaultValue = defaultValue;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Http context stores arbitrary user defined values and ensures type safety without
|
|
277
|
+
* actually knowing the types. It is backed by a `Map` and guarantees that keys do not clash.
|
|
278
|
+
*
|
|
279
|
+
* This context is mutable and is shared between cloned requests unless explicitly specified.
|
|
280
|
+
*
|
|
281
|
+
* @usageNotes
|
|
282
|
+
*
|
|
283
|
+
* ### Usage Example
|
|
284
|
+
*
|
|
285
|
+
* ```ts
|
|
286
|
+
* // inside cache.interceptors.ts
|
|
287
|
+
* export const IS_CACHE_ENABLED = new HttpContextToken<boolean>(() => false);
|
|
288
|
+
*
|
|
289
|
+
* export class CacheInterceptor implements HttpInterceptor {
|
|
290
|
+
*
|
|
291
|
+
* intercept(req: HttpRequest<any>, delegate: HttpHandler): Observable<HttpEvent<any>> {
|
|
292
|
+
* if (req.context.get(IS_CACHE_ENABLED) === true) {
|
|
293
|
+
* return ...;
|
|
294
|
+
* }
|
|
295
|
+
* return delegate.handle(req);
|
|
296
|
+
* }
|
|
297
|
+
* }
|
|
298
|
+
*
|
|
299
|
+
* // inside a service
|
|
300
|
+
*
|
|
301
|
+
* this.httpClient.get('/api/weather', {
|
|
302
|
+
* context: new HttpContext().set(IS_CACHE_ENABLED, true)
|
|
303
|
+
* }).subscribe(...);
|
|
304
|
+
* ```
|
|
305
|
+
*
|
|
306
|
+
* @publicApi
|
|
307
|
+
*/
|
|
308
|
+
class HttpContext {
|
|
309
|
+
map = new Map();
|
|
310
|
+
/**
|
|
311
|
+
* Store a value in the context. If a value is already present it will be overwritten.
|
|
312
|
+
*
|
|
313
|
+
* @param token The reference to an instance of `HttpContextToken`.
|
|
314
|
+
* @param value The value to store.
|
|
315
|
+
*
|
|
316
|
+
* @returns A reference to itself for easy chaining.
|
|
317
|
+
*/
|
|
318
|
+
set(token, value) {
|
|
319
|
+
this.map.set(token, value);
|
|
320
|
+
return this;
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Retrieve the value associated with the given token.
|
|
324
|
+
*
|
|
325
|
+
* @param token The reference to an instance of `HttpContextToken`.
|
|
326
|
+
*
|
|
327
|
+
* @returns The stored value or default if one is defined.
|
|
328
|
+
*/
|
|
329
|
+
get(token) {
|
|
330
|
+
if (!this.map.has(token)) {
|
|
331
|
+
this.map.set(token, token.defaultValue());
|
|
332
|
+
}
|
|
333
|
+
return this.map.get(token);
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Delete the value associated with the given token.
|
|
337
|
+
*
|
|
338
|
+
* @param token The reference to an instance of `HttpContextToken`.
|
|
339
|
+
*
|
|
340
|
+
* @returns A reference to itself for easy chaining.
|
|
341
|
+
*/
|
|
342
|
+
delete(token) {
|
|
343
|
+
this.map.delete(token);
|
|
344
|
+
return this;
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* Checks for existence of a given token.
|
|
348
|
+
*
|
|
349
|
+
* @param token The reference to an instance of `HttpContextToken`.
|
|
350
|
+
*
|
|
351
|
+
* @returns True if the token exists, false otherwise.
|
|
352
|
+
*/
|
|
353
|
+
has(token) {
|
|
354
|
+
return this.map.has(token);
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* @returns a list of tokens currently stored in the context.
|
|
358
|
+
*/
|
|
359
|
+
keys() {
|
|
360
|
+
return this.map.keys();
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
291
364
|
/**
|
|
292
365
|
* Provides encoding and decoding of URL parameter and query-string values.
|
|
293
366
|
*
|
|
@@ -559,106 +632,6 @@ class HttpParams {
|
|
|
559
632
|
}
|
|
560
633
|
}
|
|
561
634
|
|
|
562
|
-
/**
|
|
563
|
-
* A token used to manipulate and access values stored in `HttpContext`.
|
|
564
|
-
*
|
|
565
|
-
* @publicApi
|
|
566
|
-
*/
|
|
567
|
-
class HttpContextToken {
|
|
568
|
-
defaultValue;
|
|
569
|
-
constructor(defaultValue) {
|
|
570
|
-
this.defaultValue = defaultValue;
|
|
571
|
-
}
|
|
572
|
-
}
|
|
573
|
-
/**
|
|
574
|
-
* Http context stores arbitrary user defined values and ensures type safety without
|
|
575
|
-
* actually knowing the types. It is backed by a `Map` and guarantees that keys do not clash.
|
|
576
|
-
*
|
|
577
|
-
* This context is mutable and is shared between cloned requests unless explicitly specified.
|
|
578
|
-
*
|
|
579
|
-
* @usageNotes
|
|
580
|
-
*
|
|
581
|
-
* ### Usage Example
|
|
582
|
-
*
|
|
583
|
-
* ```ts
|
|
584
|
-
* // inside cache.interceptors.ts
|
|
585
|
-
* export const IS_CACHE_ENABLED = new HttpContextToken<boolean>(() => false);
|
|
586
|
-
*
|
|
587
|
-
* export class CacheInterceptor implements HttpInterceptor {
|
|
588
|
-
*
|
|
589
|
-
* intercept(req: HttpRequest<any>, delegate: HttpHandler): Observable<HttpEvent<any>> {
|
|
590
|
-
* if (req.context.get(IS_CACHE_ENABLED) === true) {
|
|
591
|
-
* return ...;
|
|
592
|
-
* }
|
|
593
|
-
* return delegate.handle(req);
|
|
594
|
-
* }
|
|
595
|
-
* }
|
|
596
|
-
*
|
|
597
|
-
* // inside a service
|
|
598
|
-
*
|
|
599
|
-
* this.httpClient.get('/api/weather', {
|
|
600
|
-
* context: new HttpContext().set(IS_CACHE_ENABLED, true)
|
|
601
|
-
* }).subscribe(...);
|
|
602
|
-
* ```
|
|
603
|
-
*
|
|
604
|
-
* @publicApi
|
|
605
|
-
*/
|
|
606
|
-
class HttpContext {
|
|
607
|
-
map = new Map();
|
|
608
|
-
/**
|
|
609
|
-
* Store a value in the context. If a value is already present it will be overwritten.
|
|
610
|
-
*
|
|
611
|
-
* @param token The reference to an instance of `HttpContextToken`.
|
|
612
|
-
* @param value The value to store.
|
|
613
|
-
*
|
|
614
|
-
* @returns A reference to itself for easy chaining.
|
|
615
|
-
*/
|
|
616
|
-
set(token, value) {
|
|
617
|
-
this.map.set(token, value);
|
|
618
|
-
return this;
|
|
619
|
-
}
|
|
620
|
-
/**
|
|
621
|
-
* Retrieve the value associated with the given token.
|
|
622
|
-
*
|
|
623
|
-
* @param token The reference to an instance of `HttpContextToken`.
|
|
624
|
-
*
|
|
625
|
-
* @returns The stored value or default if one is defined.
|
|
626
|
-
*/
|
|
627
|
-
get(token) {
|
|
628
|
-
if (!this.map.has(token)) {
|
|
629
|
-
this.map.set(token, token.defaultValue());
|
|
630
|
-
}
|
|
631
|
-
return this.map.get(token);
|
|
632
|
-
}
|
|
633
|
-
/**
|
|
634
|
-
* Delete the value associated with the given token.
|
|
635
|
-
*
|
|
636
|
-
* @param token The reference to an instance of `HttpContextToken`.
|
|
637
|
-
*
|
|
638
|
-
* @returns A reference to itself for easy chaining.
|
|
639
|
-
*/
|
|
640
|
-
delete(token) {
|
|
641
|
-
this.map.delete(token);
|
|
642
|
-
return this;
|
|
643
|
-
}
|
|
644
|
-
/**
|
|
645
|
-
* Checks for existence of a given token.
|
|
646
|
-
*
|
|
647
|
-
* @param token The reference to an instance of `HttpContextToken`.
|
|
648
|
-
*
|
|
649
|
-
* @returns True if the token exists, false otherwise.
|
|
650
|
-
*/
|
|
651
|
-
has(token) {
|
|
652
|
-
return this.map.has(token);
|
|
653
|
-
}
|
|
654
|
-
/**
|
|
655
|
-
* @returns a list of tokens currently stored in the context.
|
|
656
|
-
*/
|
|
657
|
-
keys() {
|
|
658
|
-
return this.map.keys();
|
|
659
|
-
}
|
|
660
|
-
}
|
|
661
|
-
|
|
662
635
|
/**
|
|
663
636
|
* Determine whether the given HTTP method may include a body.
|
|
664
637
|
*/
|
|
@@ -717,12 +690,6 @@ const CONTENT_TYPE_HEADER = 'Content-Type';
|
|
|
717
690
|
* (or content types) the client is willing to receive from the server.
|
|
718
691
|
*/
|
|
719
692
|
const ACCEPT_HEADER = 'Accept';
|
|
720
|
-
/**
|
|
721
|
-
* `X-Request-URL` is a custom HTTP header used in older browser versions,
|
|
722
|
-
* including Firefox (< 32), Chrome (< 37), Safari (< 8), and Internet Explorer,
|
|
723
|
-
* to include the full URL of the request in cross-origin requests.
|
|
724
|
-
*/
|
|
725
|
-
const X_REQUEST_URL_HEADER = 'X-Request-URL';
|
|
726
693
|
/**
|
|
727
694
|
* `text/plain` is a content type used to indicate that the content being
|
|
728
695
|
* sent is plain text with no special formatting or structured data
|
|
@@ -1361,647 +1328,651 @@ var HttpStatusCode;
|
|
|
1361
1328
|
HttpStatusCode[HttpStatusCode["NetworkAuthenticationRequired"] = 511] = "NetworkAuthenticationRequired";
|
|
1362
1329
|
})(HttpStatusCode || (HttpStatusCode = {}));
|
|
1363
1330
|
|
|
1331
|
+
const XSSI_PREFIX$1 = /^\)\]\}',?\n/;
|
|
1364
1332
|
/**
|
|
1365
|
-
*
|
|
1366
|
-
*
|
|
1333
|
+
* An internal injection token to reference `FetchBackend` implementation
|
|
1334
|
+
* in a tree-shakable way.
|
|
1335
|
+
*/
|
|
1336
|
+
const FETCH_BACKEND = new InjectionToken(typeof ngDevMode === 'undefined' || ngDevMode ? 'FETCH_BACKEND' : '');
|
|
1337
|
+
/**
|
|
1338
|
+
* Uses `fetch` to send requests to a backend server.
|
|
1367
1339
|
*
|
|
1368
|
-
*
|
|
1369
|
-
*
|
|
1370
|
-
*
|
|
1371
|
-
*
|
|
1340
|
+
* This `FetchBackend` requires the support of the
|
|
1341
|
+
* [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) which is available on all
|
|
1342
|
+
* supported browsers and on Node.js v18 or later.
|
|
1343
|
+
*
|
|
1344
|
+
* @see {@link HttpHandler}
|
|
1372
1345
|
*
|
|
1346
|
+
* @publicApi
|
|
1373
1347
|
*/
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
timeout: options.timeout,
|
|
1387
|
-
keepalive: options.keepalive,
|
|
1388
|
-
priority: options.priority,
|
|
1389
|
-
cache: options.cache,
|
|
1390
|
-
mode: options.mode,
|
|
1391
|
-
redirect: options.redirect,
|
|
1392
|
-
integrity: options.integrity,
|
|
1393
|
-
referrer: options.referrer,
|
|
1394
|
-
};
|
|
1395
|
-
}
|
|
1396
|
-
/**
|
|
1397
|
-
* Performs HTTP requests.
|
|
1398
|
-
* This service is available as an injectable class, with methods to perform HTTP requests.
|
|
1399
|
-
* Each request method has multiple signatures, and the return type varies based on
|
|
1400
|
-
* the signature that is called (mainly the values of `observe` and `responseType`).
|
|
1401
|
-
*
|
|
1402
|
-
* Note that the `responseType` *options* value is a String that identifies the
|
|
1403
|
-
* single data type of the response.
|
|
1404
|
-
* A single overload version of the method handles each response type.
|
|
1405
|
-
* The value of `responseType` cannot be a union, as the combined signature could imply.
|
|
1406
|
-
*
|
|
1407
|
-
* @usageNotes
|
|
1408
|
-
*
|
|
1409
|
-
* ### HTTP Request Example
|
|
1410
|
-
*
|
|
1411
|
-
* ```ts
|
|
1412
|
-
* // GET heroes whose name contains search term
|
|
1413
|
-
* searchHeroes(term: string): observable<Hero[]>{
|
|
1414
|
-
*
|
|
1415
|
-
* const params = new HttpParams({fromString: 'name=term'});
|
|
1416
|
-
* return this.httpClient.request('GET', this.heroesUrl, {responseType:'json', params});
|
|
1417
|
-
* }
|
|
1418
|
-
* ```
|
|
1419
|
-
*
|
|
1420
|
-
* Alternatively, the parameter string can be used without invoking HttpParams
|
|
1421
|
-
* by directly joining to the URL.
|
|
1422
|
-
* ```ts
|
|
1423
|
-
* this.httpClient.request('GET', this.heroesUrl + '?' + 'name=term', {responseType:'json'});
|
|
1424
|
-
* ```
|
|
1425
|
-
*
|
|
1426
|
-
*
|
|
1427
|
-
* ### JSONP Example
|
|
1428
|
-
* ```ts
|
|
1429
|
-
* requestJsonp(url, callback = 'callback') {
|
|
1430
|
-
* return this.httpClient.jsonp(this.heroesURL, callback);
|
|
1431
|
-
* }
|
|
1432
|
-
* ```
|
|
1433
|
-
*
|
|
1434
|
-
* ### PATCH Example
|
|
1435
|
-
* ```ts
|
|
1436
|
-
* // PATCH one of the heroes' name
|
|
1437
|
-
* patchHero (id: number, heroName: string): Observable<{}> {
|
|
1438
|
-
* const url = `${this.heroesUrl}/${id}`; // PATCH api/heroes/42
|
|
1439
|
-
* return this.httpClient.patch(url, {name: heroName}, httpOptions)
|
|
1440
|
-
* .pipe(catchError(this.handleError('patchHero')));
|
|
1441
|
-
* }
|
|
1442
|
-
* ```
|
|
1443
|
-
*
|
|
1444
|
-
* @see [HTTP Guide](guide/http)
|
|
1445
|
-
* @see [HTTP Request](api/common/http/HttpRequest)
|
|
1446
|
-
*
|
|
1447
|
-
* @publicApi
|
|
1448
|
-
*/
|
|
1449
|
-
class HttpClient {
|
|
1450
|
-
handler;
|
|
1451
|
-
constructor(handler) {
|
|
1452
|
-
this.handler = handler;
|
|
1348
|
+
class FetchBackend {
|
|
1349
|
+
// We use an arrow function to always reference the current global implementation of `fetch`.
|
|
1350
|
+
// This is helpful for cases when the global `fetch` implementation is modified by external code,
|
|
1351
|
+
// see https://github.com/angular/angular/issues/57527.
|
|
1352
|
+
fetchImpl = inject(FetchFactory, { optional: true })?.fetch ?? ((...args) => globalThis.fetch(...args));
|
|
1353
|
+
ngZone = inject(NgZone);
|
|
1354
|
+
destroyRef = inject(DestroyRef);
|
|
1355
|
+
destroyed = false;
|
|
1356
|
+
constructor() {
|
|
1357
|
+
this.destroyRef.onDestroy(() => {
|
|
1358
|
+
this.destroyed = true;
|
|
1359
|
+
});
|
|
1453
1360
|
}
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
* * If `responseType` is the default `json`, you can pass a type interface for the resulting
|
|
1468
|
-
* object as a type parameter to the call.
|
|
1469
|
-
*
|
|
1470
|
-
* The `observe` value determines the return type, according to what you are interested in
|
|
1471
|
-
* observing.
|
|
1472
|
-
* * An `observe` value of events returns an observable of the raw `HttpEvent` stream, including
|
|
1473
|
-
* progress events by default.
|
|
1474
|
-
* * An `observe` value of response returns an observable of `HttpResponse<T>`,
|
|
1475
|
-
* where the `T` parameter depends on the `responseType` and any optionally provided type
|
|
1476
|
-
* parameter.
|
|
1477
|
-
* * An `observe` value of body returns an observable of `<T>` with the same `T` body type.
|
|
1478
|
-
*
|
|
1479
|
-
*/
|
|
1480
|
-
request(first, url, options = {}) {
|
|
1481
|
-
let req;
|
|
1482
|
-
// First, check whether the primary argument is an instance of `HttpRequest`.
|
|
1483
|
-
if (first instanceof HttpRequest) {
|
|
1484
|
-
// It is. The other arguments must be undefined (per the signatures) and can be
|
|
1485
|
-
// ignored.
|
|
1486
|
-
req = first;
|
|
1487
|
-
}
|
|
1488
|
-
else {
|
|
1489
|
-
// It's a string, so it represents a URL. Construct a request based on it,
|
|
1490
|
-
// and incorporate the remaining arguments (assuming `GET` unless a method is
|
|
1491
|
-
// provided.
|
|
1492
|
-
// Figure out the headers.
|
|
1493
|
-
let headers = undefined;
|
|
1494
|
-
if (options.headers instanceof HttpHeaders) {
|
|
1495
|
-
headers = options.headers;
|
|
1496
|
-
}
|
|
1497
|
-
else {
|
|
1498
|
-
headers = new HttpHeaders(options.headers);
|
|
1361
|
+
handle(request) {
|
|
1362
|
+
return new Observable((observer) => {
|
|
1363
|
+
const aborter = new AbortController();
|
|
1364
|
+
this.doRequest(request, aborter.signal, observer).then(noop, (error) => observer.error(new HttpErrorResponse({ error })));
|
|
1365
|
+
let timeoutId;
|
|
1366
|
+
if (request.timeout) {
|
|
1367
|
+
// TODO: Replace with AbortSignal.any([aborter.signal, AbortSignal.timeout(request.timeout)])
|
|
1368
|
+
// when AbortSignal.any support is Baseline widely available (NET nov. 2026)
|
|
1369
|
+
timeoutId = this.ngZone.runOutsideAngular(() => setTimeout(() => {
|
|
1370
|
+
if (!aborter.signal.aborted) {
|
|
1371
|
+
aborter.abort(new DOMException('signal timed out', 'TimeoutError'));
|
|
1372
|
+
}
|
|
1373
|
+
}, request.timeout));
|
|
1499
1374
|
}
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
if (options.params instanceof HttpParams) {
|
|
1504
|
-
params = options.params;
|
|
1375
|
+
return () => {
|
|
1376
|
+
if (timeoutId !== undefined) {
|
|
1377
|
+
clearTimeout(timeoutId);
|
|
1505
1378
|
}
|
|
1506
|
-
|
|
1507
|
-
|
|
1379
|
+
aborter.abort();
|
|
1380
|
+
};
|
|
1381
|
+
});
|
|
1382
|
+
}
|
|
1383
|
+
async doRequest(request, signal, observer) {
|
|
1384
|
+
const init = this.createRequestInit(request);
|
|
1385
|
+
let response;
|
|
1386
|
+
try {
|
|
1387
|
+
// Run fetch outside of Angular zone.
|
|
1388
|
+
// This is due to Node.js fetch implementation (Undici) which uses a number of setTimeouts to check if
|
|
1389
|
+
// the response should eventually timeout which causes extra CD cycles every 500ms
|
|
1390
|
+
const fetchPromise = this.ngZone.runOutsideAngular(() => this.fetchImpl(request.urlWithParams, { signal, ...init }));
|
|
1391
|
+
// Make sure Zone.js doesn't trigger false-positive unhandled promise
|
|
1392
|
+
// error in case the Promise is rejected synchronously. See function
|
|
1393
|
+
// description for additional information.
|
|
1394
|
+
silenceSuperfluousUnhandledPromiseRejection(fetchPromise);
|
|
1395
|
+
// Send the `Sent` event before awaiting the response.
|
|
1396
|
+
observer.next({ type: HttpEventType.Sent });
|
|
1397
|
+
response = await fetchPromise;
|
|
1398
|
+
}
|
|
1399
|
+
catch (error) {
|
|
1400
|
+
observer.error(new HttpErrorResponse({
|
|
1401
|
+
error,
|
|
1402
|
+
status: error.status ?? 0,
|
|
1403
|
+
statusText: error.statusText,
|
|
1404
|
+
url: request.urlWithParams,
|
|
1405
|
+
headers: error.headers,
|
|
1406
|
+
}));
|
|
1407
|
+
return;
|
|
1408
|
+
}
|
|
1409
|
+
const headers = new HttpHeaders(response.headers);
|
|
1410
|
+
const statusText = response.statusText;
|
|
1411
|
+
const url = response.url || request.urlWithParams;
|
|
1412
|
+
let status = response.status;
|
|
1413
|
+
let body = null;
|
|
1414
|
+
if (request.reportProgress) {
|
|
1415
|
+
observer.next(new HttpHeaderResponse({ headers, status, statusText, url }));
|
|
1416
|
+
}
|
|
1417
|
+
if (response.body) {
|
|
1418
|
+
// Read Progress
|
|
1419
|
+
const contentLength = response.headers.get('content-length');
|
|
1420
|
+
const chunks = [];
|
|
1421
|
+
const reader = response.body.getReader();
|
|
1422
|
+
let receivedLength = 0;
|
|
1423
|
+
let decoder;
|
|
1424
|
+
let partialText;
|
|
1425
|
+
// We have to check whether the Zone is defined in the global scope because this may be called
|
|
1426
|
+
// when the zone is nooped.
|
|
1427
|
+
const reqZone = typeof Zone !== 'undefined' && Zone.current;
|
|
1428
|
+
let canceled = false;
|
|
1429
|
+
// Perform response processing outside of Angular zone to
|
|
1430
|
+
// ensure no excessive change detection runs are executed
|
|
1431
|
+
// Here calling the async ReadableStreamDefaultReader.read() is responsible for triggering CD
|
|
1432
|
+
await this.ngZone.runOutsideAngular(async () => {
|
|
1433
|
+
while (true) {
|
|
1434
|
+
// Prevent reading chunks if the app is destroyed. Otherwise, we risk doing
|
|
1435
|
+
// unnecessary work or triggering side effects after teardown.
|
|
1436
|
+
// This may happen if the app was explicitly destroyed before
|
|
1437
|
+
// the response returned entirely.
|
|
1438
|
+
if (this.destroyed) {
|
|
1439
|
+
// Streams left in a pending state (due to `break` without cancel) may
|
|
1440
|
+
// continue consuming or holding onto data behind the scenes.
|
|
1441
|
+
// Calling `reader.cancel()` allows the browser or the underlying
|
|
1442
|
+
// system to release any network or memory resources associated with the stream.
|
|
1443
|
+
await reader.cancel();
|
|
1444
|
+
canceled = true;
|
|
1445
|
+
break;
|
|
1446
|
+
}
|
|
1447
|
+
const { done, value } = await reader.read();
|
|
1448
|
+
if (done) {
|
|
1449
|
+
break;
|
|
1450
|
+
}
|
|
1451
|
+
chunks.push(value);
|
|
1452
|
+
receivedLength += value.length;
|
|
1453
|
+
if (request.reportProgress) {
|
|
1454
|
+
partialText =
|
|
1455
|
+
request.responseType === 'text'
|
|
1456
|
+
? (partialText ?? '') +
|
|
1457
|
+
(decoder ??= new TextDecoder()).decode(value, { stream: true })
|
|
1458
|
+
: undefined;
|
|
1459
|
+
const reportProgress = () => observer.next({
|
|
1460
|
+
type: HttpEventType.DownloadProgress,
|
|
1461
|
+
total: contentLength ? +contentLength : undefined,
|
|
1462
|
+
loaded: receivedLength,
|
|
1463
|
+
partialText,
|
|
1464
|
+
});
|
|
1465
|
+
reqZone ? reqZone.run(reportProgress) : reportProgress();
|
|
1466
|
+
}
|
|
1508
1467
|
}
|
|
1468
|
+
});
|
|
1469
|
+
// We need to manage the canceled state — because the Streams API does not
|
|
1470
|
+
// expose a direct `.state` property on the reader.
|
|
1471
|
+
// We need to `return` because `parseBody` may not be able to parse chunks
|
|
1472
|
+
// that were only partially read (due to cancellation caused by app destruction).
|
|
1473
|
+
if (canceled) {
|
|
1474
|
+
observer.complete();
|
|
1475
|
+
return;
|
|
1509
1476
|
}
|
|
1510
|
-
//
|
|
1511
|
-
|
|
1477
|
+
// Combine all chunks.
|
|
1478
|
+
const chunksAll = this.concatChunks(chunks, receivedLength);
|
|
1479
|
+
try {
|
|
1480
|
+
const contentType = response.headers.get(CONTENT_TYPE_HEADER) ?? '';
|
|
1481
|
+
body = this.parseBody(request, chunksAll, contentType, status);
|
|
1482
|
+
}
|
|
1483
|
+
catch (error) {
|
|
1484
|
+
// Body loading or parsing failed
|
|
1485
|
+
observer.error(new HttpErrorResponse({
|
|
1486
|
+
error,
|
|
1487
|
+
headers: new HttpHeaders(response.headers),
|
|
1488
|
+
status: response.status,
|
|
1489
|
+
statusText: response.statusText,
|
|
1490
|
+
url: response.url || request.urlWithParams,
|
|
1491
|
+
}));
|
|
1492
|
+
return;
|
|
1493
|
+
}
|
|
1494
|
+
}
|
|
1495
|
+
// Same behavior as the XhrBackend
|
|
1496
|
+
if (status === 0) {
|
|
1497
|
+
status = body ? HTTP_STATUS_CODE_OK : 0;
|
|
1498
|
+
}
|
|
1499
|
+
// ok determines whether the response will be transmitted on the event or
|
|
1500
|
+
// error channel. Unsuccessful status codes (not 2xx) will always be errors,
|
|
1501
|
+
// but a successful status code can still result in an error if the user
|
|
1502
|
+
// asked for JSON data and the body cannot be parsed as such.
|
|
1503
|
+
const ok = status >= 200 && status < 300;
|
|
1504
|
+
const redirected = response.redirected;
|
|
1505
|
+
if (ok) {
|
|
1506
|
+
observer.next(new HttpResponse({
|
|
1507
|
+
body,
|
|
1512
1508
|
headers,
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
priority: options.priority,
|
|
1522
|
-
cache: options.cache,
|
|
1523
|
-
mode: options.mode,
|
|
1524
|
-
redirect: options.redirect,
|
|
1525
|
-
credentials: options.credentials,
|
|
1526
|
-
referrer: options.referrer,
|
|
1527
|
-
integrity: options.integrity,
|
|
1528
|
-
timeout: options.timeout,
|
|
1529
|
-
});
|
|
1509
|
+
status,
|
|
1510
|
+
statusText,
|
|
1511
|
+
url,
|
|
1512
|
+
redirected,
|
|
1513
|
+
}));
|
|
1514
|
+
// The full body has been received and delivered, no further events
|
|
1515
|
+
// are possible. This request is complete.
|
|
1516
|
+
observer.complete();
|
|
1530
1517
|
}
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
return events$;
|
|
1518
|
+
else {
|
|
1519
|
+
observer.error(new HttpErrorResponse({
|
|
1520
|
+
error: body,
|
|
1521
|
+
headers,
|
|
1522
|
+
status,
|
|
1523
|
+
statusText,
|
|
1524
|
+
url,
|
|
1525
|
+
redirected,
|
|
1526
|
+
}));
|
|
1541
1527
|
}
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
// body. This could be done more simply, but a misbehaving interceptor might
|
|
1551
|
-
// transform the response body into a different format and ignore the requested
|
|
1552
|
-
// responseType. Guard against this by validating that the response is of the
|
|
1553
|
-
// requested type.
|
|
1554
|
-
switch (req.responseType) {
|
|
1555
|
-
case 'arraybuffer':
|
|
1556
|
-
return res$.pipe(map((res) => {
|
|
1557
|
-
// Validate that the body is an ArrayBuffer.
|
|
1558
|
-
if (res.body !== null && !(res.body instanceof ArrayBuffer)) {
|
|
1559
|
-
throw new _RuntimeError(2806 /* RuntimeErrorCode.RESPONSE_IS_NOT_AN_ARRAY_BUFFER */, ngDevMode && 'Response is not an ArrayBuffer.');
|
|
1560
|
-
}
|
|
1561
|
-
return res.body;
|
|
1562
|
-
}));
|
|
1563
|
-
case 'blob':
|
|
1564
|
-
return res$.pipe(map((res) => {
|
|
1565
|
-
// Validate that the body is a Blob.
|
|
1566
|
-
if (res.body !== null && !(res.body instanceof Blob)) {
|
|
1567
|
-
throw new _RuntimeError(2807 /* RuntimeErrorCode.RESPONSE_IS_NOT_A_BLOB */, ngDevMode && 'Response is not a Blob.');
|
|
1568
|
-
}
|
|
1569
|
-
return res.body;
|
|
1570
|
-
}));
|
|
1571
|
-
case 'text':
|
|
1572
|
-
return res$.pipe(map((res) => {
|
|
1573
|
-
// Validate that the body is a string.
|
|
1574
|
-
if (res.body !== null && typeof res.body !== 'string') {
|
|
1575
|
-
throw new _RuntimeError(2808 /* RuntimeErrorCode.RESPONSE_IS_NOT_A_STRING */, ngDevMode && 'Response is not a string.');
|
|
1576
|
-
}
|
|
1577
|
-
return res.body;
|
|
1578
|
-
}));
|
|
1579
|
-
case 'json':
|
|
1580
|
-
default:
|
|
1581
|
-
// No validation needed for JSON responses, as they can be of any type.
|
|
1582
|
-
return res$.pipe(map((res) => res.body));
|
|
1528
|
+
}
|
|
1529
|
+
parseBody(request, binContent, contentType, status) {
|
|
1530
|
+
switch (request.responseType) {
|
|
1531
|
+
case 'json':
|
|
1532
|
+
// stripping the XSSI when present
|
|
1533
|
+
const text = new TextDecoder().decode(binContent).replace(XSSI_PREFIX$1, '');
|
|
1534
|
+
if (text === '') {
|
|
1535
|
+
return null;
|
|
1583
1536
|
}
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1537
|
+
try {
|
|
1538
|
+
return JSON.parse(text);
|
|
1539
|
+
}
|
|
1540
|
+
catch (e) {
|
|
1541
|
+
// Allow handling non-JSON errors (!) as plain text, same as the XHR
|
|
1542
|
+
// backend. Without this special sauce, any non-JSON error would be
|
|
1543
|
+
// completely inaccessible downstream as the `HttpErrorResponse.error`
|
|
1544
|
+
// would be set to the `SyntaxError` from then failing `JSON.parse`.
|
|
1545
|
+
if (status < 200 || status >= 300) {
|
|
1546
|
+
return text;
|
|
1547
|
+
}
|
|
1548
|
+
throw e;
|
|
1549
|
+
}
|
|
1550
|
+
case 'text':
|
|
1551
|
+
return new TextDecoder().decode(binContent);
|
|
1552
|
+
case 'blob':
|
|
1553
|
+
return new Blob([binContent], { type: contentType });
|
|
1554
|
+
case 'arraybuffer':
|
|
1555
|
+
return binContent.buffer;
|
|
1590
1556
|
}
|
|
1591
1557
|
}
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
* The resource API returns the JSON response wrapped in a callback function.
|
|
1633
|
-
* You can pass the callback function name as one of the query parameters.
|
|
1634
|
-
* Note that JSONP requests can only be used with `GET` requests.
|
|
1635
|
-
*
|
|
1636
|
-
* @param url The resource URL.
|
|
1637
|
-
* @param callbackParam The callback function name.
|
|
1638
|
-
*
|
|
1639
|
-
*/
|
|
1640
|
-
jsonp(url, callbackParam) {
|
|
1641
|
-
return this.request('JSONP', url, {
|
|
1642
|
-
params: new HttpParams().append(callbackParam, 'JSONP_CALLBACK'),
|
|
1643
|
-
observe: 'body',
|
|
1644
|
-
responseType: 'json',
|
|
1645
|
-
});
|
|
1646
|
-
}
|
|
1647
|
-
/**
|
|
1648
|
-
* Constructs an `Observable` that, when subscribed, causes the configured
|
|
1649
|
-
* `OPTIONS` request to execute on the server. This method allows the client
|
|
1650
|
-
* to determine the supported HTTP methods and other capabilities of an endpoint,
|
|
1651
|
-
* without implying a resource action. See the individual overloads for
|
|
1652
|
-
* details on the return type.
|
|
1653
|
-
*/
|
|
1654
|
-
options(url, options = {}) {
|
|
1655
|
-
return this.request('OPTIONS', url, options);
|
|
1656
|
-
}
|
|
1657
|
-
/**
|
|
1658
|
-
* Constructs an observable that, when subscribed, causes the configured
|
|
1659
|
-
* `PATCH` request to execute on the server. See the individual overloads for
|
|
1660
|
-
* details on the return type.
|
|
1661
|
-
*/
|
|
1662
|
-
patch(url, body, options = {}) {
|
|
1663
|
-
return this.request('PATCH', url, addBody(options, body));
|
|
1664
|
-
}
|
|
1665
|
-
/**
|
|
1666
|
-
* Constructs an observable that, when subscribed, causes the configured
|
|
1667
|
-
* `POST` request to execute on the server. The server responds with the location of
|
|
1668
|
-
* the replaced resource. See the individual overloads for
|
|
1669
|
-
* details on the return type.
|
|
1670
|
-
*/
|
|
1671
|
-
post(url, body, options = {}) {
|
|
1672
|
-
return this.request('POST', url, addBody(options, body));
|
|
1558
|
+
createRequestInit(req) {
|
|
1559
|
+
// We could share some of this logic with the XhrBackend
|
|
1560
|
+
const headers = {};
|
|
1561
|
+
let credentials;
|
|
1562
|
+
// If the request has a credentials property, use it.
|
|
1563
|
+
// Otherwise, if the request has withCredentials set to true, use 'include'.
|
|
1564
|
+
credentials = req.credentials;
|
|
1565
|
+
// If withCredentials is true should be set to 'include', for compatibility
|
|
1566
|
+
if (req.withCredentials) {
|
|
1567
|
+
// A warning is logged in development mode if the request has both
|
|
1568
|
+
(typeof ngDevMode === 'undefined' || ngDevMode) && warningOptionsMessage(req);
|
|
1569
|
+
credentials = 'include';
|
|
1570
|
+
}
|
|
1571
|
+
// Setting all the requested headers.
|
|
1572
|
+
req.headers.forEach((name, values) => (headers[name] = values.join(',')));
|
|
1573
|
+
// Add an Accept header if one isn't present already.
|
|
1574
|
+
if (!req.headers.has(ACCEPT_HEADER)) {
|
|
1575
|
+
headers[ACCEPT_HEADER] = ACCEPT_HEADER_VALUE;
|
|
1576
|
+
}
|
|
1577
|
+
// Auto-detect the Content-Type header if one isn't present already.
|
|
1578
|
+
if (!req.headers.has(CONTENT_TYPE_HEADER)) {
|
|
1579
|
+
const detectedType = req.detectContentTypeHeader();
|
|
1580
|
+
// Sometimes Content-Type detection fails.
|
|
1581
|
+
if (detectedType !== null) {
|
|
1582
|
+
headers[CONTENT_TYPE_HEADER] = detectedType;
|
|
1583
|
+
}
|
|
1584
|
+
}
|
|
1585
|
+
return {
|
|
1586
|
+
body: req.serializeBody(),
|
|
1587
|
+
method: req.method,
|
|
1588
|
+
headers,
|
|
1589
|
+
credentials,
|
|
1590
|
+
keepalive: req.keepalive,
|
|
1591
|
+
cache: req.cache,
|
|
1592
|
+
priority: req.priority,
|
|
1593
|
+
mode: req.mode,
|
|
1594
|
+
redirect: req.redirect,
|
|
1595
|
+
referrer: req.referrer,
|
|
1596
|
+
integrity: req.integrity,
|
|
1597
|
+
};
|
|
1673
1598
|
}
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
return
|
|
1599
|
+
concatChunks(chunks, totalLength) {
|
|
1600
|
+
const chunksAll = new Uint8Array(totalLength);
|
|
1601
|
+
let position = 0;
|
|
1602
|
+
for (const chunk of chunks) {
|
|
1603
|
+
chunksAll.set(chunk, position);
|
|
1604
|
+
position += chunk.length;
|
|
1605
|
+
}
|
|
1606
|
+
return chunksAll;
|
|
1682
1607
|
}
|
|
1683
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.0-next.2", ngImport: i0, type:
|
|
1684
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.2.0-next.2", ngImport: i0, type:
|
|
1608
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.0-next.2", ngImport: i0, type: FetchBackend, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1609
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.2.0-next.2", ngImport: i0, type: FetchBackend });
|
|
1685
1610
|
}
|
|
1686
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.0-next.2", ngImport: i0, type:
|
|
1611
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.0-next.2", ngImport: i0, type: FetchBackend, decorators: [{
|
|
1687
1612
|
type: Injectable
|
|
1688
|
-
}], ctorParameters: () => [
|
|
1689
|
-
|
|
1690
|
-
const XSSI_PREFIX$1 = /^\)\]\}',?\n/;
|
|
1613
|
+
}], ctorParameters: () => [] });
|
|
1691
1614
|
/**
|
|
1692
|
-
*
|
|
1693
|
-
* response url or the X-Request-URL header.
|
|
1615
|
+
* Abstract class to provide a mocked implementation of `fetch()`
|
|
1694
1616
|
*/
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1617
|
+
class FetchFactory {
|
|
1618
|
+
}
|
|
1619
|
+
function noop() { }
|
|
1620
|
+
function warningOptionsMessage(req) {
|
|
1621
|
+
if (req.credentials && req.withCredentials) {
|
|
1622
|
+
console.warn(_formatRuntimeError(2819 /* RuntimeErrorCode.WITH_CREDENTIALS_OVERRIDES_EXPLICIT_CREDENTIALS */, `Angular detected that a \`HttpClient\` request has both \`withCredentials: true\` and \`credentials: '${req.credentials}'\` options. The \`withCredentials\` option is overriding the explicit \`credentials\` setting to 'include'. Consider removing \`withCredentials\` and using \`credentials: '${req.credentials}'\` directly for clarity.`));
|
|
1698
1623
|
}
|
|
1699
|
-
// stored as lowercase in the map
|
|
1700
|
-
const xRequestUrl = X_REQUEST_URL_HEADER.toLocaleLowerCase();
|
|
1701
|
-
return response.headers.get(xRequestUrl);
|
|
1702
1624
|
}
|
|
1703
1625
|
/**
|
|
1704
|
-
*
|
|
1705
|
-
*
|
|
1626
|
+
* Zone.js treats a rejected promise that has not yet been awaited
|
|
1627
|
+
* as an unhandled error. This function adds a noop `.then` to make
|
|
1628
|
+
* sure that Zone.js doesn't throw an error if the Promise is rejected
|
|
1629
|
+
* synchronously.
|
|
1706
1630
|
*/
|
|
1707
|
-
|
|
1631
|
+
function silenceSuperfluousUnhandledPromiseRejection(promise) {
|
|
1632
|
+
promise.then(noop, noop);
|
|
1633
|
+
}
|
|
1634
|
+
|
|
1635
|
+
const XSSI_PREFIX = /^\)\]\}',?\n/;
|
|
1708
1636
|
/**
|
|
1709
|
-
*
|
|
1710
|
-
*
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1637
|
+
* Validates whether the request is compatible with the XHR backend.
|
|
1638
|
+
* Show a warning if the request contains options that are not supported by XHR.
|
|
1639
|
+
*/
|
|
1640
|
+
function validateXhrCompatibility(req) {
|
|
1641
|
+
const unsupportedOptions = [
|
|
1642
|
+
{
|
|
1643
|
+
property: 'keepalive',
|
|
1644
|
+
errorCode: 2813 /* RuntimeErrorCode.KEEPALIVE_NOT_SUPPORTED_WITH_XHR */,
|
|
1645
|
+
},
|
|
1646
|
+
{
|
|
1647
|
+
property: 'cache',
|
|
1648
|
+
errorCode: 2814 /* RuntimeErrorCode.CACHE_NOT_SUPPORTED_WITH_XHR */,
|
|
1649
|
+
},
|
|
1650
|
+
{
|
|
1651
|
+
property: 'priority',
|
|
1652
|
+
errorCode: 2815 /* RuntimeErrorCode.PRIORITY_NOT_SUPPORTED_WITH_XHR */,
|
|
1653
|
+
},
|
|
1654
|
+
{
|
|
1655
|
+
property: 'mode',
|
|
1656
|
+
errorCode: 2816 /* RuntimeErrorCode.MODE_NOT_SUPPORTED_WITH_XHR */,
|
|
1657
|
+
},
|
|
1658
|
+
{
|
|
1659
|
+
property: 'redirect',
|
|
1660
|
+
errorCode: 2817 /* RuntimeErrorCode.REDIRECT_NOT_SUPPORTED_WITH_XHR */,
|
|
1661
|
+
},
|
|
1662
|
+
{
|
|
1663
|
+
property: 'credentials',
|
|
1664
|
+
errorCode: 2818 /* RuntimeErrorCode.CREDENTIALS_NOT_SUPPORTED_WITH_XHR */,
|
|
1665
|
+
},
|
|
1666
|
+
{
|
|
1667
|
+
property: 'integrity',
|
|
1668
|
+
errorCode: 2820 /* RuntimeErrorCode.INTEGRITY_NOT_SUPPORTED_WITH_XHR */,
|
|
1669
|
+
},
|
|
1670
|
+
{
|
|
1671
|
+
property: 'referrer',
|
|
1672
|
+
errorCode: 2821 /* RuntimeErrorCode.REFERRER_NOT_SUPPORTED_WITH_XHR */,
|
|
1673
|
+
},
|
|
1674
|
+
];
|
|
1675
|
+
// Check each unsupported option and warn if present
|
|
1676
|
+
for (const { property, errorCode } of unsupportedOptions) {
|
|
1677
|
+
if (req[property]) {
|
|
1678
|
+
console.warn(_formatRuntimeError(errorCode, `Angular detected that a \`HttpClient\` request with the \`${property}\` option was sent using XHR, which does not support it. To use the \`${property}\` option, enable Fetch API support by passing \`withFetch()\` as an argument to \`provideHttpClient()\`.`));
|
|
1679
|
+
}
|
|
1680
|
+
}
|
|
1681
|
+
}
|
|
1682
|
+
/**
|
|
1683
|
+
* Uses `XMLHttpRequest` to send requests to a backend server.
|
|
1715
1684
|
* @see {@link HttpHandler}
|
|
1685
|
+
* @see {@link JsonpClientBackend}
|
|
1716
1686
|
*
|
|
1717
1687
|
* @publicApi
|
|
1718
1688
|
*/
|
|
1719
|
-
class
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
fetchImpl = inject(FetchFactory, { optional: true })?.fetch ?? ((...args) => globalThis.fetch(...args));
|
|
1724
|
-
ngZone = inject(NgZone);
|
|
1725
|
-
destroyRef = inject(DestroyRef);
|
|
1726
|
-
destroyed = false;
|
|
1727
|
-
constructor() {
|
|
1728
|
-
this.destroyRef.onDestroy(() => {
|
|
1729
|
-
this.destroyed = true;
|
|
1730
|
-
});
|
|
1689
|
+
class HttpXhrBackend {
|
|
1690
|
+
xhrFactory;
|
|
1691
|
+
constructor(xhrFactory) {
|
|
1692
|
+
this.xhrFactory = xhrFactory;
|
|
1731
1693
|
}
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1694
|
+
/**
|
|
1695
|
+
* Processes a request and returns a stream of response events.
|
|
1696
|
+
* @param req The request object.
|
|
1697
|
+
* @returns An observable of the response events.
|
|
1698
|
+
*/
|
|
1699
|
+
handle(req) {
|
|
1700
|
+
// Quick check to give a better error message when a user attempts to use
|
|
1701
|
+
// HttpClient.jsonp() without installing the HttpClientJsonpModule
|
|
1702
|
+
if (req.method === 'JSONP') {
|
|
1703
|
+
throw new _RuntimeError(-2800 /* RuntimeErrorCode.MISSING_JSONP_MODULE */, (typeof ngDevMode === 'undefined' || ngDevMode) &&
|
|
1704
|
+
`Cannot make a JSONP request without JSONP support. To fix the problem, either add the \`withJsonpSupport()\` call (if \`provideHttpClient()\` is used) or import the \`HttpClientJsonpModule\` in the root NgModule.`);
|
|
1705
|
+
}
|
|
1706
|
+
// Validate that the request is compatible with the XHR backend.
|
|
1707
|
+
ngDevMode && validateXhrCompatibility(req);
|
|
1708
|
+
// Check whether this factory has a special function to load an XHR implementation
|
|
1709
|
+
// for various non-browser environments. We currently limit it to only `ServerXhr`
|
|
1710
|
+
// class, which needs to load an XHR implementation.
|
|
1711
|
+
const xhrFactory = this.xhrFactory;
|
|
1712
|
+
const source =
|
|
1713
|
+
// Note that `ɵloadImpl` is never defined in client bundles and can be
|
|
1714
|
+
// safely dropped whenever we're running in the browser.
|
|
1715
|
+
// This branching is redundant.
|
|
1716
|
+
// The `ngServerMode` guard also enables tree-shaking of the `from()`
|
|
1717
|
+
// function from the common bundle, as it's only used in server code.
|
|
1718
|
+
typeof ngServerMode !== 'undefined' && ngServerMode && xhrFactory.ɵloadImpl
|
|
1719
|
+
? from(xhrFactory.ɵloadImpl())
|
|
1720
|
+
: of(null);
|
|
1721
|
+
return source.pipe(switchMap(() => {
|
|
1722
|
+
// Everything happens on Observable subscription.
|
|
1723
|
+
return new Observable((observer) => {
|
|
1724
|
+
// Start by setting up the XHR object with request method, URL, and withCredentials
|
|
1725
|
+
// flag.
|
|
1726
|
+
const xhr = xhrFactory.build();
|
|
1727
|
+
xhr.open(req.method, req.urlWithParams);
|
|
1728
|
+
if (req.withCredentials) {
|
|
1729
|
+
xhr.withCredentials = true;
|
|
1730
|
+
}
|
|
1731
|
+
// Add all the requested headers.
|
|
1732
|
+
req.headers.forEach((name, values) => xhr.setRequestHeader(name, values.join(',')));
|
|
1733
|
+
// Add an Accept header if one isn't present already.
|
|
1734
|
+
if (!req.headers.has(ACCEPT_HEADER)) {
|
|
1735
|
+
xhr.setRequestHeader(ACCEPT_HEADER, ACCEPT_HEADER_VALUE);
|
|
1736
|
+
}
|
|
1737
|
+
// Auto-detect the Content-Type header if one isn't present already.
|
|
1738
|
+
if (!req.headers.has(CONTENT_TYPE_HEADER)) {
|
|
1739
|
+
const detectedType = req.detectContentTypeHeader();
|
|
1740
|
+
// Sometimes Content-Type detection fails.
|
|
1741
|
+
if (detectedType !== null) {
|
|
1742
|
+
xhr.setRequestHeader(CONTENT_TYPE_HEADER, detectedType);
|
|
1743
|
+
}
|
|
1744
|
+
}
|
|
1745
|
+
if (req.timeout) {
|
|
1746
|
+
xhr.timeout = req.timeout;
|
|
1747
|
+
}
|
|
1748
|
+
// Set the responseType if one was requested.
|
|
1749
|
+
if (req.responseType) {
|
|
1750
|
+
const responseType = req.responseType.toLowerCase();
|
|
1751
|
+
// JSON responses need to be processed as text. This is because if the server
|
|
1752
|
+
// returns an XSSI-prefixed JSON response, the browser will fail to parse it,
|
|
1753
|
+
// xhr.response will be null, and xhr.responseText cannot be accessed to
|
|
1754
|
+
// retrieve the prefixed JSON data in order to strip the prefix. Thus, all JSON
|
|
1755
|
+
// is parsed by first requesting text and then applying JSON.parse.
|
|
1756
|
+
xhr.responseType = (responseType !== 'json' ? responseType : 'text');
|
|
1757
|
+
}
|
|
1758
|
+
// Serialize the request body if one is present. If not, this will be set to null.
|
|
1759
|
+
const reqBody = req.serializeBody();
|
|
1760
|
+
// If progress events are enabled, response headers will be delivered
|
|
1761
|
+
// in two events - the HttpHeaderResponse event and the full HttpResponse
|
|
1762
|
+
// event. However, since response headers don't change in between these
|
|
1763
|
+
// two events, it doesn't make sense to parse them twice. So headerResponse
|
|
1764
|
+
// caches the data extracted from the response whenever it's first parsed,
|
|
1765
|
+
// to ensure parsing isn't duplicated.
|
|
1766
|
+
let headerResponse = null;
|
|
1767
|
+
// partialFromXhr extracts the HttpHeaderResponse from the current XMLHttpRequest
|
|
1768
|
+
// state, and memoizes it into headerResponse.
|
|
1769
|
+
const partialFromXhr = () => {
|
|
1770
|
+
if (headerResponse !== null) {
|
|
1771
|
+
return headerResponse;
|
|
1772
|
+
}
|
|
1773
|
+
const statusText = xhr.statusText || 'OK';
|
|
1774
|
+
// Parse headers from XMLHttpRequest - this step is lazy.
|
|
1775
|
+
const headers = new HttpHeaders(xhr.getAllResponseHeaders());
|
|
1776
|
+
// Read the response URL from the XMLHttpResponse instance and fall back on the
|
|
1777
|
+
// request URL.
|
|
1778
|
+
const url = xhr.responseURL || req.url;
|
|
1779
|
+
// Construct the HttpHeaderResponse and memoize it.
|
|
1780
|
+
headerResponse = new HttpHeaderResponse({ headers, status: xhr.status, statusText, url });
|
|
1781
|
+
return headerResponse;
|
|
1782
|
+
};
|
|
1783
|
+
// Next, a few closures are defined for the various events which XMLHttpRequest can
|
|
1784
|
+
// emit. This allows them to be unregistered as event listeners later.
|
|
1785
|
+
// First up is the load event, which represents a response being fully available.
|
|
1786
|
+
const onLoad = () => {
|
|
1787
|
+
// Read response state from the memoized partial data.
|
|
1788
|
+
let { headers, status, statusText, url } = partialFromXhr();
|
|
1789
|
+
// The body will be read out if present.
|
|
1790
|
+
let body = null;
|
|
1791
|
+
if (status !== HTTP_STATUS_CODE_NO_CONTENT) {
|
|
1792
|
+
// Use XMLHttpRequest.response if set, responseText otherwise.
|
|
1793
|
+
body = typeof xhr.response === 'undefined' ? xhr.responseText : xhr.response;
|
|
1743
1794
|
}
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
if (timeoutId !== undefined) {
|
|
1748
|
-
clearTimeout(timeoutId);
|
|
1749
|
-
}
|
|
1750
|
-
aborter.abort();
|
|
1751
|
-
};
|
|
1752
|
-
});
|
|
1753
|
-
}
|
|
1754
|
-
async doRequest(request, signal, observer) {
|
|
1755
|
-
const init = this.createRequestInit(request);
|
|
1756
|
-
let response;
|
|
1757
|
-
try {
|
|
1758
|
-
// Run fetch outside of Angular zone.
|
|
1759
|
-
// This is due to Node.js fetch implementation (Undici) which uses a number of setTimeouts to check if
|
|
1760
|
-
// the response should eventually timeout which causes extra CD cycles every 500ms
|
|
1761
|
-
const fetchPromise = this.ngZone.runOutsideAngular(() => this.fetchImpl(request.urlWithParams, { signal, ...init }));
|
|
1762
|
-
// Make sure Zone.js doesn't trigger false-positive unhandled promise
|
|
1763
|
-
// error in case the Promise is rejected synchronously. See function
|
|
1764
|
-
// description for additional information.
|
|
1765
|
-
silenceSuperfluousUnhandledPromiseRejection(fetchPromise);
|
|
1766
|
-
// Send the `Sent` event before awaiting the response.
|
|
1767
|
-
observer.next({ type: HttpEventType.Sent });
|
|
1768
|
-
response = await fetchPromise;
|
|
1769
|
-
}
|
|
1770
|
-
catch (error) {
|
|
1771
|
-
observer.error(new HttpErrorResponse({
|
|
1772
|
-
error,
|
|
1773
|
-
status: error.status ?? 0,
|
|
1774
|
-
statusText: error.statusText,
|
|
1775
|
-
url: request.urlWithParams,
|
|
1776
|
-
headers: error.headers,
|
|
1777
|
-
}));
|
|
1778
|
-
return;
|
|
1779
|
-
}
|
|
1780
|
-
const headers = new HttpHeaders(response.headers);
|
|
1781
|
-
const statusText = response.statusText;
|
|
1782
|
-
const url = getResponseUrl$1(response) ?? request.urlWithParams;
|
|
1783
|
-
let status = response.status;
|
|
1784
|
-
let body = null;
|
|
1785
|
-
if (request.reportProgress) {
|
|
1786
|
-
observer.next(new HttpHeaderResponse({ headers, status, statusText, url }));
|
|
1787
|
-
}
|
|
1788
|
-
if (response.body) {
|
|
1789
|
-
// Read Progress
|
|
1790
|
-
const contentLength = response.headers.get('content-length');
|
|
1791
|
-
const chunks = [];
|
|
1792
|
-
const reader = response.body.getReader();
|
|
1793
|
-
let receivedLength = 0;
|
|
1794
|
-
let decoder;
|
|
1795
|
-
let partialText;
|
|
1796
|
-
// We have to check whether the Zone is defined in the global scope because this may be called
|
|
1797
|
-
// when the zone is nooped.
|
|
1798
|
-
const reqZone = typeof Zone !== 'undefined' && Zone.current;
|
|
1799
|
-
let canceled = false;
|
|
1800
|
-
// Perform response processing outside of Angular zone to
|
|
1801
|
-
// ensure no excessive change detection runs are executed
|
|
1802
|
-
// Here calling the async ReadableStreamDefaultReader.read() is responsible for triggering CD
|
|
1803
|
-
await this.ngZone.runOutsideAngular(async () => {
|
|
1804
|
-
while (true) {
|
|
1805
|
-
// Prevent reading chunks if the app is destroyed. Otherwise, we risk doing
|
|
1806
|
-
// unnecessary work or triggering side effects after teardown.
|
|
1807
|
-
// This may happen if the app was explicitly destroyed before
|
|
1808
|
-
// the response returned entirely.
|
|
1809
|
-
if (this.destroyed) {
|
|
1810
|
-
// Streams left in a pending state (due to `break` without cancel) may
|
|
1811
|
-
// continue consuming or holding onto data behind the scenes.
|
|
1812
|
-
// Calling `reader.cancel()` allows the browser or the underlying
|
|
1813
|
-
// system to release any network or memory resources associated with the stream.
|
|
1814
|
-
await reader.cancel();
|
|
1815
|
-
canceled = true;
|
|
1816
|
-
break;
|
|
1795
|
+
// Normalize another potential bug (this one comes from CORS).
|
|
1796
|
+
if (status === 0) {
|
|
1797
|
+
status = !!body ? HTTP_STATUS_CODE_OK : 0;
|
|
1817
1798
|
}
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1799
|
+
// ok determines whether the response will be transmitted on the event or
|
|
1800
|
+
// error channel. Unsuccessful status codes (not 2xx) will always be errors,
|
|
1801
|
+
// but a successful status code can still result in an error if the user
|
|
1802
|
+
// asked for JSON data and the body cannot be parsed as such.
|
|
1803
|
+
let ok = status >= 200 && status < 300;
|
|
1804
|
+
// Check whether the body needs to be parsed as JSON (in many cases the browser
|
|
1805
|
+
// will have done that already).
|
|
1806
|
+
if (req.responseType === 'json' && typeof body === 'string') {
|
|
1807
|
+
// Save the original body, before attempting XSSI prefix stripping.
|
|
1808
|
+
const originalBody = body;
|
|
1809
|
+
body = body.replace(XSSI_PREFIX, '');
|
|
1810
|
+
try {
|
|
1811
|
+
// Attempt the parse. If it fails, a parse error should be delivered to the
|
|
1812
|
+
// user.
|
|
1813
|
+
body = body !== '' ? JSON.parse(body) : null;
|
|
1814
|
+
}
|
|
1815
|
+
catch (error) {
|
|
1816
|
+
// Since the JSON.parse failed, it's reasonable to assume this might not have
|
|
1817
|
+
// been a JSON response. Restore the original body (including any XSSI prefix)
|
|
1818
|
+
// to deliver a better error response.
|
|
1819
|
+
body = originalBody;
|
|
1820
|
+
// If this was an error request to begin with, leave it as a string, it
|
|
1821
|
+
// probably just isn't JSON. Otherwise, deliver the parsing error to the user.
|
|
1822
|
+
if (ok) {
|
|
1823
|
+
// Even though the response status was 2xx, this is still an error.
|
|
1824
|
+
ok = false;
|
|
1825
|
+
// The parse error contains the text of the body that failed to parse.
|
|
1826
|
+
body = { error, text: body };
|
|
1827
|
+
}
|
|
1828
|
+
}
|
|
1821
1829
|
}
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
partialText,
|
|
1835
|
-
});
|
|
1836
|
-
reqZone ? reqZone.run(reportProgress) : reportProgress();
|
|
1830
|
+
if (ok) {
|
|
1831
|
+
// A successful response is delivered on the event stream.
|
|
1832
|
+
observer.next(new HttpResponse({
|
|
1833
|
+
body,
|
|
1834
|
+
headers,
|
|
1835
|
+
status,
|
|
1836
|
+
statusText,
|
|
1837
|
+
url: url || undefined,
|
|
1838
|
+
}));
|
|
1839
|
+
// The full body has been received and delivered, no further events
|
|
1840
|
+
// are possible. This request is complete.
|
|
1841
|
+
observer.complete();
|
|
1837
1842
|
}
|
|
1843
|
+
else {
|
|
1844
|
+
// An unsuccessful request is delivered on the error channel.
|
|
1845
|
+
observer.error(new HttpErrorResponse({
|
|
1846
|
+
// The error in this case is the response body (error from the server).
|
|
1847
|
+
error: body,
|
|
1848
|
+
headers,
|
|
1849
|
+
status,
|
|
1850
|
+
statusText,
|
|
1851
|
+
url: url || undefined,
|
|
1852
|
+
}));
|
|
1853
|
+
}
|
|
1854
|
+
};
|
|
1855
|
+
// The onError callback is called when something goes wrong at the network level.
|
|
1856
|
+
// Connection timeout, DNS error, offline, etc. These are actual errors, and are
|
|
1857
|
+
// transmitted on the error channel.
|
|
1858
|
+
const onError = (error) => {
|
|
1859
|
+
const { url } = partialFromXhr();
|
|
1860
|
+
const res = new HttpErrorResponse({
|
|
1861
|
+
error,
|
|
1862
|
+
status: xhr.status || 0,
|
|
1863
|
+
statusText: xhr.statusText || 'Unknown Error',
|
|
1864
|
+
url: url || undefined,
|
|
1865
|
+
});
|
|
1866
|
+
observer.error(res);
|
|
1867
|
+
};
|
|
1868
|
+
let onTimeout = onError;
|
|
1869
|
+
if (req.timeout) {
|
|
1870
|
+
onTimeout = (_) => {
|
|
1871
|
+
const { url } = partialFromXhr();
|
|
1872
|
+
const res = new HttpErrorResponse({
|
|
1873
|
+
error: new DOMException('Request timed out', 'TimeoutError'),
|
|
1874
|
+
status: xhr.status || 0,
|
|
1875
|
+
statusText: xhr.statusText || 'Request timeout',
|
|
1876
|
+
url: url || undefined,
|
|
1877
|
+
});
|
|
1878
|
+
observer.error(res);
|
|
1879
|
+
};
|
|
1838
1880
|
}
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
parseBody(request, binContent, contentType, status) {
|
|
1901
|
-
switch (request.responseType) {
|
|
1902
|
-
case 'json':
|
|
1903
|
-
// stripping the XSSI when present
|
|
1904
|
-
const text = new TextDecoder().decode(binContent).replace(XSSI_PREFIX$1, '');
|
|
1905
|
-
if (text === '') {
|
|
1906
|
-
return null;
|
|
1907
|
-
}
|
|
1908
|
-
try {
|
|
1909
|
-
return JSON.parse(text);
|
|
1910
|
-
}
|
|
1911
|
-
catch (e) {
|
|
1912
|
-
// Allow handling non-JSON errors (!) as plain text, same as the XHR
|
|
1913
|
-
// backend. Without this special sauce, any non-JSON error would be
|
|
1914
|
-
// completely inaccessible downstream as the `HttpErrorResponse.error`
|
|
1915
|
-
// would be set to the `SyntaxError` from then failing `JSON.parse`.
|
|
1916
|
-
if (status < 200 || status >= 300) {
|
|
1917
|
-
return text;
|
|
1881
|
+
// The sentHeaders flag tracks whether the HttpResponseHeaders event
|
|
1882
|
+
// has been sent on the stream. This is necessary to track if progress
|
|
1883
|
+
// is enabled since the event will be sent on only the first download
|
|
1884
|
+
// progress event.
|
|
1885
|
+
let sentHeaders = false;
|
|
1886
|
+
// The download progress event handler, which is only registered if
|
|
1887
|
+
// progress events are enabled.
|
|
1888
|
+
const onDownProgress = (event) => {
|
|
1889
|
+
// Send the HttpResponseHeaders event if it hasn't been sent already.
|
|
1890
|
+
if (!sentHeaders) {
|
|
1891
|
+
observer.next(partialFromXhr());
|
|
1892
|
+
sentHeaders = true;
|
|
1893
|
+
}
|
|
1894
|
+
// Start building the download progress event to deliver on the response
|
|
1895
|
+
// event stream.
|
|
1896
|
+
let progressEvent = {
|
|
1897
|
+
type: HttpEventType.DownloadProgress,
|
|
1898
|
+
loaded: event.loaded,
|
|
1899
|
+
};
|
|
1900
|
+
// Set the total number of bytes in the event if it's available.
|
|
1901
|
+
if (event.lengthComputable) {
|
|
1902
|
+
progressEvent.total = event.total;
|
|
1903
|
+
}
|
|
1904
|
+
// If the request was for text content and a partial response is
|
|
1905
|
+
// available on XMLHttpRequest, include it in the progress event
|
|
1906
|
+
// to allow for streaming reads.
|
|
1907
|
+
if (req.responseType === 'text' && !!xhr.responseText) {
|
|
1908
|
+
progressEvent.partialText = xhr.responseText;
|
|
1909
|
+
}
|
|
1910
|
+
// Finally, fire the event.
|
|
1911
|
+
observer.next(progressEvent);
|
|
1912
|
+
};
|
|
1913
|
+
// The upload progress event handler, which is only registered if
|
|
1914
|
+
// progress events are enabled.
|
|
1915
|
+
const onUpProgress = (event) => {
|
|
1916
|
+
// Upload progress events are simpler. Begin building the progress
|
|
1917
|
+
// event.
|
|
1918
|
+
let progress = {
|
|
1919
|
+
type: HttpEventType.UploadProgress,
|
|
1920
|
+
loaded: event.loaded,
|
|
1921
|
+
};
|
|
1922
|
+
// If the total number of bytes being uploaded is available, include
|
|
1923
|
+
// it.
|
|
1924
|
+
if (event.lengthComputable) {
|
|
1925
|
+
progress.total = event.total;
|
|
1926
|
+
}
|
|
1927
|
+
// Send the event.
|
|
1928
|
+
observer.next(progress);
|
|
1929
|
+
};
|
|
1930
|
+
// By default, register for load and error events.
|
|
1931
|
+
xhr.addEventListener('load', onLoad);
|
|
1932
|
+
xhr.addEventListener('error', onError);
|
|
1933
|
+
xhr.addEventListener('timeout', onTimeout);
|
|
1934
|
+
xhr.addEventListener('abort', onError);
|
|
1935
|
+
// Progress events are only enabled if requested.
|
|
1936
|
+
if (req.reportProgress) {
|
|
1937
|
+
// Download progress is always enabled if requested.
|
|
1938
|
+
xhr.addEventListener('progress', onDownProgress);
|
|
1939
|
+
// Upload progress depends on whether there is a body to upload.
|
|
1940
|
+
if (reqBody !== null && xhr.upload) {
|
|
1941
|
+
xhr.upload.addEventListener('progress', onUpProgress);
|
|
1918
1942
|
}
|
|
1919
|
-
throw e;
|
|
1920
1943
|
}
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
return
|
|
1925
|
-
|
|
1926
|
-
return
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
if (!req.headers.has(ACCEPT_HEADER)) {
|
|
1946
|
-
headers[ACCEPT_HEADER] = ACCEPT_HEADER_VALUE;
|
|
1947
|
-
}
|
|
1948
|
-
// Auto-detect the Content-Type header if one isn't present already.
|
|
1949
|
-
if (!req.headers.has(CONTENT_TYPE_HEADER)) {
|
|
1950
|
-
const detectedType = req.detectContentTypeHeader();
|
|
1951
|
-
// Sometimes Content-Type detection fails.
|
|
1952
|
-
if (detectedType !== null) {
|
|
1953
|
-
headers[CONTENT_TYPE_HEADER] = detectedType;
|
|
1954
|
-
}
|
|
1955
|
-
}
|
|
1956
|
-
return {
|
|
1957
|
-
body: req.serializeBody(),
|
|
1958
|
-
method: req.method,
|
|
1959
|
-
headers,
|
|
1960
|
-
credentials,
|
|
1961
|
-
keepalive: req.keepalive,
|
|
1962
|
-
cache: req.cache,
|
|
1963
|
-
priority: req.priority,
|
|
1964
|
-
mode: req.mode,
|
|
1965
|
-
redirect: req.redirect,
|
|
1966
|
-
referrer: req.referrer,
|
|
1967
|
-
integrity: req.integrity,
|
|
1968
|
-
};
|
|
1969
|
-
}
|
|
1970
|
-
concatChunks(chunks, totalLength) {
|
|
1971
|
-
const chunksAll = new Uint8Array(totalLength);
|
|
1972
|
-
let position = 0;
|
|
1973
|
-
for (const chunk of chunks) {
|
|
1974
|
-
chunksAll.set(chunk, position);
|
|
1975
|
-
position += chunk.length;
|
|
1976
|
-
}
|
|
1977
|
-
return chunksAll;
|
|
1978
|
-
}
|
|
1979
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.0-next.2", ngImport: i0, type: FetchBackend, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1980
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.2.0-next.2", ngImport: i0, type: FetchBackend });
|
|
1981
|
-
}
|
|
1982
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.0-next.2", ngImport: i0, type: FetchBackend, decorators: [{
|
|
1983
|
-
type: Injectable
|
|
1984
|
-
}], ctorParameters: () => [] });
|
|
1985
|
-
/**
|
|
1986
|
-
* Abstract class to provide a mocked implementation of `fetch()`
|
|
1987
|
-
*/
|
|
1988
|
-
class FetchFactory {
|
|
1989
|
-
}
|
|
1990
|
-
function noop() { }
|
|
1991
|
-
function warningOptionsMessage(req) {
|
|
1992
|
-
if (req.credentials && req.withCredentials) {
|
|
1993
|
-
console.warn(_formatRuntimeError(2819 /* RuntimeErrorCode.WITH_CREDENTIALS_OVERRIDES_EXPLICIT_CREDENTIALS */, `Angular detected that a \`HttpClient\` request has both \`withCredentials: true\` and \`credentials: '${req.credentials}'\` options. The \`withCredentials\` option is overriding the explicit \`credentials\` setting to 'include'. Consider removing \`withCredentials\` and using \`credentials: '${req.credentials}'\` directly for clarity.`));
|
|
1944
|
+
// Fire the request, and notify the event stream that it was fired.
|
|
1945
|
+
xhr.send(reqBody);
|
|
1946
|
+
observer.next({ type: HttpEventType.Sent });
|
|
1947
|
+
// This is the return from the Observable function, which is the
|
|
1948
|
+
// request cancellation handler.
|
|
1949
|
+
return () => {
|
|
1950
|
+
// On a cancellation, remove all registered event listeners.
|
|
1951
|
+
xhr.removeEventListener('error', onError);
|
|
1952
|
+
xhr.removeEventListener('abort', onError);
|
|
1953
|
+
xhr.removeEventListener('load', onLoad);
|
|
1954
|
+
xhr.removeEventListener('timeout', onTimeout);
|
|
1955
|
+
if (req.reportProgress) {
|
|
1956
|
+
xhr.removeEventListener('progress', onDownProgress);
|
|
1957
|
+
if (reqBody !== null && xhr.upload) {
|
|
1958
|
+
xhr.upload.removeEventListener('progress', onUpProgress);
|
|
1959
|
+
}
|
|
1960
|
+
}
|
|
1961
|
+
// Finally, abort the in-flight request.
|
|
1962
|
+
if (xhr.readyState !== xhr.DONE) {
|
|
1963
|
+
xhr.abort();
|
|
1964
|
+
}
|
|
1965
|
+
};
|
|
1966
|
+
});
|
|
1967
|
+
}));
|
|
1994
1968
|
}
|
|
1969
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.0-next.2", ngImport: i0, type: HttpXhrBackend, deps: [{ token: XhrFactory }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1970
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.2.0-next.2", ngImport: i0, type: HttpXhrBackend, providedIn: 'root' });
|
|
1995
1971
|
}
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
* synchronously.
|
|
2001
|
-
*/
|
|
2002
|
-
function silenceSuperfluousUnhandledPromiseRejection(promise) {
|
|
2003
|
-
promise.then(noop, noop);
|
|
2004
|
-
}
|
|
1972
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.0-next.2", ngImport: i0, type: HttpXhrBackend, decorators: [{
|
|
1973
|
+
type: Injectable,
|
|
1974
|
+
args: [{ providedIn: 'root' }]
|
|
1975
|
+
}], ctorParameters: () => [{ type: XhrFactory }] });
|
|
2005
1976
|
|
|
2006
1977
|
function interceptorChainEndFn(req, finalHandlerFn) {
|
|
2007
1978
|
return finalHandlerFn(req);
|
|
@@ -2032,7 +2003,7 @@ const HTTP_INTERCEPTORS = new InjectionToken(ngDevMode ? 'HTTP_INTERCEPTORS' : '
|
|
|
2032
2003
|
/**
|
|
2033
2004
|
* A multi-provided token of `HttpInterceptorFn`s.
|
|
2034
2005
|
*/
|
|
2035
|
-
const HTTP_INTERCEPTOR_FNS = new InjectionToken(ngDevMode ? 'HTTP_INTERCEPTOR_FNS' : '');
|
|
2006
|
+
const HTTP_INTERCEPTOR_FNS = new InjectionToken(ngDevMode ? 'HTTP_INTERCEPTOR_FNS' : '', { factory: () => [] });
|
|
2036
2007
|
/**
|
|
2037
2008
|
* A multi-provided token of `HttpInterceptorFn`s that are only set in root.
|
|
2038
2009
|
*/
|
|
@@ -2067,15 +2038,33 @@ function legacyInterceptorFnFactory() {
|
|
|
2067
2038
|
}
|
|
2068
2039
|
};
|
|
2069
2040
|
}
|
|
2041
|
+
|
|
2042
|
+
/**
|
|
2043
|
+
* A final `HttpHandler` which will dispatch the request via browser HTTP APIs to a backend.
|
|
2044
|
+
*
|
|
2045
|
+
* Interceptors sit between the `HttpClient` interface and the `HttpBackend`.
|
|
2046
|
+
*
|
|
2047
|
+
* When injected, `HttpBackend` dispatches requests directly to the backend, without going
|
|
2048
|
+
* through the interceptor chain.
|
|
2049
|
+
*
|
|
2050
|
+
* @publicApi
|
|
2051
|
+
*/
|
|
2052
|
+
class HttpBackend {
|
|
2053
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.0-next.2", ngImport: i0, type: HttpBackend, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
2054
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.2.0-next.2", ngImport: i0, type: HttpBackend, providedIn: 'root', useExisting: HttpXhrBackend });
|
|
2055
|
+
}
|
|
2056
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.0-next.2", ngImport: i0, type: HttpBackend, decorators: [{
|
|
2057
|
+
type: Injectable,
|
|
2058
|
+
args: [{ providedIn: 'root', useExisting: HttpXhrBackend }]
|
|
2059
|
+
}] });
|
|
2070
2060
|
let fetchBackendWarningDisplayed = false;
|
|
2071
|
-
class HttpInterceptorHandler
|
|
2061
|
+
class HttpInterceptorHandler {
|
|
2072
2062
|
backend;
|
|
2073
2063
|
injector;
|
|
2074
2064
|
chain = null;
|
|
2075
2065
|
pendingTasks = inject(PendingTasks);
|
|
2076
2066
|
contributeToStability = inject(REQUESTS_CONTRIBUTE_TO_STABILITY);
|
|
2077
2067
|
constructor(backend, injector) {
|
|
2078
|
-
super();
|
|
2079
2068
|
this.backend = backend;
|
|
2080
2069
|
this.injector = injector;
|
|
2081
2070
|
// We strongly recommend using fetch backend for HTTP calls when SSR is used
|
|
@@ -2124,615 +2113,610 @@ class HttpInterceptorHandler extends HttpHandler {
|
|
|
2124
2113
|
}
|
|
2125
2114
|
}
|
|
2126
2115
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.0-next.2", ngImport: i0, type: HttpInterceptorHandler, deps: [{ token: HttpBackend }, { token: i0.EnvironmentInjector }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
2127
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.2.0-next.2", ngImport: i0, type: HttpInterceptorHandler });
|
|
2116
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.2.0-next.2", ngImport: i0, type: HttpInterceptorHandler, providedIn: 'root' });
|
|
2128
2117
|
}
|
|
2129
2118
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.0-next.2", ngImport: i0, type: HttpInterceptorHandler, decorators: [{
|
|
2130
|
-
type: Injectable
|
|
2119
|
+
type: Injectable,
|
|
2120
|
+
args: [{ providedIn: 'root' }]
|
|
2131
2121
|
}], ctorParameters: () => [{ type: HttpBackend }, { type: i0.EnvironmentInjector }] });
|
|
2122
|
+
/**
|
|
2123
|
+
* Transforms an `HttpRequest` into a stream of `HttpEvent`s, one of which will likely be a
|
|
2124
|
+
* `HttpResponse`.
|
|
2125
|
+
*
|
|
2126
|
+
* `HttpHandler` is injectable. When injected, the handler instance dispatches requests to the
|
|
2127
|
+
* first interceptor in the chain, which dispatches to the second, etc, eventually reaching the
|
|
2128
|
+
* `HttpBackend`.
|
|
2129
|
+
*
|
|
2130
|
+
* In an `HttpInterceptor`, the `HttpHandler` parameter is the next interceptor in the chain.
|
|
2131
|
+
*
|
|
2132
|
+
* @publicApi
|
|
2133
|
+
*/
|
|
2134
|
+
class HttpHandler {
|
|
2135
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.0-next.2", ngImport: i0, type: HttpHandler, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
2136
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.2.0-next.2", ngImport: i0, type: HttpHandler, providedIn: 'root', useExisting: HttpInterceptorHandler });
|
|
2137
|
+
}
|
|
2138
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.0-next.2", ngImport: i0, type: HttpHandler, decorators: [{
|
|
2139
|
+
type: Injectable,
|
|
2140
|
+
args: [{ providedIn: 'root', useExisting: HttpInterceptorHandler }]
|
|
2141
|
+
}] });
|
|
2132
2142
|
|
|
2133
|
-
// Every request made through JSONP needs a callback name that's unique across the
|
|
2134
|
-
// whole page. Each request is assigned an id and the callback name is constructed
|
|
2135
|
-
// from that. The next id to be assigned is tracked in a global variable here that
|
|
2136
|
-
// is shared among all applications on the page.
|
|
2137
|
-
let nextRequestId = 0;
|
|
2138
2143
|
/**
|
|
2139
|
-
*
|
|
2140
|
-
*
|
|
2144
|
+
* Constructs an instance of `HttpRequestOptions<T>` from a source `HttpMethodOptions` and
|
|
2145
|
+
* the given `body`. This function clones the object and adds the body.
|
|
2146
|
+
*
|
|
2147
|
+
* Note that the `responseType` *options* value is a String that identifies the
|
|
2148
|
+
* single data type of the response.
|
|
2149
|
+
* A single overload version of the method handles each response type.
|
|
2150
|
+
* The value of `responseType` cannot be a union, as the combined signature could imply.
|
|
2151
|
+
*
|
|
2141
2152
|
*/
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
+
function addBody(options, body) {
|
|
2154
|
+
return {
|
|
2155
|
+
body,
|
|
2156
|
+
headers: options.headers,
|
|
2157
|
+
context: options.context,
|
|
2158
|
+
observe: options.observe,
|
|
2159
|
+
params: options.params,
|
|
2160
|
+
reportProgress: options.reportProgress,
|
|
2161
|
+
responseType: options.responseType,
|
|
2162
|
+
withCredentials: options.withCredentials,
|
|
2163
|
+
credentials: options.credentials,
|
|
2164
|
+
transferCache: options.transferCache,
|
|
2165
|
+
timeout: options.timeout,
|
|
2166
|
+
keepalive: options.keepalive,
|
|
2167
|
+
priority: options.priority,
|
|
2168
|
+
cache: options.cache,
|
|
2169
|
+
mode: options.mode,
|
|
2170
|
+
redirect: options.redirect,
|
|
2171
|
+
integrity: options.integrity,
|
|
2172
|
+
referrer: options.referrer,
|
|
2173
|
+
};
|
|
2174
|
+
}
|
|
2153
2175
|
/**
|
|
2154
|
-
*
|
|
2176
|
+
* Performs HTTP requests.
|
|
2177
|
+
* This service is available as an injectable class, with methods to perform HTTP requests.
|
|
2178
|
+
* Each request method has multiple signatures, and the return type varies based on
|
|
2179
|
+
* the signature that is called (mainly the values of `observe` and `responseType`).
|
|
2180
|
+
*
|
|
2181
|
+
* Note that the `responseType` *options* value is a String that identifies the
|
|
2182
|
+
* single data type of the response.
|
|
2183
|
+
* A single overload version of the method handles each response type.
|
|
2184
|
+
* The value of `responseType` cannot be a union, as the combined signature could imply.
|
|
2155
2185
|
*
|
|
2156
|
-
*
|
|
2186
|
+
* @usageNotes
|
|
2157
2187
|
*
|
|
2188
|
+
* ### HTTP Request Example
|
|
2158
2189
|
*
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
/**
|
|
2163
|
-
* Factory function that determines where to store JSONP callbacks.
|
|
2190
|
+
* ```ts
|
|
2191
|
+
* // GET heroes whose name contains search term
|
|
2192
|
+
* searchHeroes(term: string): observable<Hero[]>{
|
|
2164
2193
|
*
|
|
2165
|
-
*
|
|
2166
|
-
*
|
|
2194
|
+
* const params = new HttpParams({fromString: 'name=term'});
|
|
2195
|
+
* return this.httpClient.request('GET', this.heroesUrl, {responseType:'json', params});
|
|
2196
|
+
* }
|
|
2197
|
+
* ```
|
|
2167
2198
|
*
|
|
2199
|
+
* Alternatively, the parameter string can be used without invoking HttpParams
|
|
2200
|
+
* by directly joining to the URL.
|
|
2201
|
+
* ```ts
|
|
2202
|
+
* this.httpClient.request('GET', this.heroesUrl + '?' + 'name=term', {responseType:'json'});
|
|
2203
|
+
* ```
|
|
2168
2204
|
*
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
*
|
|
2178
|
-
*
|
|
2179
|
-
*
|
|
2180
|
-
*
|
|
2205
|
+
*
|
|
2206
|
+
* ### JSONP Example
|
|
2207
|
+
* ```ts
|
|
2208
|
+
* requestJsonp(url, callback = 'callback') {
|
|
2209
|
+
* return this.httpClient.jsonp(this.heroesURL, callback);
|
|
2210
|
+
* }
|
|
2211
|
+
* ```
|
|
2212
|
+
*
|
|
2213
|
+
* ### PATCH Example
|
|
2214
|
+
* ```ts
|
|
2215
|
+
* // PATCH one of the heroes' name
|
|
2216
|
+
* patchHero (id: number, heroName: string): Observable<{}> {
|
|
2217
|
+
* const url = `${this.heroesUrl}/${id}`; // PATCH api/heroes/42
|
|
2218
|
+
* return this.httpClient.patch(url, {name: heroName}, httpOptions)
|
|
2219
|
+
* .pipe(catchError(this.handleError('patchHero')));
|
|
2220
|
+
* }
|
|
2221
|
+
* ```
|
|
2222
|
+
*
|
|
2223
|
+
* @see [HTTP Guide](guide/http)
|
|
2224
|
+
* @see [HTTP Request](api/common/http/HttpRequest)
|
|
2181
2225
|
*
|
|
2182
2226
|
* @publicApi
|
|
2183
2227
|
*/
|
|
2184
|
-
class
|
|
2185
|
-
|
|
2186
|
-
|
|
2228
|
+
class HttpClient {
|
|
2229
|
+
handler;
|
|
2230
|
+
constructor(handler) {
|
|
2231
|
+
this.handler = handler;
|
|
2232
|
+
}
|
|
2187
2233
|
/**
|
|
2188
|
-
*
|
|
2234
|
+
* Constructs an observable for a generic HTTP request that, when subscribed,
|
|
2235
|
+
* fires the request through the chain of registered interceptors and on to the
|
|
2236
|
+
* server.
|
|
2237
|
+
*
|
|
2238
|
+
* You can pass an `HttpRequest` directly as the only parameter. In this case,
|
|
2239
|
+
* the call returns an observable of the raw `HttpEvent` stream.
|
|
2240
|
+
*
|
|
2241
|
+
* Alternatively you can pass an HTTP method as the first parameter,
|
|
2242
|
+
* a URL string as the second, and an options hash containing the request body as the third.
|
|
2243
|
+
* See `addBody()`. In this case, the specified `responseType` and `observe` options determine the
|
|
2244
|
+
* type of returned observable.
|
|
2245
|
+
* * The `responseType` value determines how a successful response body is parsed.
|
|
2246
|
+
* * If `responseType` is the default `json`, you can pass a type interface for the resulting
|
|
2247
|
+
* object as a type parameter to the call.
|
|
2248
|
+
*
|
|
2249
|
+
* The `observe` value determines the return type, according to what you are interested in
|
|
2250
|
+
* observing.
|
|
2251
|
+
* * An `observe` value of events returns an observable of the raw `HttpEvent` stream, including
|
|
2252
|
+
* progress events by default.
|
|
2253
|
+
* * An `observe` value of response returns an observable of `HttpResponse<T>`,
|
|
2254
|
+
* where the `T` parameter depends on the `responseType` and any optionally provided type
|
|
2255
|
+
* parameter.
|
|
2256
|
+
* * An `observe` value of body returns an observable of `<T>` with the same `T` body type.
|
|
2257
|
+
*
|
|
2189
2258
|
*/
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2259
|
+
request(first, url, options = {}) {
|
|
2260
|
+
let req;
|
|
2261
|
+
// First, check whether the primary argument is an instance of `HttpRequest`.
|
|
2262
|
+
if (first instanceof HttpRequest) {
|
|
2263
|
+
// It is. The other arguments must be undefined (per the signatures) and can be
|
|
2264
|
+
// ignored.
|
|
2265
|
+
req = first;
|
|
2266
|
+
}
|
|
2267
|
+
else {
|
|
2268
|
+
// It's a string, so it represents a URL. Construct a request based on it,
|
|
2269
|
+
// and incorporate the remaining arguments (assuming `GET` unless a method is
|
|
2270
|
+
// provided.
|
|
2271
|
+
// Figure out the headers.
|
|
2272
|
+
let headers = undefined;
|
|
2273
|
+
if (options.headers instanceof HttpHeaders) {
|
|
2274
|
+
headers = options.headers;
|
|
2275
|
+
}
|
|
2276
|
+
else {
|
|
2277
|
+
headers = new HttpHeaders(options.headers);
|
|
2278
|
+
}
|
|
2279
|
+
// Sort out parameters.
|
|
2280
|
+
let params = undefined;
|
|
2281
|
+
if (!!options.params) {
|
|
2282
|
+
if (options.params instanceof HttpParams) {
|
|
2283
|
+
params = options.params;
|
|
2284
|
+
}
|
|
2285
|
+
else {
|
|
2286
|
+
params = new HttpParams({ fromObject: options.params });
|
|
2287
|
+
}
|
|
2288
|
+
}
|
|
2289
|
+
// Construct the request.
|
|
2290
|
+
req = new HttpRequest(first, url, options.body !== undefined ? options.body : null, {
|
|
2291
|
+
headers,
|
|
2292
|
+
context: options.context,
|
|
2293
|
+
params,
|
|
2294
|
+
reportProgress: options.reportProgress,
|
|
2295
|
+
// By default, JSON is assumed to be returned for all calls.
|
|
2296
|
+
responseType: options.responseType || 'json',
|
|
2297
|
+
withCredentials: options.withCredentials,
|
|
2298
|
+
transferCache: options.transferCache,
|
|
2299
|
+
keepalive: options.keepalive,
|
|
2300
|
+
priority: options.priority,
|
|
2301
|
+
cache: options.cache,
|
|
2302
|
+
mode: options.mode,
|
|
2303
|
+
redirect: options.redirect,
|
|
2304
|
+
credentials: options.credentials,
|
|
2305
|
+
referrer: options.referrer,
|
|
2306
|
+
integrity: options.integrity,
|
|
2307
|
+
timeout: options.timeout,
|
|
2308
|
+
});
|
|
2309
|
+
}
|
|
2310
|
+
// Start with an Observable.of() the initial request, and run the handler (which
|
|
2311
|
+
// includes all interceptors) inside a concatMap(). This way, the handler runs
|
|
2312
|
+
// inside an Observable chain, which causes interceptors to be re-run on every
|
|
2313
|
+
// subscription (this also makes retries re-run the handler, including interceptors).
|
|
2314
|
+
const events$ = of(req).pipe(concatMap((req) => this.handler.handle(req)));
|
|
2315
|
+
// If coming via the API signature which accepts a previously constructed HttpRequest,
|
|
2316
|
+
// the only option is to get the event stream. Otherwise, return the event stream if
|
|
2317
|
+
// that is what was requested.
|
|
2318
|
+
if (first instanceof HttpRequest || options.observe === 'events') {
|
|
2319
|
+
return events$;
|
|
2320
|
+
}
|
|
2321
|
+
// The requested stream contains either the full response or the body. In either
|
|
2322
|
+
// case, the first step is to filter the event stream to extract a stream of
|
|
2323
|
+
// responses(s).
|
|
2324
|
+
const res$ = (events$.pipe(filter((event) => event instanceof HttpResponse)));
|
|
2325
|
+
// Decide which stream to return.
|
|
2326
|
+
switch (options.observe || 'body') {
|
|
2327
|
+
case 'body':
|
|
2328
|
+
// The requested stream is the body. Map the response stream to the response
|
|
2329
|
+
// body. This could be done more simply, but a misbehaving interceptor might
|
|
2330
|
+
// transform the response body into a different format and ignore the requested
|
|
2331
|
+
// responseType. Guard against this by validating that the response is of the
|
|
2332
|
+
// requested type.
|
|
2333
|
+
switch (req.responseType) {
|
|
2334
|
+
case 'arraybuffer':
|
|
2335
|
+
return res$.pipe(map((res) => {
|
|
2336
|
+
// Validate that the body is an ArrayBuffer.
|
|
2337
|
+
if (res.body !== null && !(res.body instanceof ArrayBuffer)) {
|
|
2338
|
+
throw new _RuntimeError(2806 /* RuntimeErrorCode.RESPONSE_IS_NOT_AN_ARRAY_BUFFER */, ngDevMode && 'Response is not an ArrayBuffer.');
|
|
2339
|
+
}
|
|
2340
|
+
return res.body;
|
|
2341
|
+
}));
|
|
2342
|
+
case 'blob':
|
|
2343
|
+
return res$.pipe(map((res) => {
|
|
2344
|
+
// Validate that the body is a Blob.
|
|
2345
|
+
if (res.body !== null && !(res.body instanceof Blob)) {
|
|
2346
|
+
throw new _RuntimeError(2807 /* RuntimeErrorCode.RESPONSE_IS_NOT_A_BLOB */, ngDevMode && 'Response is not a Blob.');
|
|
2347
|
+
}
|
|
2348
|
+
return res.body;
|
|
2349
|
+
}));
|
|
2350
|
+
case 'text':
|
|
2351
|
+
return res$.pipe(map((res) => {
|
|
2352
|
+
// Validate that the body is a string.
|
|
2353
|
+
if (res.body !== null && typeof res.body !== 'string') {
|
|
2354
|
+
throw new _RuntimeError(2808 /* RuntimeErrorCode.RESPONSE_IS_NOT_A_STRING */, ngDevMode && 'Response is not a string.');
|
|
2355
|
+
}
|
|
2356
|
+
return res.body;
|
|
2357
|
+
}));
|
|
2358
|
+
case 'json':
|
|
2359
|
+
default:
|
|
2360
|
+
// No validation needed for JSON responses, as they can be of any type.
|
|
2361
|
+
return res$.pipe(map((res) => res.body));
|
|
2362
|
+
}
|
|
2363
|
+
case 'response':
|
|
2364
|
+
// The response stream was requested directly, so return it.
|
|
2365
|
+
return res$;
|
|
2366
|
+
default:
|
|
2367
|
+
// Guard against new future observe types being added.
|
|
2368
|
+
throw new _RuntimeError(2809 /* RuntimeErrorCode.UNHANDLED_OBSERVE_TYPE */, ngDevMode && `Unreachable: unhandled observe type ${options.observe}}`);
|
|
2369
|
+
}
|
|
2370
|
+
}
|
|
2371
|
+
/**
|
|
2372
|
+
* Constructs an observable that, when subscribed, causes the configured
|
|
2373
|
+
* `DELETE` request to execute on the server. See the individual overloads for
|
|
2374
|
+
* details on the return type.
|
|
2375
|
+
*
|
|
2376
|
+
* @param url The endpoint URL.
|
|
2377
|
+
* @param options The HTTP options to send with the request.
|
|
2378
|
+
*
|
|
2379
|
+
*/
|
|
2380
|
+
delete(url, options = {}) {
|
|
2381
|
+
return this.request('DELETE', url, options);
|
|
2382
|
+
}
|
|
2383
|
+
/**
|
|
2384
|
+
* Constructs an observable that, when subscribed, causes the configured
|
|
2385
|
+
* `GET` request to execute on the server. See the individual overloads for
|
|
2386
|
+
* details on the return type.
|
|
2387
|
+
*/
|
|
2388
|
+
get(url, options = {}) {
|
|
2389
|
+
return this.request('GET', url, options);
|
|
2390
|
+
}
|
|
2391
|
+
/**
|
|
2392
|
+
* Constructs an observable that, when subscribed, causes the configured
|
|
2393
|
+
* `HEAD` request to execute on the server. The `HEAD` method returns
|
|
2394
|
+
* meta information about the resource without transferring the
|
|
2395
|
+
* resource itself. See the individual overloads for
|
|
2396
|
+
* details on the return type.
|
|
2397
|
+
*/
|
|
2398
|
+
head(url, options = {}) {
|
|
2399
|
+
return this.request('HEAD', url, options);
|
|
2400
|
+
}
|
|
2401
|
+
/**
|
|
2402
|
+
* Constructs an `Observable` that, when subscribed, causes a request with the special method
|
|
2403
|
+
* `JSONP` to be dispatched via the interceptor pipeline.
|
|
2404
|
+
* The [JSONP pattern](https://en.wikipedia.org/wiki/JSONP) works around limitations of certain
|
|
2405
|
+
* API endpoints that don't support newer,
|
|
2406
|
+
* and preferable [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) protocol.
|
|
2407
|
+
* JSONP treats the endpoint API as a JavaScript file and tricks the browser to process the
|
|
2408
|
+
* requests even if the API endpoint is not located on the same domain (origin) as the client-side
|
|
2409
|
+
* application making the request.
|
|
2410
|
+
* The endpoint API must support JSONP callback for JSONP requests to work.
|
|
2411
|
+
* The resource API returns the JSON response wrapped in a callback function.
|
|
2412
|
+
* You can pass the callback function name as one of the query parameters.
|
|
2413
|
+
* Note that JSONP requests can only be used with `GET` requests.
|
|
2414
|
+
*
|
|
2415
|
+
* @param url The resource URL.
|
|
2416
|
+
* @param callbackParam The callback function name.
|
|
2417
|
+
*
|
|
2418
|
+
*/
|
|
2419
|
+
jsonp(url, callbackParam) {
|
|
2420
|
+
return this.request('JSONP', url, {
|
|
2421
|
+
params: new HttpParams().append(callbackParam, 'JSONP_CALLBACK'),
|
|
2422
|
+
observe: 'body',
|
|
2423
|
+
responseType: 'json',
|
|
2424
|
+
});
|
|
2194
2425
|
}
|
|
2195
2426
|
/**
|
|
2196
|
-
*
|
|
2427
|
+
* Constructs an `Observable` that, when subscribed, causes the configured
|
|
2428
|
+
* `OPTIONS` request to execute on the server. This method allows the client
|
|
2429
|
+
* to determine the supported HTTP methods and other capabilities of an endpoint,
|
|
2430
|
+
* without implying a resource action. See the individual overloads for
|
|
2431
|
+
* details on the return type.
|
|
2197
2432
|
*/
|
|
2198
|
-
|
|
2199
|
-
return
|
|
2433
|
+
options(url, options = {}) {
|
|
2434
|
+
return this.request('OPTIONS', url, options);
|
|
2200
2435
|
}
|
|
2201
2436
|
/**
|
|
2202
|
-
*
|
|
2203
|
-
*
|
|
2204
|
-
*
|
|
2205
|
-
*
|
|
2437
|
+
* Constructs an observable that, when subscribed, causes the configured
|
|
2438
|
+
* `PATCH` request to execute on the server. See the individual overloads for
|
|
2439
|
+
* details on the return type.
|
|
2206
2440
|
*/
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
// then the request was improperly routed here and cannot be handled.
|
|
2210
|
-
if (req.method !== 'JSONP') {
|
|
2211
|
-
throw new _RuntimeError(2810 /* RuntimeErrorCode.JSONP_WRONG_METHOD */, ngDevMode && JSONP_ERR_WRONG_METHOD);
|
|
2212
|
-
}
|
|
2213
|
-
else if (req.responseType !== 'json') {
|
|
2214
|
-
throw new _RuntimeError(2811 /* RuntimeErrorCode.JSONP_WRONG_RESPONSE_TYPE */, ngDevMode && JSONP_ERR_WRONG_RESPONSE_TYPE);
|
|
2215
|
-
}
|
|
2216
|
-
// Check the request headers. JSONP doesn't support headers and
|
|
2217
|
-
// cannot set any that were supplied.
|
|
2218
|
-
if (req.headers.keys().length > 0) {
|
|
2219
|
-
throw new _RuntimeError(2812 /* RuntimeErrorCode.JSONP_HEADERS_NOT_SUPPORTED */, ngDevMode && JSONP_ERR_HEADERS_NOT_SUPPORTED);
|
|
2220
|
-
}
|
|
2221
|
-
// Everything else happens inside the Observable boundary.
|
|
2222
|
-
return new Observable((observer) => {
|
|
2223
|
-
// The first step to make a request is to generate the callback name, and replace the
|
|
2224
|
-
// callback placeholder in the URL with the name. Care has to be taken here to ensure
|
|
2225
|
-
// a trailing &, if matched, gets inserted back into the URL in the correct place.
|
|
2226
|
-
const callback = this.nextCallback();
|
|
2227
|
-
const url = req.urlWithParams.replace(/=JSONP_CALLBACK(&|$)/, `=${callback}$1`);
|
|
2228
|
-
// Construct the <script> tag and point it at the URL.
|
|
2229
|
-
const node = this.document.createElement('script');
|
|
2230
|
-
node.src = url;
|
|
2231
|
-
// A JSONP request requires waiting for multiple callbacks. These variables
|
|
2232
|
-
// are closed over and track state across those callbacks.
|
|
2233
|
-
// The response object, if one has been received, or null otherwise.
|
|
2234
|
-
let body = null;
|
|
2235
|
-
// Whether the response callback has been called.
|
|
2236
|
-
let finished = false;
|
|
2237
|
-
// Set the response callback in this.callbackMap (which will be the window
|
|
2238
|
-
// object in the browser. The script being loaded via the <script> tag will
|
|
2239
|
-
// eventually call this callback.
|
|
2240
|
-
this.callbackMap[callback] = (data) => {
|
|
2241
|
-
// Data has been received from the JSONP script. Firstly, delete this callback.
|
|
2242
|
-
delete this.callbackMap[callback];
|
|
2243
|
-
// Set state to indicate data was received.
|
|
2244
|
-
body = data;
|
|
2245
|
-
finished = true;
|
|
2246
|
-
};
|
|
2247
|
-
// cleanup() is a utility closure that removes the <script> from the page and
|
|
2248
|
-
// the response callback from the window. This logic is used in both the
|
|
2249
|
-
// success, error, and cancellation paths, so it's extracted out for convenience.
|
|
2250
|
-
const cleanup = () => {
|
|
2251
|
-
node.removeEventListener('load', onLoad);
|
|
2252
|
-
node.removeEventListener('error', onError);
|
|
2253
|
-
// Remove the <script> tag if it's still on the page.
|
|
2254
|
-
node.remove();
|
|
2255
|
-
// Remove the response callback from the callbackMap (window object in the
|
|
2256
|
-
// browser).
|
|
2257
|
-
delete this.callbackMap[callback];
|
|
2258
|
-
};
|
|
2259
|
-
// onLoad() is the success callback which runs after the response callback
|
|
2260
|
-
// if the JSONP script loads successfully. The event itself is unimportant.
|
|
2261
|
-
// If something went wrong, onLoad() may run without the response callback
|
|
2262
|
-
// having been invoked.
|
|
2263
|
-
const onLoad = () => {
|
|
2264
|
-
// We wrap it in an extra Promise, to ensure the microtask
|
|
2265
|
-
// is scheduled after the loaded endpoint has executed any potential microtask itself,
|
|
2266
|
-
// which is not guaranteed in Internet Explorer and EdgeHTML. See issue #39496
|
|
2267
|
-
this.resolvedPromise.then(() => {
|
|
2268
|
-
// Cleanup the page.
|
|
2269
|
-
cleanup();
|
|
2270
|
-
// Check whether the response callback has run.
|
|
2271
|
-
if (!finished) {
|
|
2272
|
-
// It hasn't, something went wrong with the request. Return an error via
|
|
2273
|
-
// the Observable error path. All JSONP errors have status 0.
|
|
2274
|
-
observer.error(new HttpErrorResponse({
|
|
2275
|
-
url,
|
|
2276
|
-
status: 0,
|
|
2277
|
-
statusText: 'JSONP Error',
|
|
2278
|
-
error: new Error(JSONP_ERR_NO_CALLBACK),
|
|
2279
|
-
}));
|
|
2280
|
-
return;
|
|
2281
|
-
}
|
|
2282
|
-
// Success. body either contains the response body or null if none was
|
|
2283
|
-
// returned.
|
|
2284
|
-
observer.next(new HttpResponse({
|
|
2285
|
-
body,
|
|
2286
|
-
status: HTTP_STATUS_CODE_OK,
|
|
2287
|
-
statusText: 'OK',
|
|
2288
|
-
url,
|
|
2289
|
-
}));
|
|
2290
|
-
// Complete the stream, the response is over.
|
|
2291
|
-
observer.complete();
|
|
2292
|
-
});
|
|
2293
|
-
};
|
|
2294
|
-
// onError() is the error callback, which runs if the script returned generates
|
|
2295
|
-
// a Javascript error. It emits the error via the Observable error channel as
|
|
2296
|
-
// a HttpErrorResponse.
|
|
2297
|
-
const onError = (error) => {
|
|
2298
|
-
cleanup();
|
|
2299
|
-
// Wrap the error in a HttpErrorResponse.
|
|
2300
|
-
observer.error(new HttpErrorResponse({
|
|
2301
|
-
error,
|
|
2302
|
-
status: 0,
|
|
2303
|
-
statusText: 'JSONP Error',
|
|
2304
|
-
url,
|
|
2305
|
-
}));
|
|
2306
|
-
};
|
|
2307
|
-
// Subscribe to both the success (load) and error events on the <script> tag,
|
|
2308
|
-
// and add it to the page.
|
|
2309
|
-
node.addEventListener('load', onLoad);
|
|
2310
|
-
node.addEventListener('error', onError);
|
|
2311
|
-
this.document.body.appendChild(node);
|
|
2312
|
-
// The request has now been successfully sent.
|
|
2313
|
-
observer.next({ type: HttpEventType.Sent });
|
|
2314
|
-
// Cancellation handler.
|
|
2315
|
-
return () => {
|
|
2316
|
-
if (!finished) {
|
|
2317
|
-
this.removeListeners(node);
|
|
2318
|
-
}
|
|
2319
|
-
// And finally, clean up the page.
|
|
2320
|
-
cleanup();
|
|
2321
|
-
};
|
|
2322
|
-
});
|
|
2323
|
-
}
|
|
2324
|
-
removeListeners(script) {
|
|
2325
|
-
// Issue #34818
|
|
2326
|
-
// Changing <script>'s ownerDocument will prevent it from execution.
|
|
2327
|
-
// https://html.spec.whatwg.org/multipage/scripting.html#execute-the-script-block
|
|
2328
|
-
foreignDocument ??= this.document.implementation.createHTMLDocument();
|
|
2329
|
-
foreignDocument.adoptNode(script);
|
|
2330
|
-
}
|
|
2331
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.0-next.2", ngImport: i0, type: JsonpClientBackend, deps: [{ token: JsonpCallbackContext }, { token: DOCUMENT }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
2332
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.2.0-next.2", ngImport: i0, type: JsonpClientBackend });
|
|
2333
|
-
}
|
|
2334
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.0-next.2", ngImport: i0, type: JsonpClientBackend, decorators: [{
|
|
2335
|
-
type: Injectable
|
|
2336
|
-
}], ctorParameters: () => [{ type: JsonpCallbackContext }, { type: undefined, decorators: [{
|
|
2337
|
-
type: Inject,
|
|
2338
|
-
args: [DOCUMENT]
|
|
2339
|
-
}] }] });
|
|
2340
|
-
/**
|
|
2341
|
-
* Identifies requests with the method JSONP and shifts them to the `JsonpClientBackend`.
|
|
2342
|
-
*/
|
|
2343
|
-
function jsonpInterceptorFn(req, next) {
|
|
2344
|
-
if (req.method === 'JSONP') {
|
|
2345
|
-
return inject(JsonpClientBackend).handle(req);
|
|
2441
|
+
patch(url, body, options = {}) {
|
|
2442
|
+
return this.request('PATCH', url, addBody(options, body));
|
|
2346
2443
|
}
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
*
|
|
2356
|
-
* @publicApi
|
|
2357
|
-
*/
|
|
2358
|
-
class JsonpInterceptor {
|
|
2359
|
-
injector;
|
|
2360
|
-
constructor(injector) {
|
|
2361
|
-
this.injector = injector;
|
|
2444
|
+
/**
|
|
2445
|
+
* Constructs an observable that, when subscribed, causes the configured
|
|
2446
|
+
* `POST` request to execute on the server. The server responds with the location of
|
|
2447
|
+
* the replaced resource. See the individual overloads for
|
|
2448
|
+
* details on the return type.
|
|
2449
|
+
*/
|
|
2450
|
+
post(url, body, options = {}) {
|
|
2451
|
+
return this.request('POST', url, addBody(options, body));
|
|
2362
2452
|
}
|
|
2363
2453
|
/**
|
|
2364
|
-
*
|
|
2365
|
-
*
|
|
2366
|
-
*
|
|
2367
|
-
*
|
|
2368
|
-
* @returns An observable of the event stream.
|
|
2454
|
+
* Constructs an observable that, when subscribed, causes the configured
|
|
2455
|
+
* `PUT` request to execute on the server. The `PUT` method replaces an existing resource
|
|
2456
|
+
* with a new set of values.
|
|
2457
|
+
* See the individual overloads for details on the return type.
|
|
2369
2458
|
*/
|
|
2370
|
-
|
|
2371
|
-
return
|
|
2459
|
+
put(url, body, options = {}) {
|
|
2460
|
+
return this.request('PUT', url, addBody(options, body));
|
|
2372
2461
|
}
|
|
2373
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.0-next.2", ngImport: i0, type:
|
|
2374
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.2.0-next.2", ngImport: i0, type:
|
|
2462
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.0-next.2", ngImport: i0, type: HttpClient, deps: [{ token: HttpHandler }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
2463
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.2.0-next.2", ngImport: i0, type: HttpClient, providedIn: 'root' });
|
|
2375
2464
|
}
|
|
2376
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.0-next.2", ngImport: i0, type:
|
|
2377
|
-
type: Injectable
|
|
2378
|
-
|
|
2465
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.0-next.2", ngImport: i0, type: HttpClient, decorators: [{
|
|
2466
|
+
type: Injectable,
|
|
2467
|
+
args: [{ providedIn: 'root' }]
|
|
2468
|
+
}], ctorParameters: () => [{ type: HttpHandler }] });
|
|
2379
2469
|
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
},
|
|
2421
|
-
{
|
|
2422
|
-
property: 'credentials',
|
|
2423
|
-
errorCode: 2818 /* RuntimeErrorCode.CREDENTIALS_NOT_SUPPORTED_WITH_XHR */,
|
|
2424
|
-
},
|
|
2425
|
-
{
|
|
2426
|
-
property: 'integrity',
|
|
2427
|
-
errorCode: 2820 /* RuntimeErrorCode.INTEGRITY_NOT_SUPPORTED_WITH_XHR */,
|
|
2428
|
-
},
|
|
2429
|
-
{
|
|
2430
|
-
property: 'referrer',
|
|
2431
|
-
errorCode: 2821 /* RuntimeErrorCode.REFERRER_NOT_SUPPORTED_WITH_XHR */,
|
|
2432
|
-
},
|
|
2433
|
-
];
|
|
2434
|
-
// Check each unsupported option and warn if present
|
|
2435
|
-
for (const { property, errorCode } of unsupportedOptions) {
|
|
2436
|
-
if (req[property]) {
|
|
2437
|
-
console.warn(_formatRuntimeError(errorCode, `Angular detected that a \`HttpClient\` request with the \`${property}\` option was sent using XHR, which does not support it. To use the \`${property}\` option, enable Fetch API support by passing \`withFetch()\` as an argument to \`provideHttpClient()\`.`));
|
|
2438
|
-
}
|
|
2470
|
+
// Every request made through JSONP needs a callback name that's unique across the
|
|
2471
|
+
// whole page. Each request is assigned an id and the callback name is constructed
|
|
2472
|
+
// from that. The next id to be assigned is tracked in a global variable here that
|
|
2473
|
+
// is shared among all applications on the page.
|
|
2474
|
+
let nextRequestId = 0;
|
|
2475
|
+
/**
|
|
2476
|
+
* When a pending <script> is unsubscribed we'll move it to this document, so it won't be
|
|
2477
|
+
* executed.
|
|
2478
|
+
*/
|
|
2479
|
+
let foreignDocument;
|
|
2480
|
+
// Error text given when a JSONP script is injected, but doesn't invoke the callback
|
|
2481
|
+
// passed in its URL.
|
|
2482
|
+
const JSONP_ERR_NO_CALLBACK = 'JSONP injected script did not invoke callback.';
|
|
2483
|
+
// Error text given when a request is passed to the JsonpClientBackend that doesn't
|
|
2484
|
+
// have a request method JSONP.
|
|
2485
|
+
const JSONP_ERR_WRONG_METHOD = 'JSONP requests must use JSONP request method.';
|
|
2486
|
+
const JSONP_ERR_WRONG_RESPONSE_TYPE = 'JSONP requests must use Json response type.';
|
|
2487
|
+
// Error text given when a request is passed to the JsonpClientBackend that has
|
|
2488
|
+
// headers set
|
|
2489
|
+
const JSONP_ERR_HEADERS_NOT_SUPPORTED = 'JSONP requests do not support headers.';
|
|
2490
|
+
/**
|
|
2491
|
+
* DI token/abstract type representing a map of JSONP callbacks.
|
|
2492
|
+
*
|
|
2493
|
+
* In the browser, this should always be the `window` object.
|
|
2494
|
+
*
|
|
2495
|
+
*
|
|
2496
|
+
*/
|
|
2497
|
+
class JsonpCallbackContext {
|
|
2498
|
+
}
|
|
2499
|
+
/**
|
|
2500
|
+
* Factory function that determines where to store JSONP callbacks.
|
|
2501
|
+
*
|
|
2502
|
+
* Ordinarily JSONP callbacks are stored on the `window` object, but this may not exist
|
|
2503
|
+
* in test environments. In that case, callbacks are stored on an anonymous object instead.
|
|
2504
|
+
*
|
|
2505
|
+
*
|
|
2506
|
+
*/
|
|
2507
|
+
function jsonpCallbackContext() {
|
|
2508
|
+
if (typeof window === 'object') {
|
|
2509
|
+
return window;
|
|
2439
2510
|
}
|
|
2511
|
+
return {};
|
|
2440
2512
|
}
|
|
2441
2513
|
/**
|
|
2442
|
-
*
|
|
2514
|
+
* Processes an `HttpRequest` with the JSONP method,
|
|
2515
|
+
* by performing JSONP style requests.
|
|
2443
2516
|
* @see {@link HttpHandler}
|
|
2444
|
-
* @see {@link
|
|
2517
|
+
* @see {@link HttpXhrBackend}
|
|
2445
2518
|
*
|
|
2446
2519
|
* @publicApi
|
|
2447
2520
|
*/
|
|
2448
|
-
class
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2521
|
+
class JsonpClientBackend {
|
|
2522
|
+
callbackMap;
|
|
2523
|
+
document;
|
|
2524
|
+
/**
|
|
2525
|
+
* A resolved promise that can be used to schedule microtasks in the event handlers.
|
|
2526
|
+
*/
|
|
2527
|
+
resolvedPromise = Promise.resolve();
|
|
2528
|
+
constructor(callbackMap, document) {
|
|
2529
|
+
this.callbackMap = callbackMap;
|
|
2530
|
+
this.document = document;
|
|
2452
2531
|
}
|
|
2453
2532
|
/**
|
|
2454
|
-
*
|
|
2533
|
+
* Get the name of the next callback method, by incrementing the global `nextRequestId`.
|
|
2534
|
+
*/
|
|
2535
|
+
nextCallback() {
|
|
2536
|
+
return `ng_jsonp_callback_${nextRequestId++}`;
|
|
2537
|
+
}
|
|
2538
|
+
/**
|
|
2539
|
+
* Processes a JSONP request and returns an event stream of the results.
|
|
2455
2540
|
* @param req The request object.
|
|
2456
2541
|
* @returns An observable of the response events.
|
|
2542
|
+
*
|
|
2457
2543
|
*/
|
|
2458
2544
|
handle(req) {
|
|
2459
|
-
//
|
|
2460
|
-
//
|
|
2461
|
-
if (req.method
|
|
2462
|
-
throw new _RuntimeError(
|
|
2463
|
-
`Cannot make a JSONP request without JSONP support. To fix the problem, either add the \`withJsonpSupport()\` call (if \`provideHttpClient()\` is used) or import the \`HttpClientJsonpModule\` in the root NgModule.`);
|
|
2545
|
+
// Firstly, check both the method and response type. If either doesn't match
|
|
2546
|
+
// then the request was improperly routed here and cannot be handled.
|
|
2547
|
+
if (req.method !== 'JSONP') {
|
|
2548
|
+
throw new _RuntimeError(2810 /* RuntimeErrorCode.JSONP_WRONG_METHOD */, ngDevMode && JSONP_ERR_WRONG_METHOD);
|
|
2464
2549
|
}
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
//
|
|
2469
|
-
//
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
//
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
//
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
//
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
// partialFromXhr extracts the HttpHeaderResponse from the current XMLHttpRequest
|
|
2527
|
-
// state, and memoizes it into headerResponse.
|
|
2528
|
-
const partialFromXhr = () => {
|
|
2529
|
-
if (headerResponse !== null) {
|
|
2530
|
-
return headerResponse;
|
|
2531
|
-
}
|
|
2532
|
-
const statusText = xhr.statusText || 'OK';
|
|
2533
|
-
// Parse headers from XMLHttpRequest - this step is lazy.
|
|
2534
|
-
const headers = new HttpHeaders(xhr.getAllResponseHeaders());
|
|
2535
|
-
// Read the response URL from the XMLHttpResponse instance and fall back on the
|
|
2536
|
-
// request URL.
|
|
2537
|
-
const url = getResponseUrl(xhr) || req.url;
|
|
2538
|
-
// Construct the HttpHeaderResponse and memoize it.
|
|
2539
|
-
headerResponse = new HttpHeaderResponse({ headers, status: xhr.status, statusText, url });
|
|
2540
|
-
return headerResponse;
|
|
2541
|
-
};
|
|
2542
|
-
// Next, a few closures are defined for the various events which XMLHttpRequest can
|
|
2543
|
-
// emit. This allows them to be unregistered as event listeners later.
|
|
2544
|
-
// First up is the load event, which represents a response being fully available.
|
|
2545
|
-
const onLoad = () => {
|
|
2546
|
-
// Read response state from the memoized partial data.
|
|
2547
|
-
let { headers, status, statusText, url } = partialFromXhr();
|
|
2548
|
-
// The body will be read out if present.
|
|
2549
|
-
let body = null;
|
|
2550
|
-
if (status !== HTTP_STATUS_CODE_NO_CONTENT) {
|
|
2551
|
-
// Use XMLHttpRequest.response if set, responseText otherwise.
|
|
2552
|
-
body = typeof xhr.response === 'undefined' ? xhr.responseText : xhr.response;
|
|
2553
|
-
}
|
|
2554
|
-
// Normalize another potential bug (this one comes from CORS).
|
|
2555
|
-
if (status === 0) {
|
|
2556
|
-
status = !!body ? HTTP_STATUS_CODE_OK : 0;
|
|
2557
|
-
}
|
|
2558
|
-
// ok determines whether the response will be transmitted on the event or
|
|
2559
|
-
// error channel. Unsuccessful status codes (not 2xx) will always be errors,
|
|
2560
|
-
// but a successful status code can still result in an error if the user
|
|
2561
|
-
// asked for JSON data and the body cannot be parsed as such.
|
|
2562
|
-
let ok = status >= 200 && status < 300;
|
|
2563
|
-
// Check whether the body needs to be parsed as JSON (in many cases the browser
|
|
2564
|
-
// will have done that already).
|
|
2565
|
-
if (req.responseType === 'json' && typeof body === 'string') {
|
|
2566
|
-
// Save the original body, before attempting XSSI prefix stripping.
|
|
2567
|
-
const originalBody = body;
|
|
2568
|
-
body = body.replace(XSSI_PREFIX, '');
|
|
2569
|
-
try {
|
|
2570
|
-
// Attempt the parse. If it fails, a parse error should be delivered to the
|
|
2571
|
-
// user.
|
|
2572
|
-
body = body !== '' ? JSON.parse(body) : null;
|
|
2573
|
-
}
|
|
2574
|
-
catch (error) {
|
|
2575
|
-
// Since the JSON.parse failed, it's reasonable to assume this might not have
|
|
2576
|
-
// been a JSON response. Restore the original body (including any XSSI prefix)
|
|
2577
|
-
// to deliver a better error response.
|
|
2578
|
-
body = originalBody;
|
|
2579
|
-
// If this was an error request to begin with, leave it as a string, it
|
|
2580
|
-
// probably just isn't JSON. Otherwise, deliver the parsing error to the user.
|
|
2581
|
-
if (ok) {
|
|
2582
|
-
// Even though the response status was 2xx, this is still an error.
|
|
2583
|
-
ok = false;
|
|
2584
|
-
// The parse error contains the text of the body that failed to parse.
|
|
2585
|
-
body = { error, text: body };
|
|
2586
|
-
}
|
|
2587
|
-
}
|
|
2588
|
-
}
|
|
2589
|
-
if (ok) {
|
|
2590
|
-
// A successful response is delivered on the event stream.
|
|
2591
|
-
observer.next(new HttpResponse({
|
|
2592
|
-
body,
|
|
2593
|
-
headers,
|
|
2594
|
-
status,
|
|
2595
|
-
statusText,
|
|
2596
|
-
url: url || undefined,
|
|
2597
|
-
}));
|
|
2598
|
-
// The full body has been received and delivered, no further events
|
|
2599
|
-
// are possible. This request is complete.
|
|
2600
|
-
observer.complete();
|
|
2601
|
-
}
|
|
2602
|
-
else {
|
|
2603
|
-
// An unsuccessful request is delivered on the error channel.
|
|
2550
|
+
else if (req.responseType !== 'json') {
|
|
2551
|
+
throw new _RuntimeError(2811 /* RuntimeErrorCode.JSONP_WRONG_RESPONSE_TYPE */, ngDevMode && JSONP_ERR_WRONG_RESPONSE_TYPE);
|
|
2552
|
+
}
|
|
2553
|
+
// Check the request headers. JSONP doesn't support headers and
|
|
2554
|
+
// cannot set any that were supplied.
|
|
2555
|
+
if (req.headers.keys().length > 0) {
|
|
2556
|
+
throw new _RuntimeError(2812 /* RuntimeErrorCode.JSONP_HEADERS_NOT_SUPPORTED */, ngDevMode && JSONP_ERR_HEADERS_NOT_SUPPORTED);
|
|
2557
|
+
}
|
|
2558
|
+
// Everything else happens inside the Observable boundary.
|
|
2559
|
+
return new Observable((observer) => {
|
|
2560
|
+
// The first step to make a request is to generate the callback name, and replace the
|
|
2561
|
+
// callback placeholder in the URL with the name. Care has to be taken here to ensure
|
|
2562
|
+
// a trailing &, if matched, gets inserted back into the URL in the correct place.
|
|
2563
|
+
const callback = this.nextCallback();
|
|
2564
|
+
const url = req.urlWithParams.replace(/=JSONP_CALLBACK(&|$)/, `=${callback}$1`);
|
|
2565
|
+
// Construct the <script> tag and point it at the URL.
|
|
2566
|
+
const node = this.document.createElement('script');
|
|
2567
|
+
node.src = url;
|
|
2568
|
+
// A JSONP request requires waiting for multiple callbacks. These variables
|
|
2569
|
+
// are closed over and track state across those callbacks.
|
|
2570
|
+
// The response object, if one has been received, or null otherwise.
|
|
2571
|
+
let body = null;
|
|
2572
|
+
// Whether the response callback has been called.
|
|
2573
|
+
let finished = false;
|
|
2574
|
+
// Set the response callback in this.callbackMap (which will be the window
|
|
2575
|
+
// object in the browser. The script being loaded via the <script> tag will
|
|
2576
|
+
// eventually call this callback.
|
|
2577
|
+
this.callbackMap[callback] = (data) => {
|
|
2578
|
+
// Data has been received from the JSONP script. Firstly, delete this callback.
|
|
2579
|
+
delete this.callbackMap[callback];
|
|
2580
|
+
// Set state to indicate data was received.
|
|
2581
|
+
body = data;
|
|
2582
|
+
finished = true;
|
|
2583
|
+
};
|
|
2584
|
+
// cleanup() is a utility closure that removes the <script> from the page and
|
|
2585
|
+
// the response callback from the window. This logic is used in both the
|
|
2586
|
+
// success, error, and cancellation paths, so it's extracted out for convenience.
|
|
2587
|
+
const cleanup = () => {
|
|
2588
|
+
node.removeEventListener('load', onLoad);
|
|
2589
|
+
node.removeEventListener('error', onError);
|
|
2590
|
+
// Remove the <script> tag if it's still on the page.
|
|
2591
|
+
node.remove();
|
|
2592
|
+
// Remove the response callback from the callbackMap (window object in the
|
|
2593
|
+
// browser).
|
|
2594
|
+
delete this.callbackMap[callback];
|
|
2595
|
+
};
|
|
2596
|
+
// onLoad() is the success callback which runs after the response callback
|
|
2597
|
+
// if the JSONP script loads successfully. The event itself is unimportant.
|
|
2598
|
+
// If something went wrong, onLoad() may run without the response callback
|
|
2599
|
+
// having been invoked.
|
|
2600
|
+
const onLoad = () => {
|
|
2601
|
+
// We wrap it in an extra Promise, to ensure the microtask
|
|
2602
|
+
// is scheduled after the loaded endpoint has executed any potential microtask itself,
|
|
2603
|
+
// which is not guaranteed in Internet Explorer and EdgeHTML. See issue #39496
|
|
2604
|
+
this.resolvedPromise.then(() => {
|
|
2605
|
+
// Cleanup the page.
|
|
2606
|
+
cleanup();
|
|
2607
|
+
// Check whether the response callback has run.
|
|
2608
|
+
if (!finished) {
|
|
2609
|
+
// It hasn't, something went wrong with the request. Return an error via
|
|
2610
|
+
// the Observable error path. All JSONP errors have status 0.
|
|
2604
2611
|
observer.error(new HttpErrorResponse({
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
statusText,
|
|
2610
|
-
url: url || undefined,
|
|
2612
|
+
url,
|
|
2613
|
+
status: 0,
|
|
2614
|
+
statusText: 'JSONP Error',
|
|
2615
|
+
error: new Error(JSONP_ERR_NO_CALLBACK),
|
|
2611
2616
|
}));
|
|
2617
|
+
return;
|
|
2612
2618
|
}
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
if (!sentHeaders) {
|
|
2650
|
-
observer.next(partialFromXhr());
|
|
2651
|
-
sentHeaders = true;
|
|
2652
|
-
}
|
|
2653
|
-
// Start building the download progress event to deliver on the response
|
|
2654
|
-
// event stream.
|
|
2655
|
-
let progressEvent = {
|
|
2656
|
-
type: HttpEventType.DownloadProgress,
|
|
2657
|
-
loaded: event.loaded,
|
|
2658
|
-
};
|
|
2659
|
-
// Set the total number of bytes in the event if it's available.
|
|
2660
|
-
if (event.lengthComputable) {
|
|
2661
|
-
progressEvent.total = event.total;
|
|
2662
|
-
}
|
|
2663
|
-
// If the request was for text content and a partial response is
|
|
2664
|
-
// available on XMLHttpRequest, include it in the progress event
|
|
2665
|
-
// to allow for streaming reads.
|
|
2666
|
-
if (req.responseType === 'text' && !!xhr.responseText) {
|
|
2667
|
-
progressEvent.partialText = xhr.responseText;
|
|
2668
|
-
}
|
|
2669
|
-
// Finally, fire the event.
|
|
2670
|
-
observer.next(progressEvent);
|
|
2671
|
-
};
|
|
2672
|
-
// The upload progress event handler, which is only registered if
|
|
2673
|
-
// progress events are enabled.
|
|
2674
|
-
const onUpProgress = (event) => {
|
|
2675
|
-
// Upload progress events are simpler. Begin building the progress
|
|
2676
|
-
// event.
|
|
2677
|
-
let progress = {
|
|
2678
|
-
type: HttpEventType.UploadProgress,
|
|
2679
|
-
loaded: event.loaded,
|
|
2680
|
-
};
|
|
2681
|
-
// If the total number of bytes being uploaded is available, include
|
|
2682
|
-
// it.
|
|
2683
|
-
if (event.lengthComputable) {
|
|
2684
|
-
progress.total = event.total;
|
|
2685
|
-
}
|
|
2686
|
-
// Send the event.
|
|
2687
|
-
observer.next(progress);
|
|
2688
|
-
};
|
|
2689
|
-
// By default, register for load and error events.
|
|
2690
|
-
xhr.addEventListener('load', onLoad);
|
|
2691
|
-
xhr.addEventListener('error', onError);
|
|
2692
|
-
xhr.addEventListener('timeout', onTimeout);
|
|
2693
|
-
xhr.addEventListener('abort', onError);
|
|
2694
|
-
// Progress events are only enabled if requested.
|
|
2695
|
-
if (req.reportProgress) {
|
|
2696
|
-
// Download progress is always enabled if requested.
|
|
2697
|
-
xhr.addEventListener('progress', onDownProgress);
|
|
2698
|
-
// Upload progress depends on whether there is a body to upload.
|
|
2699
|
-
if (reqBody !== null && xhr.upload) {
|
|
2700
|
-
xhr.upload.addEventListener('progress', onUpProgress);
|
|
2701
|
-
}
|
|
2619
|
+
// Success. body either contains the response body or null if none was
|
|
2620
|
+
// returned.
|
|
2621
|
+
observer.next(new HttpResponse({
|
|
2622
|
+
body,
|
|
2623
|
+
status: HTTP_STATUS_CODE_OK,
|
|
2624
|
+
statusText: 'OK',
|
|
2625
|
+
url,
|
|
2626
|
+
}));
|
|
2627
|
+
// Complete the stream, the response is over.
|
|
2628
|
+
observer.complete();
|
|
2629
|
+
});
|
|
2630
|
+
};
|
|
2631
|
+
// onError() is the error callback, which runs if the script returned generates
|
|
2632
|
+
// a Javascript error. It emits the error via the Observable error channel as
|
|
2633
|
+
// a HttpErrorResponse.
|
|
2634
|
+
const onError = (error) => {
|
|
2635
|
+
cleanup();
|
|
2636
|
+
// Wrap the error in a HttpErrorResponse.
|
|
2637
|
+
observer.error(new HttpErrorResponse({
|
|
2638
|
+
error,
|
|
2639
|
+
status: 0,
|
|
2640
|
+
statusText: 'JSONP Error',
|
|
2641
|
+
url,
|
|
2642
|
+
}));
|
|
2643
|
+
};
|
|
2644
|
+
// Subscribe to both the success (load) and error events on the <script> tag,
|
|
2645
|
+
// and add it to the page.
|
|
2646
|
+
node.addEventListener('load', onLoad);
|
|
2647
|
+
node.addEventListener('error', onError);
|
|
2648
|
+
this.document.body.appendChild(node);
|
|
2649
|
+
// The request has now been successfully sent.
|
|
2650
|
+
observer.next({ type: HttpEventType.Sent });
|
|
2651
|
+
// Cancellation handler.
|
|
2652
|
+
return () => {
|
|
2653
|
+
if (!finished) {
|
|
2654
|
+
this.removeListeners(node);
|
|
2702
2655
|
}
|
|
2703
|
-
//
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
// request cancellation handler.
|
|
2708
|
-
return () => {
|
|
2709
|
-
// On a cancellation, remove all registered event listeners.
|
|
2710
|
-
xhr.removeEventListener('error', onError);
|
|
2711
|
-
xhr.removeEventListener('abort', onError);
|
|
2712
|
-
xhr.removeEventListener('load', onLoad);
|
|
2713
|
-
xhr.removeEventListener('timeout', onTimeout);
|
|
2714
|
-
if (req.reportProgress) {
|
|
2715
|
-
xhr.removeEventListener('progress', onDownProgress);
|
|
2716
|
-
if (reqBody !== null && xhr.upload) {
|
|
2717
|
-
xhr.upload.removeEventListener('progress', onUpProgress);
|
|
2718
|
-
}
|
|
2719
|
-
}
|
|
2720
|
-
// Finally, abort the in-flight request.
|
|
2721
|
-
if (xhr.readyState !== xhr.DONE) {
|
|
2722
|
-
xhr.abort();
|
|
2723
|
-
}
|
|
2724
|
-
};
|
|
2725
|
-
});
|
|
2726
|
-
}));
|
|
2656
|
+
// And finally, clean up the page.
|
|
2657
|
+
cleanup();
|
|
2658
|
+
};
|
|
2659
|
+
});
|
|
2727
2660
|
}
|
|
2728
|
-
|
|
2729
|
-
|
|
2661
|
+
removeListeners(script) {
|
|
2662
|
+
// Issue #34818
|
|
2663
|
+
// Changing <script>'s ownerDocument will prevent it from execution.
|
|
2664
|
+
// https://html.spec.whatwg.org/multipage/scripting.html#execute-the-script-block
|
|
2665
|
+
foreignDocument ??= this.document.implementation.createHTMLDocument();
|
|
2666
|
+
foreignDocument.adoptNode(script);
|
|
2667
|
+
}
|
|
2668
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.0-next.2", ngImport: i0, type: JsonpClientBackend, deps: [{ token: JsonpCallbackContext }, { token: DOCUMENT }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
2669
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.2.0-next.2", ngImport: i0, type: JsonpClientBackend });
|
|
2730
2670
|
}
|
|
2731
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.0-next.2", ngImport: i0, type:
|
|
2671
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.0-next.2", ngImport: i0, type: JsonpClientBackend, decorators: [{
|
|
2732
2672
|
type: Injectable
|
|
2733
|
-
}], ctorParameters: () => [{ type:
|
|
2673
|
+
}], ctorParameters: () => [{ type: JsonpCallbackContext }, { type: undefined, decorators: [{
|
|
2674
|
+
type: Inject,
|
|
2675
|
+
args: [DOCUMENT]
|
|
2676
|
+
}] }] });
|
|
2677
|
+
/**
|
|
2678
|
+
* Identifies requests with the method JSONP and shifts them to the `JsonpClientBackend`.
|
|
2679
|
+
*/
|
|
2680
|
+
function jsonpInterceptorFn(req, next) {
|
|
2681
|
+
if (req.method === 'JSONP') {
|
|
2682
|
+
return inject(JsonpClientBackend).handle(req);
|
|
2683
|
+
}
|
|
2684
|
+
// Fall through for normal HTTP requests.
|
|
2685
|
+
return next(req);
|
|
2686
|
+
}
|
|
2687
|
+
/**
|
|
2688
|
+
* Identifies requests with the method JSONP and
|
|
2689
|
+
* shifts them to the `JsonpClientBackend`.
|
|
2690
|
+
*
|
|
2691
|
+
* @see {@link HttpInterceptor}
|
|
2692
|
+
*
|
|
2693
|
+
* @publicApi
|
|
2694
|
+
*/
|
|
2695
|
+
class JsonpInterceptor {
|
|
2696
|
+
injector;
|
|
2697
|
+
constructor(injector) {
|
|
2698
|
+
this.injector = injector;
|
|
2699
|
+
}
|
|
2700
|
+
/**
|
|
2701
|
+
* Identifies and handles a given JSONP request.
|
|
2702
|
+
* @param initialRequest The outgoing request object to handle.
|
|
2703
|
+
* @param next The next interceptor in the chain, or the backend
|
|
2704
|
+
* if no interceptors remain in the chain.
|
|
2705
|
+
* @returns An observable of the event stream.
|
|
2706
|
+
*/
|
|
2707
|
+
intercept(initialRequest, next) {
|
|
2708
|
+
return runInInjectionContext(this.injector, () => jsonpInterceptorFn(initialRequest, (downstreamRequest) => next.handle(downstreamRequest)));
|
|
2709
|
+
}
|
|
2710
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.0-next.2", ngImport: i0, type: JsonpInterceptor, deps: [{ token: i0.EnvironmentInjector }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
2711
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.2.0-next.2", ngImport: i0, type: JsonpInterceptor });
|
|
2712
|
+
}
|
|
2713
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.0-next.2", ngImport: i0, type: JsonpInterceptor, decorators: [{
|
|
2714
|
+
type: Injectable
|
|
2715
|
+
}], ctorParameters: () => [{ type: i0.EnvironmentInjector }] });
|
|
2734
2716
|
|
|
2735
|
-
const XSRF_ENABLED = new InjectionToken(ngDevMode ? 'XSRF_ENABLED' : ''
|
|
2717
|
+
const XSRF_ENABLED = new InjectionToken(ngDevMode ? 'XSRF_ENABLED' : '', {
|
|
2718
|
+
factory: () => true,
|
|
2719
|
+
});
|
|
2736
2720
|
const XSRF_DEFAULT_COOKIE_NAME = 'XSRF-TOKEN';
|
|
2737
2721
|
const XSRF_COOKIE_NAME = new InjectionToken(ngDevMode ? 'XSRF_COOKIE_NAME' : '', {
|
|
2738
2722
|
providedIn: 'root',
|
|
@@ -2743,13 +2727,6 @@ const XSRF_HEADER_NAME = new InjectionToken(ngDevMode ? 'XSRF_HEADER_NAME' : '',
|
|
|
2743
2727
|
providedIn: 'root',
|
|
2744
2728
|
factory: () => XSRF_DEFAULT_HEADER_NAME,
|
|
2745
2729
|
});
|
|
2746
|
-
/**
|
|
2747
|
-
* Retrieves the current XSRF token to use with the next outgoing request.
|
|
2748
|
-
*
|
|
2749
|
-
* @publicApi
|
|
2750
|
-
*/
|
|
2751
|
-
class HttpXsrfTokenExtractor {
|
|
2752
|
-
}
|
|
2753
2730
|
/**
|
|
2754
2731
|
* `HttpXsrfTokenExtractor` which retrieves the token from a cookie.
|
|
2755
2732
|
*/
|
|
@@ -2779,10 +2756,11 @@ class HttpXsrfCookieExtractor {
|
|
|
2779
2756
|
return this.lastToken;
|
|
2780
2757
|
}
|
|
2781
2758
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.0-next.2", ngImport: i0, type: HttpXsrfCookieExtractor, deps: [{ token: DOCUMENT }, { token: XSRF_COOKIE_NAME }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
2782
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.2.0-next.2", ngImport: i0, type: HttpXsrfCookieExtractor });
|
|
2759
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.2.0-next.2", ngImport: i0, type: HttpXsrfCookieExtractor, providedIn: 'root' });
|
|
2783
2760
|
}
|
|
2784
2761
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.0-next.2", ngImport: i0, type: HttpXsrfCookieExtractor, decorators: [{
|
|
2785
|
-
type: Injectable
|
|
2762
|
+
type: Injectable,
|
|
2763
|
+
args: [{ providedIn: 'root' }]
|
|
2786
2764
|
}], ctorParameters: () => [{ type: undefined, decorators: [{
|
|
2787
2765
|
type: Inject,
|
|
2788
2766
|
args: [DOCUMENT]
|
|
@@ -2790,6 +2768,19 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.0-next.2",
|
|
|
2790
2768
|
type: Inject,
|
|
2791
2769
|
args: [XSRF_COOKIE_NAME]
|
|
2792
2770
|
}] }] });
|
|
2771
|
+
/**
|
|
2772
|
+
* Retrieves the current XSRF token to use with the next outgoing request.
|
|
2773
|
+
*
|
|
2774
|
+
* @publicApi
|
|
2775
|
+
*/
|
|
2776
|
+
class HttpXsrfTokenExtractor {
|
|
2777
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.0-next.2", ngImport: i0, type: HttpXsrfTokenExtractor, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
2778
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.2.0-next.2", ngImport: i0, type: HttpXsrfTokenExtractor, providedIn: 'root', useExisting: HttpXsrfCookieExtractor });
|
|
2779
|
+
}
|
|
2780
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.0-next.2", ngImport: i0, type: HttpXsrfTokenExtractor, decorators: [{
|
|
2781
|
+
type: Injectable,
|
|
2782
|
+
args: [{ providedIn: 'root', useExisting: HttpXsrfCookieExtractor }]
|
|
2783
|
+
}] });
|
|
2793
2784
|
function xsrfInterceptorFn(req, next) {
|
|
2794
2785
|
const lcUrl = req.url.toLowerCase();
|
|
2795
2786
|
// Skip both non-mutating requests and absolute URLs.
|
|
@@ -2891,7 +2882,6 @@ function provideHttpClient(...features) {
|
|
|
2891
2882
|
}
|
|
2892
2883
|
const providers = [
|
|
2893
2884
|
HttpClient,
|
|
2894
|
-
HttpXhrBackend,
|
|
2895
2885
|
HttpInterceptorHandler,
|
|
2896
2886
|
{ provide: HttpHandler, useExisting: HttpInterceptorHandler },
|
|
2897
2887
|
{
|
|
@@ -2905,8 +2895,6 @@ function provideHttpClient(...features) {
|
|
|
2905
2895
|
useValue: xsrfInterceptorFn,
|
|
2906
2896
|
multi: true,
|
|
2907
2897
|
},
|
|
2908
|
-
{ provide: XSRF_ENABLED, useValue: true },
|
|
2909
|
-
{ provide: HttpXsrfTokenExtractor, useClass: HttpXsrfCookieExtractor },
|
|
2910
2898
|
];
|
|
2911
2899
|
for (const feature of features) {
|
|
2912
2900
|
providers.push(...feature.ɵproviders);
|