@angular/common 21.0.0-next.9 → 21.0.0-rc.0
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/fesm2022/_common_module-chunk.mjs +3050 -4380
- package/fesm2022/_common_module-chunk.mjs.map +1 -1
- package/fesm2022/_location-chunk.mjs +392 -588
- package/fesm2022/_location-chunk.mjs.map +1 -1
- package/fesm2022/_module-chunk.mjs +2042 -3001
- package/fesm2022/_module-chunk.mjs.map +1 -1
- package/fesm2022/_platform_navigation-chunk.mjs +30 -16
- package/fesm2022/_platform_navigation-chunk.mjs.map +1 -1
- package/fesm2022/_xhr-chunk.mjs +10 -16
- package/fesm2022/_xhr-chunk.mjs.map +1 -1
- package/fesm2022/common.mjs +1135 -1837
- package/fesm2022/common.mjs.map +1 -1
- package/fesm2022/http-testing.mjs +259 -310
- package/fesm2022/http-testing.mjs.map +1 -1
- package/fesm2022/http.mjs +302 -370
- package/fesm2022/http.mjs.map +1 -1
- package/fesm2022/testing.mjs +596 -552
- package/fesm2022/testing.mjs.map +1 -1
- package/fesm2022/upgrade.mjs +601 -838
- package/fesm2022/upgrade.mjs.map +1 -1
- package/package.json +2 -2
- package/types/_common_module-chunk.d.ts +1 -1
- package/types/_module-chunk.d.ts +33 -1
- package/types/_platform_location-chunk.d.ts +1 -1
- package/types/_xhr-chunk.d.ts +1 -1
- package/types/common.d.ts +34 -4
- package/types/http-testing.d.ts +1 -1
- package/types/http.d.ts +13 -2
- package/types/testing.d.ts +88 -62
- package/types/upgrade.d.ts +1 -1
package/fesm2022/http.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @license Angular v21.0.0-
|
|
2
|
+
* @license Angular v21.0.0-rc.0
|
|
3
3
|
* (c) 2010-2025 Google LLC. https://angular.dev/
|
|
4
4
|
* License: MIT
|
|
5
5
|
*/
|
|
@@ -11,178 +11,159 @@ import { of } from 'rxjs';
|
|
|
11
11
|
import { tap } from 'rxjs/operators';
|
|
12
12
|
import './_xhr-chunk.mjs';
|
|
13
13
|
|
|
14
|
-
/**
|
|
15
|
-
* `httpResource` makes a reactive HTTP request and exposes the request status and response value as
|
|
16
|
-
* a `WritableResource`. By default, it assumes that the backend will return JSON data. To make a
|
|
17
|
-
* request that expects a different kind of data, you can use a sub-constructor of `httpResource`,
|
|
18
|
-
* such as `httpResource.text`.
|
|
19
|
-
*
|
|
20
|
-
* @experimental 19.2
|
|
21
|
-
* @initializerApiFunction
|
|
22
|
-
*/
|
|
23
14
|
const httpResource = (() => {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
15
|
+
const jsonFn = makeHttpResourceFn('json');
|
|
16
|
+
jsonFn.arrayBuffer = makeHttpResourceFn('arraybuffer');
|
|
17
|
+
jsonFn.blob = makeHttpResourceFn('blob');
|
|
18
|
+
jsonFn.text = makeHttpResourceFn('text');
|
|
19
|
+
return jsonFn;
|
|
29
20
|
})();
|
|
30
21
|
function makeHttpResourceFn(responseType) {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
22
|
+
return function httpResource(request, options) {
|
|
23
|
+
if (ngDevMode && !options?.injector) {
|
|
24
|
+
assertInInjectionContext(httpResource);
|
|
25
|
+
}
|
|
26
|
+
const injector = options?.injector ?? inject(Injector);
|
|
27
|
+
return new HttpResourceImpl(injector, () => normalizeRequest(request, responseType), options?.defaultValue, options?.parse, options?.equal);
|
|
28
|
+
};
|
|
38
29
|
}
|
|
39
30
|
function normalizeRequest(request, responseType) {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
});
|
|
31
|
+
let unwrappedRequest = typeof request === 'function' ? request() : request;
|
|
32
|
+
if (unwrappedRequest === undefined) {
|
|
33
|
+
return undefined;
|
|
34
|
+
} else if (typeof unwrappedRequest === 'string') {
|
|
35
|
+
unwrappedRequest = {
|
|
36
|
+
url: unwrappedRequest
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
const headers = unwrappedRequest.headers instanceof HttpHeaders ? unwrappedRequest.headers : new HttpHeaders(unwrappedRequest.headers);
|
|
40
|
+
const params = unwrappedRequest.params instanceof HttpParams ? unwrappedRequest.params : new HttpParams({
|
|
41
|
+
fromObject: unwrappedRequest.params
|
|
42
|
+
});
|
|
43
|
+
return new HttpRequest(unwrappedRequest.method ?? 'GET', unwrappedRequest.url, unwrappedRequest.body ?? null, {
|
|
44
|
+
headers,
|
|
45
|
+
params,
|
|
46
|
+
reportProgress: unwrappedRequest.reportProgress,
|
|
47
|
+
withCredentials: unwrappedRequest.withCredentials,
|
|
48
|
+
keepalive: unwrappedRequest.keepalive,
|
|
49
|
+
cache: unwrappedRequest.cache,
|
|
50
|
+
priority: unwrappedRequest.priority,
|
|
51
|
+
mode: unwrappedRequest.mode,
|
|
52
|
+
redirect: unwrappedRequest.redirect,
|
|
53
|
+
responseType,
|
|
54
|
+
context: unwrappedRequest.context,
|
|
55
|
+
transferCache: unwrappedRequest.transferCache,
|
|
56
|
+
credentials: unwrappedRequest.credentials,
|
|
57
|
+
referrer: unwrappedRequest.referrer,
|
|
58
|
+
referrerPolicy: unwrappedRequest.referrerPolicy,
|
|
59
|
+
integrity: unwrappedRequest.integrity,
|
|
60
|
+
timeout: unwrappedRequest.timeout
|
|
61
|
+
});
|
|
72
62
|
}
|
|
73
63
|
class HttpResourceImpl extends _ResourceImpl {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
64
|
+
client;
|
|
65
|
+
_headers = linkedSignal(...(ngDevMode ? [{
|
|
66
|
+
debugName: "_headers",
|
|
67
|
+
source: this.extRequest,
|
|
68
|
+
computation: () => undefined
|
|
69
|
+
}] : [{
|
|
70
|
+
source: this.extRequest,
|
|
71
|
+
computation: () => undefined
|
|
72
|
+
}]));
|
|
73
|
+
_progress = linkedSignal(...(ngDevMode ? [{
|
|
74
|
+
debugName: "_progress",
|
|
75
|
+
source: this.extRequest,
|
|
76
|
+
computation: () => undefined
|
|
77
|
+
}] : [{
|
|
78
|
+
source: this.extRequest,
|
|
79
|
+
computation: () => undefined
|
|
80
|
+
}]));
|
|
81
|
+
_statusCode = linkedSignal(...(ngDevMode ? [{
|
|
82
|
+
debugName: "_statusCode",
|
|
83
|
+
source: this.extRequest,
|
|
84
|
+
computation: () => undefined
|
|
85
|
+
}] : [{
|
|
86
|
+
source: this.extRequest,
|
|
87
|
+
computation: () => undefined
|
|
88
|
+
}]));
|
|
89
|
+
headers = computed(() => this.status() === 'resolved' || this.status() === 'error' ? this._headers() : undefined, ...(ngDevMode ? [{
|
|
90
|
+
debugName: "headers"
|
|
91
|
+
}] : []));
|
|
92
|
+
progress = this._progress.asReadonly();
|
|
93
|
+
statusCode = this._statusCode.asReadonly();
|
|
94
|
+
constructor(injector, request, defaultValue, parse, equal) {
|
|
95
|
+
super(request, ({
|
|
96
|
+
params: request,
|
|
97
|
+
abortSignal
|
|
98
|
+
}) => {
|
|
99
|
+
let sub;
|
|
100
|
+
const onAbort = () => sub.unsubscribe();
|
|
101
|
+
abortSignal.addEventListener('abort', onAbort);
|
|
102
|
+
const stream = signal({
|
|
103
|
+
value: undefined
|
|
104
|
+
}, ...(ngDevMode ? [{
|
|
105
|
+
debugName: "stream"
|
|
106
|
+
}] : []));
|
|
107
|
+
let resolve;
|
|
108
|
+
const promise = new Promise(r => resolve = r);
|
|
109
|
+
const send = value => {
|
|
110
|
+
stream.set(value);
|
|
111
|
+
resolve?.(stream);
|
|
112
|
+
resolve = undefined;
|
|
113
|
+
};
|
|
114
|
+
sub = this.client.request(request).subscribe({
|
|
115
|
+
next: event => {
|
|
116
|
+
switch (event.type) {
|
|
117
|
+
case HttpEventType.Response:
|
|
118
|
+
this._headers.set(event.headers);
|
|
119
|
+
this._statusCode.set(event.status);
|
|
120
|
+
try {
|
|
121
|
+
send({
|
|
122
|
+
value: parse ? parse(event.body) : event.body
|
|
123
|
+
});
|
|
124
|
+
} catch (error) {
|
|
125
|
+
send({
|
|
126
|
+
error: _encapsulateResourceError(error)
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
break;
|
|
130
|
+
case HttpEventType.DownloadProgress:
|
|
131
|
+
this._progress.set(event);
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
134
|
+
},
|
|
135
|
+
error: error => {
|
|
136
|
+
if (error instanceof HttpErrorResponse) {
|
|
137
|
+
this._headers.set(error.headers);
|
|
138
|
+
this._statusCode.set(error.status);
|
|
139
|
+
}
|
|
140
|
+
send({
|
|
141
|
+
error
|
|
142
|
+
});
|
|
143
|
+
abortSignal.removeEventListener('abort', onAbort);
|
|
144
|
+
},
|
|
145
|
+
complete: () => {
|
|
146
|
+
if (resolve) {
|
|
147
|
+
send({
|
|
148
|
+
error: new _RuntimeError(991, ngDevMode && 'Resource completed before producing a value')
|
|
143
149
|
});
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
150
|
+
}
|
|
151
|
+
abortSignal.removeEventListener('abort', onAbort);
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
return promise;
|
|
155
|
+
}, defaultValue, equal, injector);
|
|
156
|
+
this.client = injector.get(HttpClient);
|
|
157
|
+
}
|
|
158
|
+
set(value) {
|
|
159
|
+
super.set(value);
|
|
160
|
+
this._headers.set(undefined);
|
|
161
|
+
this._progress.set(undefined);
|
|
162
|
+
this._statusCode.set(undefined);
|
|
163
|
+
}
|
|
154
164
|
}
|
|
155
165
|
|
|
156
|
-
/**
|
|
157
|
-
* If your application uses different HTTP origins to make API calls (via `HttpClient`) on the server and
|
|
158
|
-
* on the client, the `HTTP_TRANSFER_CACHE_ORIGIN_MAP` token allows you to establish a mapping
|
|
159
|
-
* between those origins, so that `HttpTransferCache` feature can recognize those requests as the same
|
|
160
|
-
* ones and reuse the data cached on the server during hydration on the client.
|
|
161
|
-
*
|
|
162
|
-
* **Important note**: the `HTTP_TRANSFER_CACHE_ORIGIN_MAP` token should *only* be provided in
|
|
163
|
-
* the *server* code of your application (typically in the `app.server.config.ts` script). Angular throws an
|
|
164
|
-
* error if it detects that the token is defined while running on the client.
|
|
165
|
-
*
|
|
166
|
-
* @usageNotes
|
|
167
|
-
*
|
|
168
|
-
* When the same API endpoint is accessed via `http://internal-domain.com:8080` on the server and
|
|
169
|
-
* via `https://external-domain.com` on the client, you can use the following configuration:
|
|
170
|
-
* ```ts
|
|
171
|
-
* // in app.server.config.ts
|
|
172
|
-
* {
|
|
173
|
-
* provide: HTTP_TRANSFER_CACHE_ORIGIN_MAP,
|
|
174
|
-
* useValue: {
|
|
175
|
-
* 'http://internal-domain.com:8080': 'https://external-domain.com'
|
|
176
|
-
* }
|
|
177
|
-
* }
|
|
178
|
-
* ```
|
|
179
|
-
*
|
|
180
|
-
* @publicApi
|
|
181
|
-
*/
|
|
182
166
|
const HTTP_TRANSFER_CACHE_ORIGIN_MAP = new InjectionToken(typeof ngDevMode !== undefined && ngDevMode ? 'HTTP_TRANSFER_CACHE_ORIGIN_MAP' : '');
|
|
183
|
-
/**
|
|
184
|
-
* Keys within cached response data structure.
|
|
185
|
-
*/
|
|
186
167
|
const BODY = 'b';
|
|
187
168
|
const HEADERS = 'h';
|
|
188
169
|
const STATUS = 's';
|
|
@@ -190,237 +171,188 @@ const STATUS_TEXT = 'st';
|
|
|
190
171
|
const REQ_URL = 'u';
|
|
191
172
|
const RESPONSE_TYPE = 'rt';
|
|
192
173
|
const CACHE_OPTIONS = new InjectionToken(typeof ngDevMode !== undefined && ngDevMode ? 'HTTP_TRANSFER_STATE_CACHE_OPTIONS' : '');
|
|
193
|
-
/**
|
|
194
|
-
* A list of allowed HTTP methods to cache.
|
|
195
|
-
*/
|
|
196
174
|
const ALLOWED_METHODS = ['GET', 'HEAD'];
|
|
197
175
|
function transferCacheInterceptorFn(req, next) {
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
const
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
body = new Blob([undecodedBody]);
|
|
241
|
-
break;
|
|
242
|
-
}
|
|
243
|
-
// We want to warn users accessing a header provided from the cache
|
|
244
|
-
// That HttpTransferCache alters the headers
|
|
245
|
-
// The warning will be logged a single time by HttpHeaders instance
|
|
246
|
-
let headers = new HttpHeaders(httpHeaders);
|
|
247
|
-
if (typeof ngDevMode === 'undefined' || ngDevMode) {
|
|
248
|
-
// Append extra logic in dev mode to produce a warning when a header
|
|
249
|
-
// that was not transferred to the client is accessed in the code via `get`
|
|
250
|
-
// and `has` calls.
|
|
251
|
-
headers = appendMissingHeadersDetection(req.url, headers, headersToInclude ?? []);
|
|
252
|
-
}
|
|
253
|
-
return of(new HttpResponse({
|
|
254
|
-
body,
|
|
255
|
-
headers,
|
|
256
|
-
status,
|
|
257
|
-
statusText,
|
|
258
|
-
url,
|
|
259
|
-
}));
|
|
176
|
+
const {
|
|
177
|
+
isCacheActive,
|
|
178
|
+
...globalOptions
|
|
179
|
+
} = inject(CACHE_OPTIONS);
|
|
180
|
+
const {
|
|
181
|
+
transferCache: requestOptions,
|
|
182
|
+
method: requestMethod
|
|
183
|
+
} = req;
|
|
184
|
+
if (!isCacheActive || requestOptions === false || requestMethod === 'POST' && !globalOptions.includePostRequests && !requestOptions || requestMethod !== 'POST' && !ALLOWED_METHODS.includes(requestMethod) || !globalOptions.includeRequestsWithAuthHeaders && hasAuthHeaders(req) || globalOptions.filter?.(req) === false) {
|
|
185
|
+
return next(req);
|
|
186
|
+
}
|
|
187
|
+
const transferState = inject(TransferState);
|
|
188
|
+
const originMap = inject(HTTP_TRANSFER_CACHE_ORIGIN_MAP, {
|
|
189
|
+
optional: true
|
|
190
|
+
});
|
|
191
|
+
if (typeof ngServerMode !== 'undefined' && !ngServerMode && originMap) {
|
|
192
|
+
throw new _RuntimeError(2803, ngDevMode && 'Angular detected that the `HTTP_TRANSFER_CACHE_ORIGIN_MAP` token is configured and ' + 'present in the client side code. Please ensure that this token is only provided in the ' + 'server code of the application.');
|
|
193
|
+
}
|
|
194
|
+
const requestUrl = typeof ngServerMode !== 'undefined' && ngServerMode && originMap ? mapRequestOriginUrl(req.url, originMap) : req.url;
|
|
195
|
+
const storeKey = makeCacheKey(req, requestUrl);
|
|
196
|
+
const response = transferState.get(storeKey, null);
|
|
197
|
+
let headersToInclude = globalOptions.includeHeaders;
|
|
198
|
+
if (typeof requestOptions === 'object' && requestOptions.includeHeaders) {
|
|
199
|
+
headersToInclude = requestOptions.includeHeaders;
|
|
200
|
+
}
|
|
201
|
+
if (response) {
|
|
202
|
+
const {
|
|
203
|
+
[BODY]: undecodedBody,
|
|
204
|
+
[RESPONSE_TYPE]: responseType,
|
|
205
|
+
[HEADERS]: httpHeaders,
|
|
206
|
+
[STATUS]: status,
|
|
207
|
+
[STATUS_TEXT]: statusText,
|
|
208
|
+
[REQ_URL]: url
|
|
209
|
+
} = response;
|
|
210
|
+
let body = undecodedBody;
|
|
211
|
+
switch (responseType) {
|
|
212
|
+
case 'arraybuffer':
|
|
213
|
+
body = new TextEncoder().encode(undecodedBody).buffer;
|
|
214
|
+
break;
|
|
215
|
+
case 'blob':
|
|
216
|
+
body = new Blob([undecodedBody]);
|
|
217
|
+
break;
|
|
260
218
|
}
|
|
261
|
-
|
|
262
|
-
if (typeof
|
|
263
|
-
|
|
264
|
-
return event$.pipe(tap((event) => {
|
|
265
|
-
// Only cache successful HTTP responses.
|
|
266
|
-
if (event instanceof HttpResponse) {
|
|
267
|
-
transferState.set(storeKey, {
|
|
268
|
-
[BODY]: event.body,
|
|
269
|
-
[HEADERS]: getFilteredHeaders(event.headers, headersToInclude),
|
|
270
|
-
[STATUS]: event.status,
|
|
271
|
-
[STATUS_TEXT]: event.statusText,
|
|
272
|
-
[REQ_URL]: requestUrl,
|
|
273
|
-
[RESPONSE_TYPE]: req.responseType,
|
|
274
|
-
});
|
|
275
|
-
}
|
|
276
|
-
}));
|
|
219
|
+
let headers = new HttpHeaders(httpHeaders);
|
|
220
|
+
if (typeof ngDevMode === 'undefined' || ngDevMode) {
|
|
221
|
+
headers = appendMissingHeadersDetection(req.url, headers, headersToInclude ?? []);
|
|
277
222
|
}
|
|
278
|
-
return
|
|
223
|
+
return of(new HttpResponse({
|
|
224
|
+
body,
|
|
225
|
+
headers,
|
|
226
|
+
status,
|
|
227
|
+
statusText,
|
|
228
|
+
url
|
|
229
|
+
}));
|
|
230
|
+
}
|
|
231
|
+
const event$ = next(req);
|
|
232
|
+
if (typeof ngServerMode !== 'undefined' && ngServerMode) {
|
|
233
|
+
return event$.pipe(tap(event => {
|
|
234
|
+
if (event instanceof HttpResponse) {
|
|
235
|
+
transferState.set(storeKey, {
|
|
236
|
+
[BODY]: event.body,
|
|
237
|
+
[HEADERS]: getFilteredHeaders(event.headers, headersToInclude),
|
|
238
|
+
[STATUS]: event.status,
|
|
239
|
+
[STATUS_TEXT]: event.statusText,
|
|
240
|
+
[REQ_URL]: requestUrl,
|
|
241
|
+
[RESPONSE_TYPE]: req.responseType
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
}));
|
|
245
|
+
}
|
|
246
|
+
return event$;
|
|
279
247
|
}
|
|
280
|
-
/** @returns true when the requests contains autorization related headers. */
|
|
281
248
|
function hasAuthHeaders(req) {
|
|
282
|
-
|
|
249
|
+
return req.headers.has('authorization') || req.headers.has('proxy-authorization');
|
|
283
250
|
}
|
|
284
251
|
function getFilteredHeaders(headers, includeHeaders) {
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
}
|
|
252
|
+
if (!includeHeaders) {
|
|
253
|
+
return {};
|
|
254
|
+
}
|
|
255
|
+
const headersMap = {};
|
|
256
|
+
for (const key of includeHeaders) {
|
|
257
|
+
const values = headers.getAll(key);
|
|
258
|
+
if (values !== null) {
|
|
259
|
+
headersMap[key] = values;
|
|
294
260
|
}
|
|
295
|
-
|
|
261
|
+
}
|
|
262
|
+
return headersMap;
|
|
296
263
|
}
|
|
297
264
|
function sortAndConcatParams(params) {
|
|
298
|
-
|
|
299
|
-
.sort()
|
|
300
|
-
.map((k) => `${k}=${params.getAll(k)}`)
|
|
301
|
-
.join('&');
|
|
265
|
+
return [...params.keys()].sort().map(k => `${k}=${params.getAll(k)}`).join('&');
|
|
302
266
|
}
|
|
303
267
|
function makeCacheKey(request, mappedRequestUrl) {
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
268
|
+
const {
|
|
269
|
+
params,
|
|
270
|
+
method,
|
|
271
|
+
responseType
|
|
272
|
+
} = request;
|
|
273
|
+
const encodedParams = sortAndConcatParams(params);
|
|
274
|
+
let serializedBody = request.serializeBody();
|
|
275
|
+
if (serializedBody instanceof URLSearchParams) {
|
|
276
|
+
serializedBody = sortAndConcatParams(serializedBody);
|
|
277
|
+
} else if (typeof serializedBody !== 'string') {
|
|
278
|
+
serializedBody = '';
|
|
279
|
+
}
|
|
280
|
+
const key = [method, responseType, mappedRequestUrl, serializedBody, encodedParams].join('|');
|
|
281
|
+
const hash = generateHash(key);
|
|
282
|
+
return makeStateKey(hash);
|
|
317
283
|
}
|
|
318
|
-
/**
|
|
319
|
-
* A method that returns a hash representation of a string using a variant of DJB2 hash
|
|
320
|
-
* algorithm.
|
|
321
|
-
*
|
|
322
|
-
* This is the same hashing logic that is used to generate component ids.
|
|
323
|
-
*/
|
|
324
284
|
function generateHash(value) {
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
hash += 2147483647 + 1;
|
|
332
|
-
return hash.toString();
|
|
285
|
+
let hash = 0;
|
|
286
|
+
for (const char of value) {
|
|
287
|
+
hash = Math.imul(31, hash) + char.charCodeAt(0) << 0;
|
|
288
|
+
}
|
|
289
|
+
hash += 2147483647 + 1;
|
|
290
|
+
return hash.toString();
|
|
333
291
|
}
|
|
334
|
-
/**
|
|
335
|
-
* Returns the DI providers needed to enable HTTP transfer cache.
|
|
336
|
-
*
|
|
337
|
-
* By default, when using server rendering, requests are performed twice: once on the server and
|
|
338
|
-
* other one on the browser.
|
|
339
|
-
*
|
|
340
|
-
* When these providers are added, requests performed on the server are cached and reused during the
|
|
341
|
-
* bootstrapping of the application in the browser thus avoiding duplicate requests and reducing
|
|
342
|
-
* load time.
|
|
343
|
-
*
|
|
344
|
-
*/
|
|
345
292
|
function withHttpTransferCache(cacheOptions) {
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
];
|
|
293
|
+
return [{
|
|
294
|
+
provide: CACHE_OPTIONS,
|
|
295
|
+
useFactory: () => {
|
|
296
|
+
_performanceMarkFeature('NgHttpTransferCache');
|
|
297
|
+
return {
|
|
298
|
+
isCacheActive: true,
|
|
299
|
+
...cacheOptions
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
}, {
|
|
303
|
+
provide: HTTP_ROOT_INTERCEPTOR_FNS,
|
|
304
|
+
useValue: transferCacheInterceptorFn,
|
|
305
|
+
multi: true
|
|
306
|
+
}, {
|
|
307
|
+
provide: APP_BOOTSTRAP_LISTENER,
|
|
308
|
+
multi: true,
|
|
309
|
+
useFactory: () => {
|
|
310
|
+
const appRef = inject(ApplicationRef);
|
|
311
|
+
const cacheState = inject(CACHE_OPTIONS);
|
|
312
|
+
return () => {
|
|
313
|
+
appRef.whenStable().then(() => {
|
|
314
|
+
cacheState.isCacheActive = false;
|
|
315
|
+
});
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
}];
|
|
373
319
|
}
|
|
374
|
-
/**
|
|
375
|
-
* This function will add a proxy to an HttpHeader to intercept calls to get/has
|
|
376
|
-
* and log a warning if the header entry requested has been removed
|
|
377
|
-
*/
|
|
378
320
|
function appendMissingHeadersDetection(url, headers, headersToInclude) {
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
`level by adding the \`httpCacheTransfer.includeHeaders\` argument to the ` +
|
|
399
|
-
`\`provideClientHydration()\` call. `));
|
|
400
|
-
}
|
|
401
|
-
// invoking the original method
|
|
402
|
-
return value.apply(target, [headerName]);
|
|
403
|
-
};
|
|
404
|
-
},
|
|
405
|
-
});
|
|
321
|
+
const warningProduced = new Set();
|
|
322
|
+
return new Proxy(headers, {
|
|
323
|
+
get(target, prop) {
|
|
324
|
+
const value = Reflect.get(target, prop);
|
|
325
|
+
const methods = new Set(['get', 'has', 'getAll']);
|
|
326
|
+
if (typeof value !== 'function' || !methods.has(prop)) {
|
|
327
|
+
return value;
|
|
328
|
+
}
|
|
329
|
+
return headerName => {
|
|
330
|
+
const key = (prop + ':' + headerName).toLowerCase();
|
|
331
|
+
if (!headersToInclude.includes(headerName) && !warningProduced.has(key)) {
|
|
332
|
+
warningProduced.add(key);
|
|
333
|
+
const truncatedUrl = _truncateMiddle(url);
|
|
334
|
+
console.warn(_formatRuntimeError(-2802, `Angular detected that the \`${headerName}\` header is accessed, but the value of the header ` + `was not transferred from the server to the client by the HttpTransferCache. ` + `To include the value of the \`${headerName}\` header for the \`${truncatedUrl}\` request, ` + `use the \`includeHeaders\` list. The \`includeHeaders\` can be defined either ` + `on a request level by adding the \`transferCache\` parameter, or on an application ` + `level by adding the \`httpCacheTransfer.includeHeaders\` argument to the ` + `\`provideClientHydration()\` call. `));
|
|
335
|
+
}
|
|
336
|
+
return value.apply(target, [headerName]);
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
});
|
|
406
340
|
}
|
|
407
341
|
function mapRequestOriginUrl(url, originMap) {
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
342
|
+
const origin = new URL(url, 'resolve://').origin;
|
|
343
|
+
const mappedOrigin = originMap[origin];
|
|
344
|
+
if (!mappedOrigin) {
|
|
345
|
+
return url;
|
|
346
|
+
}
|
|
347
|
+
if (typeof ngDevMode === 'undefined' || ngDevMode) {
|
|
348
|
+
verifyMappedOrigin(mappedOrigin);
|
|
349
|
+
}
|
|
350
|
+
return url.replace(origin, mappedOrigin);
|
|
417
351
|
}
|
|
418
352
|
function verifyMappedOrigin(url) {
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
'without any other segments.');
|
|
423
|
-
}
|
|
353
|
+
if (new URL(url, 'resolve://').pathname !== '/') {
|
|
354
|
+
throw new _RuntimeError(2804, 'Angular detected a URL with a path segment in the value provided for the ' + `\`HTTP_TRANSFER_CACHE_ORIGIN_MAP\` token: ${url}. The map should only contain origins ` + 'without any other segments.');
|
|
355
|
+
}
|
|
424
356
|
}
|
|
425
357
|
|
|
426
358
|
export { HTTP_TRANSFER_CACHE_ORIGIN_MAP, HttpClient, HttpErrorResponse, HttpEventType, HttpHeaders, HttpParams, HttpRequest, HttpResponse, httpResource, HTTP_ROOT_INTERCEPTOR_FNS as ɵHTTP_ROOT_INTERCEPTOR_FNS, withHttpTransferCache as ɵwithHttpTransferCache };
|