http.mjs 137 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288
  1. /**
  2. * @license Angular v19.2.4
  3. * (c) 2010-2025 Google LLC. https://angular.io/
  4. * License: MIT
  5. */
  6. import * as i0 from '@angular/core';
  7. import { ɵRuntimeError as _RuntimeError, Injectable, inject, NgZone, InjectionToken, ɵPendingTasksInternal as _PendingTasksInternal, PLATFORM_ID, ɵConsole as _Console, ɵformatRuntimeError as _formatRuntimeError, runInInjectionContext, Inject, makeEnvironmentProviders, NgModule, assertInInjectionContext, Injector, ɵResourceImpl as _ResourceImpl, linkedSignal, computed, ResourceStatus, signal, APP_BOOTSTRAP_LISTENER, ɵperformanceMarkFeature as _performanceMarkFeature, ApplicationRef, TransferState, makeStateKey, ɵtruncateMiddle as _truncateMiddle } from '@angular/core';
  8. import { of, Observable, from } from 'rxjs';
  9. import { concatMap, filter, map, finalize, switchMap, tap } from 'rxjs/operators';
  10. import * as i1 from '@angular/common';
  11. import { isPlatformServer, DOCUMENT, ɵparseCookieValue as _parseCookieValue } from '@angular/common';
  12. /**
  13. * Transforms an `HttpRequest` into a stream of `HttpEvent`s, one of which will likely be a
  14. * `HttpResponse`.
  15. *
  16. * `HttpHandler` is injectable. When injected, the handler instance dispatches requests to the
  17. * first interceptor in the chain, which dispatches to the second, etc, eventually reaching the
  18. * `HttpBackend`.
  19. *
  20. * In an `HttpInterceptor`, the `HttpHandler` parameter is the next interceptor in the chain.
  21. *
  22. * @publicApi
  23. */
  24. class HttpHandler {
  25. }
  26. /**
  27. * A final `HttpHandler` which will dispatch the request via browser HTTP APIs to a backend.
  28. *
  29. * Interceptors sit between the `HttpClient` interface and the `HttpBackend`.
  30. *
  31. * When injected, `HttpBackend` dispatches requests directly to the backend, without going
  32. * through the interceptor chain.
  33. *
  34. * @publicApi
  35. */
  36. class HttpBackend {
  37. }
  38. /**
  39. * Represents the header configuration options for an HTTP request.
  40. * Instances are immutable. Modifying methods return a cloned
  41. * instance with the change. The original object is never changed.
  42. *
  43. * @publicApi
  44. */
  45. class HttpHeaders {
  46. /**
  47. * Internal map of lowercase header names to values.
  48. */
  49. // TODO(issue/24571): remove '!'.
  50. headers;
  51. /**
  52. * Internal map of lowercased header names to the normalized
  53. * form of the name (the form seen first).
  54. */
  55. normalizedNames = new Map();
  56. /**
  57. * Complete the lazy initialization of this object (needed before reading).
  58. */
  59. lazyInit;
  60. /**
  61. * Queued updates to be materialized the next initialization.
  62. */
  63. lazyUpdate = null;
  64. /** Constructs a new HTTP header object with the given values.*/
  65. constructor(headers) {
  66. if (!headers) {
  67. this.headers = new Map();
  68. }
  69. else if (typeof headers === 'string') {
  70. this.lazyInit = () => {
  71. this.headers = new Map();
  72. headers.split('\n').forEach((line) => {
  73. const index = line.indexOf(':');
  74. if (index > 0) {
  75. const name = line.slice(0, index);
  76. const value = line.slice(index + 1).trim();
  77. this.addHeaderEntry(name, value);
  78. }
  79. });
  80. };
  81. }
  82. else if (typeof Headers !== 'undefined' && headers instanceof Headers) {
  83. this.headers = new Map();
  84. headers.forEach((value, name) => {
  85. this.addHeaderEntry(name, value);
  86. });
  87. }
  88. else {
  89. this.lazyInit = () => {
  90. if (typeof ngDevMode === 'undefined' || ngDevMode) {
  91. assertValidHeaders(headers);
  92. }
  93. this.headers = new Map();
  94. Object.entries(headers).forEach(([name, values]) => {
  95. this.setHeaderEntries(name, values);
  96. });
  97. };
  98. }
  99. }
  100. /**
  101. * Checks for existence of a given header.
  102. *
  103. * @param name The header name to check for existence.
  104. *
  105. * @returns True if the header exists, false otherwise.
  106. */
  107. has(name) {
  108. this.init();
  109. return this.headers.has(name.toLowerCase());
  110. }
  111. /**
  112. * Retrieves the first value of a given header.
  113. *
  114. * @param name The header name.
  115. *
  116. * @returns The value string if the header exists, null otherwise
  117. */
  118. get(name) {
  119. this.init();
  120. const values = this.headers.get(name.toLowerCase());
  121. return values && values.length > 0 ? values[0] : null;
  122. }
  123. /**
  124. * Retrieves the names of the headers.
  125. *
  126. * @returns A list of header names.
  127. */
  128. keys() {
  129. this.init();
  130. return Array.from(this.normalizedNames.values());
  131. }
  132. /**
  133. * Retrieves a list of values for a given header.
  134. *
  135. * @param name The header name from which to retrieve values.
  136. *
  137. * @returns A string of values if the header exists, null otherwise.
  138. */
  139. getAll(name) {
  140. this.init();
  141. return this.headers.get(name.toLowerCase()) || null;
  142. }
  143. /**
  144. * Appends a new value to the existing set of values for a header
  145. * and returns them in a clone of the original instance.
  146. *
  147. * @param name The header name for which to append the values.
  148. * @param value The value to append.
  149. *
  150. * @returns A clone of the HTTP headers object with the value appended to the given header.
  151. */
  152. append(name, value) {
  153. return this.clone({ name, value, op: 'a' });
  154. }
  155. /**
  156. * Sets or modifies a value for a given header in a clone of the original instance.
  157. * If the header already exists, its value is replaced with the given value
  158. * in the returned object.
  159. *
  160. * @param name The header name.
  161. * @param value The value or values to set or override for the given header.
  162. *
  163. * @returns A clone of the HTTP headers object with the newly set header value.
  164. */
  165. set(name, value) {
  166. return this.clone({ name, value, op: 's' });
  167. }
  168. /**
  169. * Deletes values for a given header in a clone of the original instance.
  170. *
  171. * @param name The header name.
  172. * @param value The value or values to delete for the given header.
  173. *
  174. * @returns A clone of the HTTP headers object with the given value deleted.
  175. */
  176. delete(name, value) {
  177. return this.clone({ name, value, op: 'd' });
  178. }
  179. maybeSetNormalizedName(name, lcName) {
  180. if (!this.normalizedNames.has(lcName)) {
  181. this.normalizedNames.set(lcName, name);
  182. }
  183. }
  184. init() {
  185. if (!!this.lazyInit) {
  186. if (this.lazyInit instanceof HttpHeaders) {
  187. this.copyFrom(this.lazyInit);
  188. }
  189. else {
  190. this.lazyInit();
  191. }
  192. this.lazyInit = null;
  193. if (!!this.lazyUpdate) {
  194. this.lazyUpdate.forEach((update) => this.applyUpdate(update));
  195. this.lazyUpdate = null;
  196. }
  197. }
  198. }
  199. copyFrom(other) {
  200. other.init();
  201. Array.from(other.headers.keys()).forEach((key) => {
  202. this.headers.set(key, other.headers.get(key));
  203. this.normalizedNames.set(key, other.normalizedNames.get(key));
  204. });
  205. }
  206. clone(update) {
  207. const clone = new HttpHeaders();
  208. clone.lazyInit = !!this.lazyInit && this.lazyInit instanceof HttpHeaders ? this.lazyInit : this;
  209. clone.lazyUpdate = (this.lazyUpdate || []).concat([update]);
  210. return clone;
  211. }
  212. applyUpdate(update) {
  213. const key = update.name.toLowerCase();
  214. switch (update.op) {
  215. case 'a':
  216. case 's':
  217. let value = update.value;
  218. if (typeof value === 'string') {
  219. value = [value];
  220. }
  221. if (value.length === 0) {
  222. return;
  223. }
  224. this.maybeSetNormalizedName(update.name, key);
  225. const base = (update.op === 'a' ? this.headers.get(key) : undefined) || [];
  226. base.push(...value);
  227. this.headers.set(key, base);
  228. break;
  229. case 'd':
  230. const toDelete = update.value;
  231. if (!toDelete) {
  232. this.headers.delete(key);
  233. this.normalizedNames.delete(key);
  234. }
  235. else {
  236. let existing = this.headers.get(key);
  237. if (!existing) {
  238. return;
  239. }
  240. existing = existing.filter((value) => toDelete.indexOf(value) === -1);
  241. if (existing.length === 0) {
  242. this.headers.delete(key);
  243. this.normalizedNames.delete(key);
  244. }
  245. else {
  246. this.headers.set(key, existing);
  247. }
  248. }
  249. break;
  250. }
  251. }
  252. addHeaderEntry(name, value) {
  253. const key = name.toLowerCase();
  254. this.maybeSetNormalizedName(name, key);
  255. if (this.headers.has(key)) {
  256. this.headers.get(key).push(value);
  257. }
  258. else {
  259. this.headers.set(key, [value]);
  260. }
  261. }
  262. setHeaderEntries(name, values) {
  263. const headerValues = (Array.isArray(values) ? values : [values]).map((value) => value.toString());
  264. const key = name.toLowerCase();
  265. this.headers.set(key, headerValues);
  266. this.maybeSetNormalizedName(name, key);
  267. }
  268. /**
  269. * @internal
  270. */
  271. forEach(fn) {
  272. this.init();
  273. Array.from(this.normalizedNames.keys()).forEach((key) => fn(this.normalizedNames.get(key), this.headers.get(key)));
  274. }
  275. }
  276. /**
  277. * Verifies that the headers object has the right shape: the values
  278. * must be either strings, numbers or arrays. Throws an error if an invalid
  279. * header value is present.
  280. */
  281. function assertValidHeaders(headers) {
  282. for (const [key, value] of Object.entries(headers)) {
  283. if (!(typeof value === 'string' || typeof value === 'number') && !Array.isArray(value)) {
  284. throw new Error(`Unexpected value of the \`${key}\` header provided. ` +
  285. `Expecting either a string, a number or an array, but got: \`${value}\`.`);
  286. }
  287. }
  288. }
  289. /**
  290. * Provides encoding and decoding of URL parameter and query-string values.
  291. *
  292. * Serializes and parses URL parameter keys and values to encode and decode them.
  293. * If you pass URL query parameters without encoding,
  294. * the query parameters can be misinterpreted at the receiving end.
  295. *
  296. *
  297. * @publicApi
  298. */
  299. class HttpUrlEncodingCodec {
  300. /**
  301. * Encodes a key name for a URL parameter or query-string.
  302. * @param key The key name.
  303. * @returns The encoded key name.
  304. */
  305. encodeKey(key) {
  306. return standardEncoding(key);
  307. }
  308. /**
  309. * Encodes the value of a URL parameter or query-string.
  310. * @param value The value.
  311. * @returns The encoded value.
  312. */
  313. encodeValue(value) {
  314. return standardEncoding(value);
  315. }
  316. /**
  317. * Decodes an encoded URL parameter or query-string key.
  318. * @param key The encoded key name.
  319. * @returns The decoded key name.
  320. */
  321. decodeKey(key) {
  322. return decodeURIComponent(key);
  323. }
  324. /**
  325. * Decodes an encoded URL parameter or query-string value.
  326. * @param value The encoded value.
  327. * @returns The decoded value.
  328. */
  329. decodeValue(value) {
  330. return decodeURIComponent(value);
  331. }
  332. }
  333. function paramParser(rawParams, codec) {
  334. const map = new Map();
  335. if (rawParams.length > 0) {
  336. // The `window.location.search` can be used while creating an instance of the `HttpParams` class
  337. // (e.g. `new HttpParams({ fromString: window.location.search })`). The `window.location.search`
  338. // may start with the `?` char, so we strip it if it's present.
  339. const params = rawParams.replace(/^\?/, '').split('&');
  340. params.forEach((param) => {
  341. const eqIdx = param.indexOf('=');
  342. const [key, val] = eqIdx == -1
  343. ? [codec.decodeKey(param), '']
  344. : [codec.decodeKey(param.slice(0, eqIdx)), codec.decodeValue(param.slice(eqIdx + 1))];
  345. const list = map.get(key) || [];
  346. list.push(val);
  347. map.set(key, list);
  348. });
  349. }
  350. return map;
  351. }
  352. /**
  353. * Encode input string with standard encodeURIComponent and then un-encode specific characters.
  354. */
  355. const STANDARD_ENCODING_REGEX = /%(\d[a-f0-9])/gi;
  356. const STANDARD_ENCODING_REPLACEMENTS = {
  357. '40': '@',
  358. '3A': ':',
  359. '24': '$',
  360. '2C': ',',
  361. '3B': ';',
  362. '3D': '=',
  363. '3F': '?',
  364. '2F': '/',
  365. };
  366. function standardEncoding(v) {
  367. return encodeURIComponent(v).replace(STANDARD_ENCODING_REGEX, (s, t) => STANDARD_ENCODING_REPLACEMENTS[t] ?? s);
  368. }
  369. function valueToString(value) {
  370. return `${value}`;
  371. }
  372. /**
  373. * An HTTP request/response body that represents serialized parameters,
  374. * per the MIME type `application/x-www-form-urlencoded`.
  375. *
  376. * This class is immutable; all mutation operations return a new instance.
  377. *
  378. * @publicApi
  379. */
  380. class HttpParams {
  381. map;
  382. encoder;
  383. updates = null;
  384. cloneFrom = null;
  385. constructor(options = {}) {
  386. this.encoder = options.encoder || new HttpUrlEncodingCodec();
  387. if (options.fromString) {
  388. if (options.fromObject) {
  389. throw new _RuntimeError(2805 /* RuntimeErrorCode.CANNOT_SPECIFY_BOTH_FROM_STRING_AND_FROM_OBJECT */, ngDevMode && 'Cannot specify both fromString and fromObject.');
  390. }
  391. this.map = paramParser(options.fromString, this.encoder);
  392. }
  393. else if (!!options.fromObject) {
  394. this.map = new Map();
  395. Object.keys(options.fromObject).forEach((key) => {
  396. const value = options.fromObject[key];
  397. // convert the values to strings
  398. const values = Array.isArray(value) ? value.map(valueToString) : [valueToString(value)];
  399. this.map.set(key, values);
  400. });
  401. }
  402. else {
  403. this.map = null;
  404. }
  405. }
  406. /**
  407. * Reports whether the body includes one or more values for a given parameter.
  408. * @param param The parameter name.
  409. * @returns True if the parameter has one or more values,
  410. * false if it has no value or is not present.
  411. */
  412. has(param) {
  413. this.init();
  414. return this.map.has(param);
  415. }
  416. /**
  417. * Retrieves the first value for a parameter.
  418. * @param param The parameter name.
  419. * @returns The first value of the given parameter,
  420. * or `null` if the parameter is not present.
  421. */
  422. get(param) {
  423. this.init();
  424. const res = this.map.get(param);
  425. return !!res ? res[0] : null;
  426. }
  427. /**
  428. * Retrieves all values for a parameter.
  429. * @param param The parameter name.
  430. * @returns All values in a string array,
  431. * or `null` if the parameter not present.
  432. */
  433. getAll(param) {
  434. this.init();
  435. return this.map.get(param) || null;
  436. }
  437. /**
  438. * Retrieves all the parameters for this body.
  439. * @returns The parameter names in a string array.
  440. */
  441. keys() {
  442. this.init();
  443. return Array.from(this.map.keys());
  444. }
  445. /**
  446. * Appends a new value to existing values for a parameter.
  447. * @param param The parameter name.
  448. * @param value The new value to add.
  449. * @return A new body with the appended value.
  450. */
  451. append(param, value) {
  452. return this.clone({ param, value, op: 'a' });
  453. }
  454. /**
  455. * Constructs a new body with appended values for the given parameter name.
  456. * @param params parameters and values
  457. * @return A new body with the new value.
  458. */
  459. appendAll(params) {
  460. const updates = [];
  461. Object.keys(params).forEach((param) => {
  462. const value = params[param];
  463. if (Array.isArray(value)) {
  464. value.forEach((_value) => {
  465. updates.push({ param, value: _value, op: 'a' });
  466. });
  467. }
  468. else {
  469. updates.push({ param, value: value, op: 'a' });
  470. }
  471. });
  472. return this.clone(updates);
  473. }
  474. /**
  475. * Replaces the value for a parameter.
  476. * @param param The parameter name.
  477. * @param value The new value.
  478. * @return A new body with the new value.
  479. */
  480. set(param, value) {
  481. return this.clone({ param, value, op: 's' });
  482. }
  483. /**
  484. * Removes a given value or all values from a parameter.
  485. * @param param The parameter name.
  486. * @param value The value to remove, if provided.
  487. * @return A new body with the given value removed, or with all values
  488. * removed if no value is specified.
  489. */
  490. delete(param, value) {
  491. return this.clone({ param, value, op: 'd' });
  492. }
  493. /**
  494. * Serializes the body to an encoded string, where key-value pairs (separated by `=`) are
  495. * separated by `&`s.
  496. */
  497. toString() {
  498. this.init();
  499. return (this.keys()
  500. .map((key) => {
  501. const eKey = this.encoder.encodeKey(key);
  502. // `a: ['1']` produces `'a=1'`
  503. // `b: []` produces `''`
  504. // `c: ['1', '2']` produces `'c=1&c=2'`
  505. return this.map.get(key)
  506. .map((value) => eKey + '=' + this.encoder.encodeValue(value))
  507. .join('&');
  508. })
  509. // filter out empty values because `b: []` produces `''`
  510. // which results in `a=1&&c=1&c=2` instead of `a=1&c=1&c=2` if we don't
  511. .filter((param) => param !== '')
  512. .join('&'));
  513. }
  514. clone(update) {
  515. const clone = new HttpParams({ encoder: this.encoder });
  516. clone.cloneFrom = this.cloneFrom || this;
  517. clone.updates = (this.updates || []).concat(update);
  518. return clone;
  519. }
  520. init() {
  521. if (this.map === null) {
  522. this.map = new Map();
  523. }
  524. if (this.cloneFrom !== null) {
  525. this.cloneFrom.init();
  526. this.cloneFrom.keys().forEach((key) => this.map.set(key, this.cloneFrom.map.get(key)));
  527. this.updates.forEach((update) => {
  528. switch (update.op) {
  529. case 'a':
  530. case 's':
  531. const base = (update.op === 'a' ? this.map.get(update.param) : undefined) || [];
  532. base.push(valueToString(update.value));
  533. this.map.set(update.param, base);
  534. break;
  535. case 'd':
  536. if (update.value !== undefined) {
  537. let base = this.map.get(update.param) || [];
  538. const idx = base.indexOf(valueToString(update.value));
  539. if (idx !== -1) {
  540. base.splice(idx, 1);
  541. }
  542. if (base.length > 0) {
  543. this.map.set(update.param, base);
  544. }
  545. else {
  546. this.map.delete(update.param);
  547. }
  548. }
  549. else {
  550. this.map.delete(update.param);
  551. break;
  552. }
  553. }
  554. });
  555. this.cloneFrom = this.updates = null;
  556. }
  557. }
  558. }
  559. /**
  560. * A token used to manipulate and access values stored in `HttpContext`.
  561. *
  562. * @publicApi
  563. */
  564. class HttpContextToken {
  565. defaultValue;
  566. constructor(defaultValue) {
  567. this.defaultValue = defaultValue;
  568. }
  569. }
  570. /**
  571. * Http context stores arbitrary user defined values and ensures type safety without
  572. * actually knowing the types. It is backed by a `Map` and guarantees that keys do not clash.
  573. *
  574. * This context is mutable and is shared between cloned requests unless explicitly specified.
  575. *
  576. * @usageNotes
  577. *
  578. * ### Usage Example
  579. *
  580. * ```ts
  581. * // inside cache.interceptors.ts
  582. * export const IS_CACHE_ENABLED = new HttpContextToken<boolean>(() => false);
  583. *
  584. * export class CacheInterceptor implements HttpInterceptor {
  585. *
  586. * intercept(req: HttpRequest<any>, delegate: HttpHandler): Observable<HttpEvent<any>> {
  587. * if (req.context.get(IS_CACHE_ENABLED) === true) {
  588. * return ...;
  589. * }
  590. * return delegate.handle(req);
  591. * }
  592. * }
  593. *
  594. * // inside a service
  595. *
  596. * this.httpClient.get('/api/weather', {
  597. * context: new HttpContext().set(IS_CACHE_ENABLED, true)
  598. * }).subscribe(...);
  599. * ```
  600. *
  601. * @publicApi
  602. */
  603. class HttpContext {
  604. map = new Map();
  605. /**
  606. * Store a value in the context. If a value is already present it will be overwritten.
  607. *
  608. * @param token The reference to an instance of `HttpContextToken`.
  609. * @param value The value to store.
  610. *
  611. * @returns A reference to itself for easy chaining.
  612. */
  613. set(token, value) {
  614. this.map.set(token, value);
  615. return this;
  616. }
  617. /**
  618. * Retrieve the value associated with the given token.
  619. *
  620. * @param token The reference to an instance of `HttpContextToken`.
  621. *
  622. * @returns The stored value or default if one is defined.
  623. */
  624. get(token) {
  625. if (!this.map.has(token)) {
  626. this.map.set(token, token.defaultValue());
  627. }
  628. return this.map.get(token);
  629. }
  630. /**
  631. * Delete the value associated with the given token.
  632. *
  633. * @param token The reference to an instance of `HttpContextToken`.
  634. *
  635. * @returns A reference to itself for easy chaining.
  636. */
  637. delete(token) {
  638. this.map.delete(token);
  639. return this;
  640. }
  641. /**
  642. * Checks for existence of a given token.
  643. *
  644. * @param token The reference to an instance of `HttpContextToken`.
  645. *
  646. * @returns True if the token exists, false otherwise.
  647. */
  648. has(token) {
  649. return this.map.has(token);
  650. }
  651. /**
  652. * @returns a list of tokens currently stored in the context.
  653. */
  654. keys() {
  655. return this.map.keys();
  656. }
  657. }
  658. /**
  659. * Determine whether the given HTTP method may include a body.
  660. */
  661. function mightHaveBody(method) {
  662. switch (method) {
  663. case 'DELETE':
  664. case 'GET':
  665. case 'HEAD':
  666. case 'OPTIONS':
  667. case 'JSONP':
  668. return false;
  669. default:
  670. return true;
  671. }
  672. }
  673. /**
  674. * Safely assert whether the given value is an ArrayBuffer.
  675. *
  676. * In some execution environments ArrayBuffer is not defined.
  677. */
  678. function isArrayBuffer(value) {
  679. return typeof ArrayBuffer !== 'undefined' && value instanceof ArrayBuffer;
  680. }
  681. /**
  682. * Safely assert whether the given value is a Blob.
  683. *
  684. * In some execution environments Blob is not defined.
  685. */
  686. function isBlob(value) {
  687. return typeof Blob !== 'undefined' && value instanceof Blob;
  688. }
  689. /**
  690. * Safely assert whether the given value is a FormData instance.
  691. *
  692. * In some execution environments FormData is not defined.
  693. */
  694. function isFormData(value) {
  695. return typeof FormData !== 'undefined' && value instanceof FormData;
  696. }
  697. /**
  698. * Safely assert whether the given value is a URLSearchParams instance.
  699. *
  700. * In some execution environments URLSearchParams is not defined.
  701. */
  702. function isUrlSearchParams(value) {
  703. return typeof URLSearchParams !== 'undefined' && value instanceof URLSearchParams;
  704. }
  705. /**
  706. * `Content-Type` is an HTTP header used to indicate the media type
  707. * (also known as MIME type) of the resource being sent to the client
  708. * or received from the server.
  709. */
  710. const CONTENT_TYPE_HEADER = 'Content-Type';
  711. /**
  712. * The `Accept` header is an HTTP request header that indicates the media types
  713. * (or content types) the client is willing to receive from the server.
  714. */
  715. const ACCEPT_HEADER = 'Accept';
  716. /**
  717. * `X-Request-URL` is a custom HTTP header used in older browser versions,
  718. * including Firefox (< 32), Chrome (< 37), Safari (< 8), and Internet Explorer,
  719. * to include the full URL of the request in cross-origin requests.
  720. */
  721. const X_REQUEST_URL_HEADER = 'X-Request-URL';
  722. /**
  723. * `text/plain` is a content type used to indicate that the content being
  724. * sent is plain text with no special formatting or structured data
  725. * like HTML, XML, or JSON.
  726. */
  727. const TEXT_CONTENT_TYPE = 'text/plain';
  728. /**
  729. * `application/json` is a content type used to indicate that the content
  730. * being sent is in the JSON format.
  731. */
  732. const JSON_CONTENT_TYPE = 'application/json';
  733. /**
  734. * `application/json, text/plain, *\/*` is a content negotiation string often seen in the
  735. * Accept header of HTTP requests. It indicates the types of content the client is willing
  736. * to accept from the server, with a preference for `application/json` and `text/plain`,
  737. * but also accepting any other type (*\/*).
  738. */
  739. const ACCEPT_HEADER_VALUE = `${JSON_CONTENT_TYPE}, ${TEXT_CONTENT_TYPE}, */*`;
  740. /**
  741. * An outgoing HTTP request with an optional typed body.
  742. *
  743. * `HttpRequest` represents an outgoing request, including URL, method,
  744. * headers, body, and other request configuration options. Instances should be
  745. * assumed to be immutable. To modify a `HttpRequest`, the `clone`
  746. * method should be used.
  747. *
  748. * @publicApi
  749. */
  750. class HttpRequest {
  751. url;
  752. /**
  753. * The request body, or `null` if one isn't set.
  754. *
  755. * Bodies are not enforced to be immutable, as they can include a reference to any
  756. * user-defined data type. However, interceptors should take care to preserve
  757. * idempotence by treating them as such.
  758. */
  759. body = null;
  760. /**
  761. * Outgoing headers for this request.
  762. */
  763. // TODO(issue/24571): remove '!'.
  764. headers;
  765. /**
  766. * Shared and mutable context that can be used by interceptors
  767. */
  768. context;
  769. /**
  770. * Whether this request should be made in a way that exposes progress events.
  771. *
  772. * Progress events are expensive (change detection runs on each event) and so
  773. * they should only be requested if the consumer intends to monitor them.
  774. *
  775. * Note: The `FetchBackend` doesn't support progress report on uploads.
  776. */
  777. reportProgress = false;
  778. /**
  779. * Whether this request should be sent with outgoing credentials (cookies).
  780. */
  781. withCredentials = false;
  782. /**
  783. * The expected response type of the server.
  784. *
  785. * This is used to parse the response appropriately before returning it to
  786. * the requestee.
  787. */
  788. responseType = 'json';
  789. /**
  790. * The outgoing HTTP request method.
  791. */
  792. method;
  793. /**
  794. * Outgoing URL parameters.
  795. *
  796. * To pass a string representation of HTTP parameters in the URL-query-string format,
  797. * the `HttpParamsOptions`' `fromString` may be used. For example:
  798. *
  799. * ```ts
  800. * new HttpParams({fromString: 'angular=awesome'})
  801. * ```
  802. */
  803. // TODO(issue/24571): remove '!'.
  804. params;
  805. /**
  806. * The outgoing URL with all URL parameters set.
  807. */
  808. urlWithParams;
  809. /**
  810. * The HttpTransferCache option for the request
  811. */
  812. transferCache;
  813. constructor(method, url, third, fourth) {
  814. this.url = url;
  815. this.method = method.toUpperCase();
  816. // Next, need to figure out which argument holds the HttpRequestInit
  817. // options, if any.
  818. let options;
  819. // Check whether a body argument is expected. The only valid way to omit
  820. // the body argument is to use a known no-body method like GET.
  821. if (mightHaveBody(this.method) || !!fourth) {
  822. // Body is the third argument, options are the fourth.
  823. this.body = third !== undefined ? third : null;
  824. options = fourth;
  825. }
  826. else {
  827. // No body required, options are the third argument. The body stays null.
  828. options = third;
  829. }
  830. // If options have been passed, interpret them.
  831. if (options) {
  832. // Normalize reportProgress and withCredentials.
  833. this.reportProgress = !!options.reportProgress;
  834. this.withCredentials = !!options.withCredentials;
  835. // Override default response type of 'json' if one is provided.
  836. if (!!options.responseType) {
  837. this.responseType = options.responseType;
  838. }
  839. // Override headers if they're provided.
  840. if (!!options.headers) {
  841. this.headers = options.headers;
  842. }
  843. if (!!options.context) {
  844. this.context = options.context;
  845. }
  846. if (!!options.params) {
  847. this.params = options.params;
  848. }
  849. // We do want to assign transferCache even if it's falsy (false is valid value)
  850. this.transferCache = options.transferCache;
  851. }
  852. // If no headers have been passed in, construct a new HttpHeaders instance.
  853. this.headers ??= new HttpHeaders();
  854. // If no context have been passed in, construct a new HttpContext instance.
  855. this.context ??= new HttpContext();
  856. // If no parameters have been passed in, construct a new HttpUrlEncodedParams instance.
  857. if (!this.params) {
  858. this.params = new HttpParams();
  859. this.urlWithParams = url;
  860. }
  861. else {
  862. // Encode the parameters to a string in preparation for inclusion in the URL.
  863. const params = this.params.toString();
  864. if (params.length === 0) {
  865. // No parameters, the visible URL is just the URL given at creation time.
  866. this.urlWithParams = url;
  867. }
  868. else {
  869. // Does the URL already have query parameters? Look for '?'.
  870. const qIdx = url.indexOf('?');
  871. // There are 3 cases to handle:
  872. // 1) No existing parameters -> append '?' followed by params.
  873. // 2) '?' exists and is followed by existing query string ->
  874. // append '&' followed by params.
  875. // 3) '?' exists at the end of the url -> append params directly.
  876. // This basically amounts to determining the character, if any, with
  877. // which to join the URL and parameters.
  878. const sep = qIdx === -1 ? '?' : qIdx < url.length - 1 ? '&' : '';
  879. this.urlWithParams = url + sep + params;
  880. }
  881. }
  882. }
  883. /**
  884. * Transform the free-form body into a serialized format suitable for
  885. * transmission to the server.
  886. */
  887. serializeBody() {
  888. // If no body is present, no need to serialize it.
  889. if (this.body === null) {
  890. return null;
  891. }
  892. // Check whether the body is already in a serialized form. If so,
  893. // it can just be returned directly.
  894. if (typeof this.body === 'string' ||
  895. isArrayBuffer(this.body) ||
  896. isBlob(this.body) ||
  897. isFormData(this.body) ||
  898. isUrlSearchParams(this.body)) {
  899. return this.body;
  900. }
  901. // Check whether the body is an instance of HttpUrlEncodedParams.
  902. if (this.body instanceof HttpParams) {
  903. return this.body.toString();
  904. }
  905. // Check whether the body is an object or array, and serialize with JSON if so.
  906. if (typeof this.body === 'object' ||
  907. typeof this.body === 'boolean' ||
  908. Array.isArray(this.body)) {
  909. return JSON.stringify(this.body);
  910. }
  911. // Fall back on toString() for everything else.
  912. return this.body.toString();
  913. }
  914. /**
  915. * Examine the body and attempt to infer an appropriate MIME type
  916. * for it.
  917. *
  918. * If no such type can be inferred, this method will return `null`.
  919. */
  920. detectContentTypeHeader() {
  921. // An empty body has no content type.
  922. if (this.body === null) {
  923. return null;
  924. }
  925. // FormData bodies rely on the browser's content type assignment.
  926. if (isFormData(this.body)) {
  927. return null;
  928. }
  929. // Blobs usually have their own content type. If it doesn't, then
  930. // no type can be inferred.
  931. if (isBlob(this.body)) {
  932. return this.body.type || null;
  933. }
  934. // Array buffers have unknown contents and thus no type can be inferred.
  935. if (isArrayBuffer(this.body)) {
  936. return null;
  937. }
  938. // Technically, strings could be a form of JSON data, but it's safe enough
  939. // to assume they're plain strings.
  940. if (typeof this.body === 'string') {
  941. return TEXT_CONTENT_TYPE;
  942. }
  943. // `HttpUrlEncodedParams` has its own content-type.
  944. if (this.body instanceof HttpParams) {
  945. return 'application/x-www-form-urlencoded;charset=UTF-8';
  946. }
  947. // Arrays, objects, boolean and numbers will be encoded as JSON.
  948. if (typeof this.body === 'object' ||
  949. typeof this.body === 'number' ||
  950. typeof this.body === 'boolean') {
  951. return JSON_CONTENT_TYPE;
  952. }
  953. // No type could be inferred.
  954. return null;
  955. }
  956. clone(update = {}) {
  957. // For method, url, and responseType, take the current value unless
  958. // it is overridden in the update hash.
  959. const method = update.method || this.method;
  960. const url = update.url || this.url;
  961. const responseType = update.responseType || this.responseType;
  962. // Carefully handle the transferCache to differentiate between
  963. // `false` and `undefined` in the update args.
  964. const transferCache = update.transferCache ?? this.transferCache;
  965. // The body is somewhat special - a `null` value in update.body means
  966. // whatever current body is present is being overridden with an empty
  967. // body, whereas an `undefined` value in update.body implies no
  968. // override.
  969. const body = update.body !== undefined ? update.body : this.body;
  970. // Carefully handle the boolean options to differentiate between
  971. // `false` and `undefined` in the update args.
  972. const withCredentials = update.withCredentials ?? this.withCredentials;
  973. const reportProgress = update.reportProgress ?? this.reportProgress;
  974. // Headers and params may be appended to if `setHeaders` or
  975. // `setParams` are used.
  976. let headers = update.headers || this.headers;
  977. let params = update.params || this.params;
  978. // Pass on context if needed
  979. const context = update.context ?? this.context;
  980. // Check whether the caller has asked to add headers.
  981. if (update.setHeaders !== undefined) {
  982. // Set every requested header.
  983. headers = Object.keys(update.setHeaders).reduce((headers, name) => headers.set(name, update.setHeaders[name]), headers);
  984. }
  985. // Check whether the caller has asked to set params.
  986. if (update.setParams) {
  987. // Set every requested param.
  988. params = Object.keys(update.setParams).reduce((params, param) => params.set(param, update.setParams[param]), params);
  989. }
  990. // Finally, construct the new HttpRequest using the pieces from above.
  991. return new HttpRequest(method, url, body, {
  992. params,
  993. headers,
  994. context,
  995. reportProgress,
  996. responseType,
  997. withCredentials,
  998. transferCache,
  999. });
  1000. }
  1001. }
  1002. /**
  1003. * Type enumeration for the different kinds of `HttpEvent`.
  1004. *
  1005. * @publicApi
  1006. */
  1007. var HttpEventType;
  1008. (function (HttpEventType) {
  1009. /**
  1010. * The request was sent out over the wire.
  1011. */
  1012. HttpEventType[HttpEventType["Sent"] = 0] = "Sent";
  1013. /**
  1014. * An upload progress event was received.
  1015. *
  1016. * Note: The `FetchBackend` doesn't support progress report on uploads.
  1017. */
  1018. HttpEventType[HttpEventType["UploadProgress"] = 1] = "UploadProgress";
  1019. /**
  1020. * The response status code and headers were received.
  1021. */
  1022. HttpEventType[HttpEventType["ResponseHeader"] = 2] = "ResponseHeader";
  1023. /**
  1024. * A download progress event was received.
  1025. */
  1026. HttpEventType[HttpEventType["DownloadProgress"] = 3] = "DownloadProgress";
  1027. /**
  1028. * The full response including the body was received.
  1029. */
  1030. HttpEventType[HttpEventType["Response"] = 4] = "Response";
  1031. /**
  1032. * A custom event from an interceptor or a backend.
  1033. */
  1034. HttpEventType[HttpEventType["User"] = 5] = "User";
  1035. })(HttpEventType || (HttpEventType = {}));
  1036. /**
  1037. * Base class for both `HttpResponse` and `HttpHeaderResponse`.
  1038. *
  1039. * @publicApi
  1040. */
  1041. class HttpResponseBase {
  1042. /**
  1043. * All response headers.
  1044. */
  1045. headers;
  1046. /**
  1047. * Response status code.
  1048. */
  1049. status;
  1050. /**
  1051. * Textual description of response status code, defaults to OK.
  1052. *
  1053. * Do not depend on this.
  1054. */
  1055. statusText;
  1056. /**
  1057. * URL of the resource retrieved, or null if not available.
  1058. */
  1059. url;
  1060. /**
  1061. * Whether the status code falls in the 2xx range.
  1062. */
  1063. ok;
  1064. /**
  1065. * Type of the response, narrowed to either the full response or the header.
  1066. */
  1067. // TODO(issue/24571): remove '!'.
  1068. type;
  1069. /**
  1070. * Super-constructor for all responses.
  1071. *
  1072. * The single parameter accepted is an initialization hash. Any properties
  1073. * of the response passed there will override the default values.
  1074. */
  1075. constructor(init, defaultStatus = 200, defaultStatusText = 'OK') {
  1076. // If the hash has values passed, use them to initialize the response.
  1077. // Otherwise use the default values.
  1078. this.headers = init.headers || new HttpHeaders();
  1079. this.status = init.status !== undefined ? init.status : defaultStatus;
  1080. this.statusText = init.statusText || defaultStatusText;
  1081. this.url = init.url || null;
  1082. // Cache the ok value to avoid defining a getter.
  1083. this.ok = this.status >= 200 && this.status < 300;
  1084. }
  1085. }
  1086. /**
  1087. * A partial HTTP response which only includes the status and header data,
  1088. * but no response body.
  1089. *
  1090. * `HttpHeaderResponse` is a `HttpEvent` available on the response
  1091. * event stream, only when progress events are requested.
  1092. *
  1093. * @publicApi
  1094. */
  1095. class HttpHeaderResponse extends HttpResponseBase {
  1096. /**
  1097. * Create a new `HttpHeaderResponse` with the given parameters.
  1098. */
  1099. constructor(init = {}) {
  1100. super(init);
  1101. }
  1102. type = HttpEventType.ResponseHeader;
  1103. /**
  1104. * Copy this `HttpHeaderResponse`, overriding its contents with the
  1105. * given parameter hash.
  1106. */
  1107. clone(update = {}) {
  1108. // Perform a straightforward initialization of the new HttpHeaderResponse,
  1109. // overriding the current parameters with new ones if given.
  1110. return new HttpHeaderResponse({
  1111. headers: update.headers || this.headers,
  1112. status: update.status !== undefined ? update.status : this.status,
  1113. statusText: update.statusText || this.statusText,
  1114. url: update.url || this.url || undefined,
  1115. });
  1116. }
  1117. }
  1118. /**
  1119. * A full HTTP response, including a typed response body (which may be `null`
  1120. * if one was not returned).
  1121. *
  1122. * `HttpResponse` is a `HttpEvent` available on the response event
  1123. * stream.
  1124. *
  1125. * @publicApi
  1126. */
  1127. class HttpResponse extends HttpResponseBase {
  1128. /**
  1129. * The response body, or `null` if one was not returned.
  1130. */
  1131. body;
  1132. /**
  1133. * Construct a new `HttpResponse`.
  1134. */
  1135. constructor(init = {}) {
  1136. super(init);
  1137. this.body = init.body !== undefined ? init.body : null;
  1138. }
  1139. type = HttpEventType.Response;
  1140. clone(update = {}) {
  1141. return new HttpResponse({
  1142. body: update.body !== undefined ? update.body : this.body,
  1143. headers: update.headers || this.headers,
  1144. status: update.status !== undefined ? update.status : this.status,
  1145. statusText: update.statusText || this.statusText,
  1146. url: update.url || this.url || undefined,
  1147. });
  1148. }
  1149. }
  1150. /**
  1151. * A response that represents an error or failure, either from a
  1152. * non-successful HTTP status, an error while executing the request,
  1153. * or some other failure which occurred during the parsing of the response.
  1154. *
  1155. * Any error returned on the `Observable` response stream will be
  1156. * wrapped in an `HttpErrorResponse` to provide additional context about
  1157. * the state of the HTTP layer when the error occurred. The error property
  1158. * will contain either a wrapped Error object or the error response returned
  1159. * from the server.
  1160. *
  1161. * @publicApi
  1162. */
  1163. class HttpErrorResponse extends HttpResponseBase {
  1164. name = 'HttpErrorResponse';
  1165. message;
  1166. error;
  1167. /**
  1168. * Errors are never okay, even when the status code is in the 2xx success range.
  1169. */
  1170. ok = false;
  1171. constructor(init) {
  1172. // Initialize with a default status of 0 / Unknown Error.
  1173. super(init, 0, 'Unknown Error');
  1174. // If the response was successful, then this was a parse error. Otherwise, it was
  1175. // a protocol-level failure of some sort. Either the request failed in transit
  1176. // or the server returned an unsuccessful status code.
  1177. if (this.status >= 200 && this.status < 300) {
  1178. this.message = `Http failure during parsing for ${init.url || '(unknown url)'}`;
  1179. }
  1180. else {
  1181. this.message = `Http failure response for ${init.url || '(unknown url)'}: ${init.status} ${init.statusText}`;
  1182. }
  1183. this.error = init.error || null;
  1184. }
  1185. }
  1186. /**
  1187. * We use these constant to prevent pulling the whole HttpStatusCode enum
  1188. * Those are the only ones referenced directly by the framework
  1189. */
  1190. const HTTP_STATUS_CODE_OK = 200;
  1191. const HTTP_STATUS_CODE_NO_CONTENT = 204;
  1192. /**
  1193. * Http status codes.
  1194. * As per https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
  1195. * @publicApi
  1196. */
  1197. var HttpStatusCode;
  1198. (function (HttpStatusCode) {
  1199. HttpStatusCode[HttpStatusCode["Continue"] = 100] = "Continue";
  1200. HttpStatusCode[HttpStatusCode["SwitchingProtocols"] = 101] = "SwitchingProtocols";
  1201. HttpStatusCode[HttpStatusCode["Processing"] = 102] = "Processing";
  1202. HttpStatusCode[HttpStatusCode["EarlyHints"] = 103] = "EarlyHints";
  1203. HttpStatusCode[HttpStatusCode["Ok"] = 200] = "Ok";
  1204. HttpStatusCode[HttpStatusCode["Created"] = 201] = "Created";
  1205. HttpStatusCode[HttpStatusCode["Accepted"] = 202] = "Accepted";
  1206. HttpStatusCode[HttpStatusCode["NonAuthoritativeInformation"] = 203] = "NonAuthoritativeInformation";
  1207. HttpStatusCode[HttpStatusCode["NoContent"] = 204] = "NoContent";
  1208. HttpStatusCode[HttpStatusCode["ResetContent"] = 205] = "ResetContent";
  1209. HttpStatusCode[HttpStatusCode["PartialContent"] = 206] = "PartialContent";
  1210. HttpStatusCode[HttpStatusCode["MultiStatus"] = 207] = "MultiStatus";
  1211. HttpStatusCode[HttpStatusCode["AlreadyReported"] = 208] = "AlreadyReported";
  1212. HttpStatusCode[HttpStatusCode["ImUsed"] = 226] = "ImUsed";
  1213. HttpStatusCode[HttpStatusCode["MultipleChoices"] = 300] = "MultipleChoices";
  1214. HttpStatusCode[HttpStatusCode["MovedPermanently"] = 301] = "MovedPermanently";
  1215. HttpStatusCode[HttpStatusCode["Found"] = 302] = "Found";
  1216. HttpStatusCode[HttpStatusCode["SeeOther"] = 303] = "SeeOther";
  1217. HttpStatusCode[HttpStatusCode["NotModified"] = 304] = "NotModified";
  1218. HttpStatusCode[HttpStatusCode["UseProxy"] = 305] = "UseProxy";
  1219. HttpStatusCode[HttpStatusCode["Unused"] = 306] = "Unused";
  1220. HttpStatusCode[HttpStatusCode["TemporaryRedirect"] = 307] = "TemporaryRedirect";
  1221. HttpStatusCode[HttpStatusCode["PermanentRedirect"] = 308] = "PermanentRedirect";
  1222. HttpStatusCode[HttpStatusCode["BadRequest"] = 400] = "BadRequest";
  1223. HttpStatusCode[HttpStatusCode["Unauthorized"] = 401] = "Unauthorized";
  1224. HttpStatusCode[HttpStatusCode["PaymentRequired"] = 402] = "PaymentRequired";
  1225. HttpStatusCode[HttpStatusCode["Forbidden"] = 403] = "Forbidden";
  1226. HttpStatusCode[HttpStatusCode["NotFound"] = 404] = "NotFound";
  1227. HttpStatusCode[HttpStatusCode["MethodNotAllowed"] = 405] = "MethodNotAllowed";
  1228. HttpStatusCode[HttpStatusCode["NotAcceptable"] = 406] = "NotAcceptable";
  1229. HttpStatusCode[HttpStatusCode["ProxyAuthenticationRequired"] = 407] = "ProxyAuthenticationRequired";
  1230. HttpStatusCode[HttpStatusCode["RequestTimeout"] = 408] = "RequestTimeout";
  1231. HttpStatusCode[HttpStatusCode["Conflict"] = 409] = "Conflict";
  1232. HttpStatusCode[HttpStatusCode["Gone"] = 410] = "Gone";
  1233. HttpStatusCode[HttpStatusCode["LengthRequired"] = 411] = "LengthRequired";
  1234. HttpStatusCode[HttpStatusCode["PreconditionFailed"] = 412] = "PreconditionFailed";
  1235. HttpStatusCode[HttpStatusCode["PayloadTooLarge"] = 413] = "PayloadTooLarge";
  1236. HttpStatusCode[HttpStatusCode["UriTooLong"] = 414] = "UriTooLong";
  1237. HttpStatusCode[HttpStatusCode["UnsupportedMediaType"] = 415] = "UnsupportedMediaType";
  1238. HttpStatusCode[HttpStatusCode["RangeNotSatisfiable"] = 416] = "RangeNotSatisfiable";
  1239. HttpStatusCode[HttpStatusCode["ExpectationFailed"] = 417] = "ExpectationFailed";
  1240. HttpStatusCode[HttpStatusCode["ImATeapot"] = 418] = "ImATeapot";
  1241. HttpStatusCode[HttpStatusCode["MisdirectedRequest"] = 421] = "MisdirectedRequest";
  1242. HttpStatusCode[HttpStatusCode["UnprocessableEntity"] = 422] = "UnprocessableEntity";
  1243. HttpStatusCode[HttpStatusCode["Locked"] = 423] = "Locked";
  1244. HttpStatusCode[HttpStatusCode["FailedDependency"] = 424] = "FailedDependency";
  1245. HttpStatusCode[HttpStatusCode["TooEarly"] = 425] = "TooEarly";
  1246. HttpStatusCode[HttpStatusCode["UpgradeRequired"] = 426] = "UpgradeRequired";
  1247. HttpStatusCode[HttpStatusCode["PreconditionRequired"] = 428] = "PreconditionRequired";
  1248. HttpStatusCode[HttpStatusCode["TooManyRequests"] = 429] = "TooManyRequests";
  1249. HttpStatusCode[HttpStatusCode["RequestHeaderFieldsTooLarge"] = 431] = "RequestHeaderFieldsTooLarge";
  1250. HttpStatusCode[HttpStatusCode["UnavailableForLegalReasons"] = 451] = "UnavailableForLegalReasons";
  1251. HttpStatusCode[HttpStatusCode["InternalServerError"] = 500] = "InternalServerError";
  1252. HttpStatusCode[HttpStatusCode["NotImplemented"] = 501] = "NotImplemented";
  1253. HttpStatusCode[HttpStatusCode["BadGateway"] = 502] = "BadGateway";
  1254. HttpStatusCode[HttpStatusCode["ServiceUnavailable"] = 503] = "ServiceUnavailable";
  1255. HttpStatusCode[HttpStatusCode["GatewayTimeout"] = 504] = "GatewayTimeout";
  1256. HttpStatusCode[HttpStatusCode["HttpVersionNotSupported"] = 505] = "HttpVersionNotSupported";
  1257. HttpStatusCode[HttpStatusCode["VariantAlsoNegotiates"] = 506] = "VariantAlsoNegotiates";
  1258. HttpStatusCode[HttpStatusCode["InsufficientStorage"] = 507] = "InsufficientStorage";
  1259. HttpStatusCode[HttpStatusCode["LoopDetected"] = 508] = "LoopDetected";
  1260. HttpStatusCode[HttpStatusCode["NotExtended"] = 510] = "NotExtended";
  1261. HttpStatusCode[HttpStatusCode["NetworkAuthenticationRequired"] = 511] = "NetworkAuthenticationRequired";
  1262. })(HttpStatusCode || (HttpStatusCode = {}));
  1263. /**
  1264. * Constructs an instance of `HttpRequestOptions<T>` from a source `HttpMethodOptions` and
  1265. * the given `body`. This function clones the object and adds the body.
  1266. *
  1267. * Note that the `responseType` *options* value is a String that identifies the
  1268. * single data type of the response.
  1269. * A single overload version of the method handles each response type.
  1270. * The value of `responseType` cannot be a union, as the combined signature could imply.
  1271. *
  1272. */
  1273. function addBody(options, body) {
  1274. return {
  1275. body,
  1276. headers: options.headers,
  1277. context: options.context,
  1278. observe: options.observe,
  1279. params: options.params,
  1280. reportProgress: options.reportProgress,
  1281. responseType: options.responseType,
  1282. withCredentials: options.withCredentials,
  1283. transferCache: options.transferCache,
  1284. };
  1285. }
  1286. /**
  1287. * Performs HTTP requests.
  1288. * This service is available as an injectable class, with methods to perform HTTP requests.
  1289. * Each request method has multiple signatures, and the return type varies based on
  1290. * the signature that is called (mainly the values of `observe` and `responseType`).
  1291. *
  1292. * Note that the `responseType` *options* value is a String that identifies the
  1293. * single data type of the response.
  1294. * A single overload version of the method handles each response type.
  1295. * The value of `responseType` cannot be a union, as the combined signature could imply.
  1296. *
  1297. * @usageNotes
  1298. *
  1299. * ### HTTP Request Example
  1300. *
  1301. * ```ts
  1302. * // GET heroes whose name contains search term
  1303. * searchHeroes(term: string): observable<Hero[]>{
  1304. *
  1305. * const params = new HttpParams({fromString: 'name=term'});
  1306. * return this.httpClient.request('GET', this.heroesUrl, {responseType:'json', params});
  1307. * }
  1308. * ```
  1309. *
  1310. * Alternatively, the parameter string can be used without invoking HttpParams
  1311. * by directly joining to the URL.
  1312. * ```ts
  1313. * this.httpClient.request('GET', this.heroesUrl + '?' + 'name=term', {responseType:'json'});
  1314. * ```
  1315. *
  1316. *
  1317. * ### JSONP Example
  1318. * ```ts
  1319. * requestJsonp(url, callback = 'callback') {
  1320. * return this.httpClient.jsonp(this.heroesURL, callback);
  1321. * }
  1322. * ```
  1323. *
  1324. * ### PATCH Example
  1325. * ```ts
  1326. * // PATCH one of the heroes' name
  1327. * patchHero (id: number, heroName: string): Observable<{}> {
  1328. * const url = `${this.heroesUrl}/${id}`; // PATCH api/heroes/42
  1329. * return this.httpClient.patch(url, {name: heroName}, httpOptions)
  1330. * .pipe(catchError(this.handleError('patchHero')));
  1331. * }
  1332. * ```
  1333. *
  1334. * @see [HTTP Guide](guide/http)
  1335. * @see [HTTP Request](api/common/http/HttpRequest)
  1336. *
  1337. * @publicApi
  1338. */
  1339. class HttpClient {
  1340. handler;
  1341. constructor(handler) {
  1342. this.handler = handler;
  1343. }
  1344. /**
  1345. * Constructs an observable for a generic HTTP request that, when subscribed,
  1346. * fires the request through the chain of registered interceptors and on to the
  1347. * server.
  1348. *
  1349. * You can pass an `HttpRequest` directly as the only parameter. In this case,
  1350. * the call returns an observable of the raw `HttpEvent` stream.
  1351. *
  1352. * Alternatively you can pass an HTTP method as the first parameter,
  1353. * a URL string as the second, and an options hash containing the request body as the third.
  1354. * See `addBody()`. In this case, the specified `responseType` and `observe` options determine the
  1355. * type of returned observable.
  1356. * * The `responseType` value determines how a successful response body is parsed.
  1357. * * If `responseType` is the default `json`, you can pass a type interface for the resulting
  1358. * object as a type parameter to the call.
  1359. *
  1360. * The `observe` value determines the return type, according to what you are interested in
  1361. * observing.
  1362. * * An `observe` value of events returns an observable of the raw `HttpEvent` stream, including
  1363. * progress events by default.
  1364. * * An `observe` value of response returns an observable of `HttpResponse<T>`,
  1365. * where the `T` parameter depends on the `responseType` and any optionally provided type
  1366. * parameter.
  1367. * * An `observe` value of body returns an observable of `<T>` with the same `T` body type.
  1368. *
  1369. */
  1370. request(first, url, options = {}) {
  1371. let req;
  1372. // First, check whether the primary argument is an instance of `HttpRequest`.
  1373. if (first instanceof HttpRequest) {
  1374. // It is. The other arguments must be undefined (per the signatures) and can be
  1375. // ignored.
  1376. req = first;
  1377. }
  1378. else {
  1379. // It's a string, so it represents a URL. Construct a request based on it,
  1380. // and incorporate the remaining arguments (assuming `GET` unless a method is
  1381. // provided.
  1382. // Figure out the headers.
  1383. let headers = undefined;
  1384. if (options.headers instanceof HttpHeaders) {
  1385. headers = options.headers;
  1386. }
  1387. else {
  1388. headers = new HttpHeaders(options.headers);
  1389. }
  1390. // Sort out parameters.
  1391. let params = undefined;
  1392. if (!!options.params) {
  1393. if (options.params instanceof HttpParams) {
  1394. params = options.params;
  1395. }
  1396. else {
  1397. params = new HttpParams({ fromObject: options.params });
  1398. }
  1399. }
  1400. // Construct the request.
  1401. req = new HttpRequest(first, url, options.body !== undefined ? options.body : null, {
  1402. headers,
  1403. context: options.context,
  1404. params,
  1405. reportProgress: options.reportProgress,
  1406. // By default, JSON is assumed to be returned for all calls.
  1407. responseType: options.responseType || 'json',
  1408. withCredentials: options.withCredentials,
  1409. transferCache: options.transferCache,
  1410. });
  1411. }
  1412. // Start with an Observable.of() the initial request, and run the handler (which
  1413. // includes all interceptors) inside a concatMap(). This way, the handler runs
  1414. // inside an Observable chain, which causes interceptors to be re-run on every
  1415. // subscription (this also makes retries re-run the handler, including interceptors).
  1416. const events$ = of(req).pipe(concatMap((req) => this.handler.handle(req)));
  1417. // If coming via the API signature which accepts a previously constructed HttpRequest,
  1418. // the only option is to get the event stream. Otherwise, return the event stream if
  1419. // that is what was requested.
  1420. if (first instanceof HttpRequest || options.observe === 'events') {
  1421. return events$;
  1422. }
  1423. // The requested stream contains either the full response or the body. In either
  1424. // case, the first step is to filter the event stream to extract a stream of
  1425. // responses(s).
  1426. const res$ = (events$.pipe(filter((event) => event instanceof HttpResponse)));
  1427. // Decide which stream to return.
  1428. switch (options.observe || 'body') {
  1429. case 'body':
  1430. // The requested stream is the body. Map the response stream to the response
  1431. // body. This could be done more simply, but a misbehaving interceptor might
  1432. // transform the response body into a different format and ignore the requested
  1433. // responseType. Guard against this by validating that the response is of the
  1434. // requested type.
  1435. switch (req.responseType) {
  1436. case 'arraybuffer':
  1437. return res$.pipe(map((res) => {
  1438. // Validate that the body is an ArrayBuffer.
  1439. if (res.body !== null && !(res.body instanceof ArrayBuffer)) {
  1440. throw new _RuntimeError(2806 /* RuntimeErrorCode.RESPONSE_IS_NOT_AN_ARRAY_BUFFER */, ngDevMode && 'Response is not an ArrayBuffer.');
  1441. }
  1442. return res.body;
  1443. }));
  1444. case 'blob':
  1445. return res$.pipe(map((res) => {
  1446. // Validate that the body is a Blob.
  1447. if (res.body !== null && !(res.body instanceof Blob)) {
  1448. throw new _RuntimeError(2807 /* RuntimeErrorCode.RESPONSE_IS_NOT_A_BLOB */, ngDevMode && 'Response is not a Blob.');
  1449. }
  1450. return res.body;
  1451. }));
  1452. case 'text':
  1453. return res$.pipe(map((res) => {
  1454. // Validate that the body is a string.
  1455. if (res.body !== null && typeof res.body !== 'string') {
  1456. throw new _RuntimeError(2808 /* RuntimeErrorCode.RESPONSE_IS_NOT_A_STRING */, ngDevMode && 'Response is not a string.');
  1457. }
  1458. return res.body;
  1459. }));
  1460. case 'json':
  1461. default:
  1462. // No validation needed for JSON responses, as they can be of any type.
  1463. return res$.pipe(map((res) => res.body));
  1464. }
  1465. case 'response':
  1466. // The response stream was requested directly, so return it.
  1467. return res$;
  1468. default:
  1469. // Guard against new future observe types being added.
  1470. throw new _RuntimeError(2809 /* RuntimeErrorCode.UNHANDLED_OBSERVE_TYPE */, ngDevMode && `Unreachable: unhandled observe type ${options.observe}}`);
  1471. }
  1472. }
  1473. /**
  1474. * Constructs an observable that, when subscribed, causes the configured
  1475. * `DELETE` request to execute on the server. See the individual overloads for
  1476. * details on the return type.
  1477. *
  1478. * @param url The endpoint URL.
  1479. * @param options The HTTP options to send with the request.
  1480. *
  1481. */
  1482. delete(url, options = {}) {
  1483. return this.request('DELETE', url, options);
  1484. }
  1485. /**
  1486. * Constructs an observable that, when subscribed, causes the configured
  1487. * `GET` request to execute on the server. See the individual overloads for
  1488. * details on the return type.
  1489. */
  1490. get(url, options = {}) {
  1491. return this.request('GET', url, options);
  1492. }
  1493. /**
  1494. * Constructs an observable that, when subscribed, causes the configured
  1495. * `HEAD` request to execute on the server. The `HEAD` method returns
  1496. * meta information about the resource without transferring the
  1497. * resource itself. See the individual overloads for
  1498. * details on the return type.
  1499. */
  1500. head(url, options = {}) {
  1501. return this.request('HEAD', url, options);
  1502. }
  1503. /**
  1504. * Constructs an `Observable` that, when subscribed, causes a request with the special method
  1505. * `JSONP` to be dispatched via the interceptor pipeline.
  1506. * The [JSONP pattern](https://en.wikipedia.org/wiki/JSONP) works around limitations of certain
  1507. * API endpoints that don't support newer,
  1508. * and preferable [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) protocol.
  1509. * JSONP treats the endpoint API as a JavaScript file and tricks the browser to process the
  1510. * requests even if the API endpoint is not located on the same domain (origin) as the client-side
  1511. * application making the request.
  1512. * The endpoint API must support JSONP callback for JSONP requests to work.
  1513. * The resource API returns the JSON response wrapped in a callback function.
  1514. * You can pass the callback function name as one of the query parameters.
  1515. * Note that JSONP requests can only be used with `GET` requests.
  1516. *
  1517. * @param url The resource URL.
  1518. * @param callbackParam The callback function name.
  1519. *
  1520. */
  1521. jsonp(url, callbackParam) {
  1522. return this.request('JSONP', url, {
  1523. params: new HttpParams().append(callbackParam, 'JSONP_CALLBACK'),
  1524. observe: 'body',
  1525. responseType: 'json',
  1526. });
  1527. }
  1528. /**
  1529. * Constructs an `Observable` that, when subscribed, causes the configured
  1530. * `OPTIONS` request to execute on the server. This method allows the client
  1531. * to determine the supported HTTP methods and other capabilities of an endpoint,
  1532. * without implying a resource action. See the individual overloads for
  1533. * details on the return type.
  1534. */
  1535. options(url, options = {}) {
  1536. return this.request('OPTIONS', url, options);
  1537. }
  1538. /**
  1539. * Constructs an observable that, when subscribed, causes the configured
  1540. * `PATCH` request to execute on the server. See the individual overloads for
  1541. * details on the return type.
  1542. */
  1543. patch(url, body, options = {}) {
  1544. return this.request('PATCH', url, addBody(options, body));
  1545. }
  1546. /**
  1547. * Constructs an observable that, when subscribed, causes the configured
  1548. * `POST` request to execute on the server. The server responds with the location of
  1549. * the replaced resource. See the individual overloads for
  1550. * details on the return type.
  1551. */
  1552. post(url, body, options = {}) {
  1553. return this.request('POST', url, addBody(options, body));
  1554. }
  1555. /**
  1556. * Constructs an observable that, when subscribed, causes the configured
  1557. * `PUT` request to execute on the server. The `PUT` method replaces an existing resource
  1558. * with a new set of values.
  1559. * See the individual overloads for details on the return type.
  1560. */
  1561. put(url, body, options = {}) {
  1562. return this.request('PUT', url, addBody(options, body));
  1563. }
  1564. static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: HttpClient, deps: [{ token: HttpHandler }], target: i0.ɵɵFactoryTarget.Injectable });
  1565. static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: HttpClient });
  1566. }
  1567. i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: HttpClient, decorators: [{
  1568. type: Injectable
  1569. }], ctorParameters: () => [{ type: HttpHandler }] });
  1570. const XSSI_PREFIX$1 = /^\)\]\}',?\n/;
  1571. /**
  1572. * Determine an appropriate URL for the response, by checking either
  1573. * response url or the X-Request-URL header.
  1574. */
  1575. function getResponseUrl$1(response) {
  1576. if (response.url) {
  1577. return response.url;
  1578. }
  1579. // stored as lowercase in the map
  1580. const xRequestUrl = X_REQUEST_URL_HEADER.toLocaleLowerCase();
  1581. return response.headers.get(xRequestUrl);
  1582. }
  1583. /**
  1584. * An internal injection token to reference `FetchBackend` implementation
  1585. * in a tree-shakable way.
  1586. */
  1587. const FETCH_BACKEND = new InjectionToken(typeof ngDevMode === 'undefined' || ngDevMode ? 'FETCH_BACKEND' : '');
  1588. /**
  1589. * Uses `fetch` to send requests to a backend server.
  1590. *
  1591. * This `FetchBackend` requires the support of the
  1592. * [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) which is available on all
  1593. * supported browsers and on Node.js v18 or later.
  1594. *
  1595. * @see {@link HttpHandler}
  1596. *
  1597. * @publicApi
  1598. */
  1599. class FetchBackend {
  1600. // We use an arrow function to always reference the current global implementation of `fetch`.
  1601. // This is helpful for cases when the global `fetch` implementation is modified by external code,
  1602. // see https://github.com/angular/angular/issues/57527.
  1603. fetchImpl = inject(FetchFactory, { optional: true })?.fetch ?? ((...args) => globalThis.fetch(...args));
  1604. ngZone = inject(NgZone);
  1605. handle(request) {
  1606. return new Observable((observer) => {
  1607. const aborter = new AbortController();
  1608. this.doRequest(request, aborter.signal, observer).then(noop, (error) => observer.error(new HttpErrorResponse({ error })));
  1609. return () => aborter.abort();
  1610. });
  1611. }
  1612. async doRequest(request, signal, observer) {
  1613. const init = this.createRequestInit(request);
  1614. let response;
  1615. try {
  1616. // Run fetch outside of Angular zone.
  1617. // This is due to Node.js fetch implementation (Undici) which uses a number of setTimeouts to check if
  1618. // the response should eventually timeout which causes extra CD cycles every 500ms
  1619. const fetchPromise = this.ngZone.runOutsideAngular(() => this.fetchImpl(request.urlWithParams, { signal, ...init }));
  1620. // Make sure Zone.js doesn't trigger false-positive unhandled promise
  1621. // error in case the Promise is rejected synchronously. See function
  1622. // description for additional information.
  1623. silenceSuperfluousUnhandledPromiseRejection(fetchPromise);
  1624. // Send the `Sent` event before awaiting the response.
  1625. observer.next({ type: HttpEventType.Sent });
  1626. response = await fetchPromise;
  1627. }
  1628. catch (error) {
  1629. observer.error(new HttpErrorResponse({
  1630. error,
  1631. status: error.status ?? 0,
  1632. statusText: error.statusText,
  1633. url: request.urlWithParams,
  1634. headers: error.headers,
  1635. }));
  1636. return;
  1637. }
  1638. const headers = new HttpHeaders(response.headers);
  1639. const statusText = response.statusText;
  1640. const url = getResponseUrl$1(response) ?? request.urlWithParams;
  1641. let status = response.status;
  1642. let body = null;
  1643. if (request.reportProgress) {
  1644. observer.next(new HttpHeaderResponse({ headers, status, statusText, url }));
  1645. }
  1646. if (response.body) {
  1647. // Read Progress
  1648. const contentLength = response.headers.get('content-length');
  1649. const chunks = [];
  1650. const reader = response.body.getReader();
  1651. let receivedLength = 0;
  1652. let decoder;
  1653. let partialText;
  1654. // We have to check whether the Zone is defined in the global scope because this may be called
  1655. // when the zone is nooped.
  1656. const reqZone = typeof Zone !== 'undefined' && Zone.current;
  1657. // Perform response processing outside of Angular zone to
  1658. // ensure no excessive change detection runs are executed
  1659. // Here calling the async ReadableStreamDefaultReader.read() is responsible for triggering CD
  1660. await this.ngZone.runOutsideAngular(async () => {
  1661. while (true) {
  1662. const { done, value } = await reader.read();
  1663. if (done) {
  1664. break;
  1665. }
  1666. chunks.push(value);
  1667. receivedLength += value.length;
  1668. if (request.reportProgress) {
  1669. partialText =
  1670. request.responseType === 'text'
  1671. ? (partialText ?? '') +
  1672. (decoder ??= new TextDecoder()).decode(value, { stream: true })
  1673. : undefined;
  1674. const reportProgress = () => observer.next({
  1675. type: HttpEventType.DownloadProgress,
  1676. total: contentLength ? +contentLength : undefined,
  1677. loaded: receivedLength,
  1678. partialText,
  1679. });
  1680. reqZone ? reqZone.run(reportProgress) : reportProgress();
  1681. }
  1682. }
  1683. });
  1684. // Combine all chunks.
  1685. const chunksAll = this.concatChunks(chunks, receivedLength);
  1686. try {
  1687. const contentType = response.headers.get(CONTENT_TYPE_HEADER) ?? '';
  1688. body = this.parseBody(request, chunksAll, contentType);
  1689. }
  1690. catch (error) {
  1691. // Body loading or parsing failed
  1692. observer.error(new HttpErrorResponse({
  1693. error,
  1694. headers: new HttpHeaders(response.headers),
  1695. status: response.status,
  1696. statusText: response.statusText,
  1697. url: getResponseUrl$1(response) ?? request.urlWithParams,
  1698. }));
  1699. return;
  1700. }
  1701. }
  1702. // Same behavior as the XhrBackend
  1703. if (status === 0) {
  1704. status = body ? HTTP_STATUS_CODE_OK : 0;
  1705. }
  1706. // ok determines whether the response will be transmitted on the event or
  1707. // error channel. Unsuccessful status codes (not 2xx) will always be errors,
  1708. // but a successful status code can still result in an error if the user
  1709. // asked for JSON data and the body cannot be parsed as such.
  1710. const ok = status >= 200 && status < 300;
  1711. if (ok) {
  1712. observer.next(new HttpResponse({
  1713. body,
  1714. headers,
  1715. status,
  1716. statusText,
  1717. url,
  1718. }));
  1719. // The full body has been received and delivered, no further events
  1720. // are possible. This request is complete.
  1721. observer.complete();
  1722. }
  1723. else {
  1724. observer.error(new HttpErrorResponse({
  1725. error: body,
  1726. headers,
  1727. status,
  1728. statusText,
  1729. url,
  1730. }));
  1731. }
  1732. }
  1733. parseBody(request, binContent, contentType) {
  1734. switch (request.responseType) {
  1735. case 'json':
  1736. // stripping the XSSI when present
  1737. const text = new TextDecoder().decode(binContent).replace(XSSI_PREFIX$1, '');
  1738. return text === '' ? null : JSON.parse(text);
  1739. case 'text':
  1740. return new TextDecoder().decode(binContent);
  1741. case 'blob':
  1742. return new Blob([binContent], { type: contentType });
  1743. case 'arraybuffer':
  1744. return binContent.buffer;
  1745. }
  1746. }
  1747. createRequestInit(req) {
  1748. // We could share some of this logic with the XhrBackend
  1749. const headers = {};
  1750. const credentials = req.withCredentials ? 'include' : undefined;
  1751. // Setting all the requested headers.
  1752. req.headers.forEach((name, values) => (headers[name] = values.join(',')));
  1753. // Add an Accept header if one isn't present already.
  1754. if (!req.headers.has(ACCEPT_HEADER)) {
  1755. headers[ACCEPT_HEADER] = ACCEPT_HEADER_VALUE;
  1756. }
  1757. // Auto-detect the Content-Type header if one isn't present already.
  1758. if (!req.headers.has(CONTENT_TYPE_HEADER)) {
  1759. const detectedType = req.detectContentTypeHeader();
  1760. // Sometimes Content-Type detection fails.
  1761. if (detectedType !== null) {
  1762. headers[CONTENT_TYPE_HEADER] = detectedType;
  1763. }
  1764. }
  1765. return {
  1766. body: req.serializeBody(),
  1767. method: req.method,
  1768. headers,
  1769. credentials,
  1770. };
  1771. }
  1772. concatChunks(chunks, totalLength) {
  1773. const chunksAll = new Uint8Array(totalLength);
  1774. let position = 0;
  1775. for (const chunk of chunks) {
  1776. chunksAll.set(chunk, position);
  1777. position += chunk.length;
  1778. }
  1779. return chunksAll;
  1780. }
  1781. static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: FetchBackend, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
  1782. static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: FetchBackend });
  1783. }
  1784. i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: FetchBackend, decorators: [{
  1785. type: Injectable
  1786. }] });
  1787. /**
  1788. * Abstract class to provide a mocked implementation of `fetch()`
  1789. */
  1790. class FetchFactory {
  1791. }
  1792. function noop() { }
  1793. /**
  1794. * Zone.js treats a rejected promise that has not yet been awaited
  1795. * as an unhandled error. This function adds a noop `.then` to make
  1796. * sure that Zone.js doesn't throw an error if the Promise is rejected
  1797. * synchronously.
  1798. */
  1799. function silenceSuperfluousUnhandledPromiseRejection(promise) {
  1800. promise.then(noop, noop);
  1801. }
  1802. function interceptorChainEndFn(req, finalHandlerFn) {
  1803. return finalHandlerFn(req);
  1804. }
  1805. /**
  1806. * Constructs a `ChainedInterceptorFn` which adapts a legacy `HttpInterceptor` to the
  1807. * `ChainedInterceptorFn` interface.
  1808. */
  1809. function adaptLegacyInterceptorToChain(chainTailFn, interceptor) {
  1810. return (initialRequest, finalHandlerFn) => interceptor.intercept(initialRequest, {
  1811. handle: (downstreamRequest) => chainTailFn(downstreamRequest, finalHandlerFn),
  1812. });
  1813. }
  1814. /**
  1815. * Constructs a `ChainedInterceptorFn` which wraps and invokes a functional interceptor in the given
  1816. * injector.
  1817. */
  1818. function chainedInterceptorFn(chainTailFn, interceptorFn, injector) {
  1819. return (initialRequest, finalHandlerFn) => runInInjectionContext(injector, () => interceptorFn(initialRequest, (downstreamRequest) => chainTailFn(downstreamRequest, finalHandlerFn)));
  1820. }
  1821. /**
  1822. * A multi-provider token that represents the array of registered
  1823. * `HttpInterceptor` objects.
  1824. *
  1825. * @publicApi
  1826. */
  1827. const HTTP_INTERCEPTORS = new InjectionToken(ngDevMode ? 'HTTP_INTERCEPTORS' : '');
  1828. /**
  1829. * A multi-provided token of `HttpInterceptorFn`s.
  1830. */
  1831. const HTTP_INTERCEPTOR_FNS = new InjectionToken(ngDevMode ? 'HTTP_INTERCEPTOR_FNS' : '');
  1832. /**
  1833. * A multi-provided token of `HttpInterceptorFn`s that are only set in root.
  1834. */
  1835. const HTTP_ROOT_INTERCEPTOR_FNS = new InjectionToken(ngDevMode ? 'HTTP_ROOT_INTERCEPTOR_FNS' : '');
  1836. // TODO(atscott): We need a larger discussion about stability and what should contribute to stability.
  1837. // Should the whole interceptor chain contribute to stability or just the backend request #55075?
  1838. // Should HttpClient contribute to stability automatically at all?
  1839. const REQUESTS_CONTRIBUTE_TO_STABILITY = new InjectionToken(ngDevMode ? 'REQUESTS_CONTRIBUTE_TO_STABILITY' : '', { providedIn: 'root', factory: () => true });
  1840. /**
  1841. * Creates an `HttpInterceptorFn` which lazily initializes an interceptor chain from the legacy
  1842. * class-based interceptors and runs the request through it.
  1843. */
  1844. function legacyInterceptorFnFactory() {
  1845. let chain = null;
  1846. return (req, handler) => {
  1847. if (chain === null) {
  1848. const interceptors = inject(HTTP_INTERCEPTORS, { optional: true }) ?? [];
  1849. // Note: interceptors are wrapped right-to-left so that final execution order is
  1850. // left-to-right. That is, if `interceptors` is the array `[a, b, c]`, we want to
  1851. // produce a chain that is conceptually `c(b(a(end)))`, which we build from the inside
  1852. // out.
  1853. chain = interceptors.reduceRight(adaptLegacyInterceptorToChain, interceptorChainEndFn);
  1854. }
  1855. const pendingTasks = inject(_PendingTasksInternal);
  1856. const contributeToStability = inject(REQUESTS_CONTRIBUTE_TO_STABILITY);
  1857. if (contributeToStability) {
  1858. const taskId = pendingTasks.add();
  1859. return chain(req, handler).pipe(finalize(() => pendingTasks.remove(taskId)));
  1860. }
  1861. else {
  1862. return chain(req, handler);
  1863. }
  1864. };
  1865. }
  1866. let fetchBackendWarningDisplayed = false;
  1867. class HttpInterceptorHandler extends HttpHandler {
  1868. backend;
  1869. injector;
  1870. chain = null;
  1871. pendingTasks = inject(_PendingTasksInternal);
  1872. contributeToStability = inject(REQUESTS_CONTRIBUTE_TO_STABILITY);
  1873. constructor(backend, injector) {
  1874. super();
  1875. this.backend = backend;
  1876. this.injector = injector;
  1877. // We strongly recommend using fetch backend for HTTP calls when SSR is used
  1878. // for an application. The logic below checks if that's the case and produces
  1879. // a warning otherwise.
  1880. if ((typeof ngDevMode === 'undefined' || ngDevMode) && !fetchBackendWarningDisplayed) {
  1881. const isServer = isPlatformServer(injector.get(PLATFORM_ID));
  1882. // This flag is necessary because provideHttpClientTesting() overrides the backend
  1883. // even if `withFetch()` is used within the test. When the testing HTTP backend is provided,
  1884. // no HTTP calls are actually performed during the test, so producing a warning would be
  1885. // misleading.
  1886. const isTestingBackend = this.backend.isTestingBackend;
  1887. if (isServer && !(this.backend instanceof FetchBackend) && !isTestingBackend) {
  1888. fetchBackendWarningDisplayed = true;
  1889. injector
  1890. .get(_Console)
  1891. .warn(_formatRuntimeError(2801 /* RuntimeErrorCode.NOT_USING_FETCH_BACKEND_IN_SSR */, 'Angular detected that `HttpClient` is not configured ' +
  1892. "to use `fetch` APIs. It's strongly recommended to " +
  1893. 'enable `fetch` for applications that use Server-Side Rendering ' +
  1894. 'for better performance and compatibility. ' +
  1895. 'To enable `fetch`, add the `withFetch()` to the `provideHttpClient()` ' +
  1896. 'call at the root of the application.'));
  1897. }
  1898. }
  1899. }
  1900. handle(initialRequest) {
  1901. if (this.chain === null) {
  1902. const dedupedInterceptorFns = Array.from(new Set([
  1903. ...this.injector.get(HTTP_INTERCEPTOR_FNS),
  1904. ...this.injector.get(HTTP_ROOT_INTERCEPTOR_FNS, []),
  1905. ]));
  1906. // Note: interceptors are wrapped right-to-left so that final execution order is
  1907. // left-to-right. That is, if `dedupedInterceptorFns` is the array `[a, b, c]`, we want to
  1908. // produce a chain that is conceptually `c(b(a(end)))`, which we build from the inside
  1909. // out.
  1910. this.chain = dedupedInterceptorFns.reduceRight((nextSequencedFn, interceptorFn) => chainedInterceptorFn(nextSequencedFn, interceptorFn, this.injector), interceptorChainEndFn);
  1911. }
  1912. if (this.contributeToStability) {
  1913. const taskId = this.pendingTasks.add();
  1914. return this.chain(initialRequest, (downstreamRequest) => this.backend.handle(downstreamRequest)).pipe(finalize(() => this.pendingTasks.remove(taskId)));
  1915. }
  1916. else {
  1917. return this.chain(initialRequest, (downstreamRequest) => this.backend.handle(downstreamRequest));
  1918. }
  1919. }
  1920. static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: HttpInterceptorHandler, deps: [{ token: HttpBackend }, { token: i0.EnvironmentInjector }], target: i0.ɵɵFactoryTarget.Injectable });
  1921. static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: HttpInterceptorHandler });
  1922. }
  1923. i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: HttpInterceptorHandler, decorators: [{
  1924. type: Injectable
  1925. }], ctorParameters: () => [{ type: HttpBackend }, { type: i0.EnvironmentInjector }] });
  1926. // Every request made through JSONP needs a callback name that's unique across the
  1927. // whole page. Each request is assigned an id and the callback name is constructed
  1928. // from that. The next id to be assigned is tracked in a global variable here that
  1929. // is shared among all applications on the page.
  1930. let nextRequestId = 0;
  1931. /**
  1932. * When a pending <script> is unsubscribed we'll move it to this document, so it won't be
  1933. * executed.
  1934. */
  1935. let foreignDocument;
  1936. // Error text given when a JSONP script is injected, but doesn't invoke the callback
  1937. // passed in its URL.
  1938. const JSONP_ERR_NO_CALLBACK = 'JSONP injected script did not invoke callback.';
  1939. // Error text given when a request is passed to the JsonpClientBackend that doesn't
  1940. // have a request method JSONP.
  1941. const JSONP_ERR_WRONG_METHOD = 'JSONP requests must use JSONP request method.';
  1942. const JSONP_ERR_WRONG_RESPONSE_TYPE = 'JSONP requests must use Json response type.';
  1943. // Error text given when a request is passed to the JsonpClientBackend that has
  1944. // headers set
  1945. const JSONP_ERR_HEADERS_NOT_SUPPORTED = 'JSONP requests do not support headers.';
  1946. /**
  1947. * DI token/abstract type representing a map of JSONP callbacks.
  1948. *
  1949. * In the browser, this should always be the `window` object.
  1950. *
  1951. *
  1952. */
  1953. class JsonpCallbackContext {
  1954. }
  1955. /**
  1956. * Factory function that determines where to store JSONP callbacks.
  1957. *
  1958. * Ordinarily JSONP callbacks are stored on the `window` object, but this may not exist
  1959. * in test environments. In that case, callbacks are stored on an anonymous object instead.
  1960. *
  1961. *
  1962. */
  1963. function jsonpCallbackContext() {
  1964. if (typeof window === 'object') {
  1965. return window;
  1966. }
  1967. return {};
  1968. }
  1969. /**
  1970. * Processes an `HttpRequest` with the JSONP method,
  1971. * by performing JSONP style requests.
  1972. * @see {@link HttpHandler}
  1973. * @see {@link HttpXhrBackend}
  1974. *
  1975. * @publicApi
  1976. */
  1977. class JsonpClientBackend {
  1978. callbackMap;
  1979. document;
  1980. /**
  1981. * A resolved promise that can be used to schedule microtasks in the event handlers.
  1982. */
  1983. resolvedPromise = Promise.resolve();
  1984. constructor(callbackMap, document) {
  1985. this.callbackMap = callbackMap;
  1986. this.document = document;
  1987. }
  1988. /**
  1989. * Get the name of the next callback method, by incrementing the global `nextRequestId`.
  1990. */
  1991. nextCallback() {
  1992. return `ng_jsonp_callback_${nextRequestId++}`;
  1993. }
  1994. /**
  1995. * Processes a JSONP request and returns an event stream of the results.
  1996. * @param req The request object.
  1997. * @returns An observable of the response events.
  1998. *
  1999. */
  2000. handle(req) {
  2001. // Firstly, check both the method and response type. If either doesn't match
  2002. // then the request was improperly routed here and cannot be handled.
  2003. if (req.method !== 'JSONP') {
  2004. throw new Error(JSONP_ERR_WRONG_METHOD);
  2005. }
  2006. else if (req.responseType !== 'json') {
  2007. throw new Error(JSONP_ERR_WRONG_RESPONSE_TYPE);
  2008. }
  2009. // Check the request headers. JSONP doesn't support headers and
  2010. // cannot set any that were supplied.
  2011. if (req.headers.keys().length > 0) {
  2012. throw new Error(JSONP_ERR_HEADERS_NOT_SUPPORTED);
  2013. }
  2014. // Everything else happens inside the Observable boundary.
  2015. return new Observable((observer) => {
  2016. // The first step to make a request is to generate the callback name, and replace the
  2017. // callback placeholder in the URL with the name. Care has to be taken here to ensure
  2018. // a trailing &, if matched, gets inserted back into the URL in the correct place.
  2019. const callback = this.nextCallback();
  2020. const url = req.urlWithParams.replace(/=JSONP_CALLBACK(&|$)/, `=${callback}$1`);
  2021. // Construct the <script> tag and point it at the URL.
  2022. const node = this.document.createElement('script');
  2023. node.src = url;
  2024. // A JSONP request requires waiting for multiple callbacks. These variables
  2025. // are closed over and track state across those callbacks.
  2026. // The response object, if one has been received, or null otherwise.
  2027. let body = null;
  2028. // Whether the response callback has been called.
  2029. let finished = false;
  2030. // Set the response callback in this.callbackMap (which will be the window
  2031. // object in the browser. The script being loaded via the <script> tag will
  2032. // eventually call this callback.
  2033. this.callbackMap[callback] = (data) => {
  2034. // Data has been received from the JSONP script. Firstly, delete this callback.
  2035. delete this.callbackMap[callback];
  2036. // Set state to indicate data was received.
  2037. body = data;
  2038. finished = true;
  2039. };
  2040. // cleanup() is a utility closure that removes the <script> from the page and
  2041. // the response callback from the window. This logic is used in both the
  2042. // success, error, and cancellation paths, so it's extracted out for convenience.
  2043. const cleanup = () => {
  2044. node.removeEventListener('load', onLoad);
  2045. node.removeEventListener('error', onError);
  2046. // Remove the <script> tag if it's still on the page.
  2047. node.remove();
  2048. // Remove the response callback from the callbackMap (window object in the
  2049. // browser).
  2050. delete this.callbackMap[callback];
  2051. };
  2052. // onLoad() is the success callback which runs after the response callback
  2053. // if the JSONP script loads successfully. The event itself is unimportant.
  2054. // If something went wrong, onLoad() may run without the response callback
  2055. // having been invoked.
  2056. const onLoad = (event) => {
  2057. // We wrap it in an extra Promise, to ensure the microtask
  2058. // is scheduled after the loaded endpoint has executed any potential microtask itself,
  2059. // which is not guaranteed in Internet Explorer and EdgeHTML. See issue #39496
  2060. this.resolvedPromise.then(() => {
  2061. // Cleanup the page.
  2062. cleanup();
  2063. // Check whether the response callback has run.
  2064. if (!finished) {
  2065. // It hasn't, something went wrong with the request. Return an error via
  2066. // the Observable error path. All JSONP errors have status 0.
  2067. observer.error(new HttpErrorResponse({
  2068. url,
  2069. status: 0,
  2070. statusText: 'JSONP Error',
  2071. error: new Error(JSONP_ERR_NO_CALLBACK),
  2072. }));
  2073. return;
  2074. }
  2075. // Success. body either contains the response body or null if none was
  2076. // returned.
  2077. observer.next(new HttpResponse({
  2078. body,
  2079. status: HTTP_STATUS_CODE_OK,
  2080. statusText: 'OK',
  2081. url,
  2082. }));
  2083. // Complete the stream, the response is over.
  2084. observer.complete();
  2085. });
  2086. };
  2087. // onError() is the error callback, which runs if the script returned generates
  2088. // a Javascript error. It emits the error via the Observable error channel as
  2089. // a HttpErrorResponse.
  2090. const onError = (error) => {
  2091. cleanup();
  2092. // Wrap the error in a HttpErrorResponse.
  2093. observer.error(new HttpErrorResponse({
  2094. error,
  2095. status: 0,
  2096. statusText: 'JSONP Error',
  2097. url,
  2098. }));
  2099. };
  2100. // Subscribe to both the success (load) and error events on the <script> tag,
  2101. // and add it to the page.
  2102. node.addEventListener('load', onLoad);
  2103. node.addEventListener('error', onError);
  2104. this.document.body.appendChild(node);
  2105. // The request has now been successfully sent.
  2106. observer.next({ type: HttpEventType.Sent });
  2107. // Cancellation handler.
  2108. return () => {
  2109. if (!finished) {
  2110. this.removeListeners(node);
  2111. }
  2112. // And finally, clean up the page.
  2113. cleanup();
  2114. };
  2115. });
  2116. }
  2117. removeListeners(script) {
  2118. // Issue #34818
  2119. // Changing <script>'s ownerDocument will prevent it from execution.
  2120. // https://html.spec.whatwg.org/multipage/scripting.html#execute-the-script-block
  2121. foreignDocument ??= this.document.implementation.createHTMLDocument();
  2122. foreignDocument.adoptNode(script);
  2123. }
  2124. static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: JsonpClientBackend, deps: [{ token: JsonpCallbackContext }, { token: DOCUMENT }], target: i0.ɵɵFactoryTarget.Injectable });
  2125. static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: JsonpClientBackend });
  2126. }
  2127. i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: JsonpClientBackend, decorators: [{
  2128. type: Injectable
  2129. }], ctorParameters: () => [{ type: JsonpCallbackContext }, { type: undefined, decorators: [{
  2130. type: Inject,
  2131. args: [DOCUMENT]
  2132. }] }] });
  2133. /**
  2134. * Identifies requests with the method JSONP and shifts them to the `JsonpClientBackend`.
  2135. */
  2136. function jsonpInterceptorFn(req, next) {
  2137. if (req.method === 'JSONP') {
  2138. return inject(JsonpClientBackend).handle(req);
  2139. }
  2140. // Fall through for normal HTTP requests.
  2141. return next(req);
  2142. }
  2143. /**
  2144. * Identifies requests with the method JSONP and
  2145. * shifts them to the `JsonpClientBackend`.
  2146. *
  2147. * @see {@link HttpInterceptor}
  2148. *
  2149. * @publicApi
  2150. */
  2151. class JsonpInterceptor {
  2152. injector;
  2153. constructor(injector) {
  2154. this.injector = injector;
  2155. }
  2156. /**
  2157. * Identifies and handles a given JSONP request.
  2158. * @param initialRequest The outgoing request object to handle.
  2159. * @param next The next interceptor in the chain, or the backend
  2160. * if no interceptors remain in the chain.
  2161. * @returns An observable of the event stream.
  2162. */
  2163. intercept(initialRequest, next) {
  2164. return runInInjectionContext(this.injector, () => jsonpInterceptorFn(initialRequest, (downstreamRequest) => next.handle(downstreamRequest)));
  2165. }
  2166. static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: JsonpInterceptor, deps: [{ token: i0.EnvironmentInjector }], target: i0.ɵɵFactoryTarget.Injectable });
  2167. static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: JsonpInterceptor });
  2168. }
  2169. i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: JsonpInterceptor, decorators: [{
  2170. type: Injectable
  2171. }], ctorParameters: () => [{ type: i0.EnvironmentInjector }] });
  2172. const XSSI_PREFIX = /^\)\]\}',?\n/;
  2173. const X_REQUEST_URL_REGEXP = RegExp(`^${X_REQUEST_URL_HEADER}:`, 'm');
  2174. /**
  2175. * Determine an appropriate URL for the response, by checking either
  2176. * XMLHttpRequest.responseURL or the X-Request-URL header.
  2177. */
  2178. function getResponseUrl(xhr) {
  2179. if ('responseURL' in xhr && xhr.responseURL) {
  2180. return xhr.responseURL;
  2181. }
  2182. if (X_REQUEST_URL_REGEXP.test(xhr.getAllResponseHeaders())) {
  2183. return xhr.getResponseHeader(X_REQUEST_URL_HEADER);
  2184. }
  2185. return null;
  2186. }
  2187. /**
  2188. * Uses `XMLHttpRequest` to send requests to a backend server.
  2189. * @see {@link HttpHandler}
  2190. * @see {@link JsonpClientBackend}
  2191. *
  2192. * @publicApi
  2193. */
  2194. class HttpXhrBackend {
  2195. xhrFactory;
  2196. constructor(xhrFactory) {
  2197. this.xhrFactory = xhrFactory;
  2198. }
  2199. /**
  2200. * Processes a request and returns a stream of response events.
  2201. * @param req The request object.
  2202. * @returns An observable of the response events.
  2203. */
  2204. handle(req) {
  2205. // Quick check to give a better error message when a user attempts to use
  2206. // HttpClient.jsonp() without installing the HttpClientJsonpModule
  2207. if (req.method === 'JSONP') {
  2208. throw new _RuntimeError(-2800 /* RuntimeErrorCode.MISSING_JSONP_MODULE */, (typeof ngDevMode === 'undefined' || ngDevMode) &&
  2209. `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.`);
  2210. }
  2211. // Check whether this factory has a special function to load an XHR implementation
  2212. // for various non-browser environments. We currently limit it to only `ServerXhr`
  2213. // class, which needs to load an XHR implementation.
  2214. const xhrFactory = this.xhrFactory;
  2215. const source = xhrFactory.ɵloadImpl
  2216. ? from(xhrFactory.ɵloadImpl())
  2217. : of(null);
  2218. return source.pipe(switchMap(() => {
  2219. // Everything happens on Observable subscription.
  2220. return new Observable((observer) => {
  2221. // Start by setting up the XHR object with request method, URL, and withCredentials
  2222. // flag.
  2223. const xhr = xhrFactory.build();
  2224. xhr.open(req.method, req.urlWithParams);
  2225. if (req.withCredentials) {
  2226. xhr.withCredentials = true;
  2227. }
  2228. // Add all the requested headers.
  2229. req.headers.forEach((name, values) => xhr.setRequestHeader(name, values.join(',')));
  2230. // Add an Accept header if one isn't present already.
  2231. if (!req.headers.has(ACCEPT_HEADER)) {
  2232. xhr.setRequestHeader(ACCEPT_HEADER, ACCEPT_HEADER_VALUE);
  2233. }
  2234. // Auto-detect the Content-Type header if one isn't present already.
  2235. if (!req.headers.has(CONTENT_TYPE_HEADER)) {
  2236. const detectedType = req.detectContentTypeHeader();
  2237. // Sometimes Content-Type detection fails.
  2238. if (detectedType !== null) {
  2239. xhr.setRequestHeader(CONTENT_TYPE_HEADER, detectedType);
  2240. }
  2241. }
  2242. // Set the responseType if one was requested.
  2243. if (req.responseType) {
  2244. const responseType = req.responseType.toLowerCase();
  2245. // JSON responses need to be processed as text. This is because if the server
  2246. // returns an XSSI-prefixed JSON response, the browser will fail to parse it,
  2247. // xhr.response will be null, and xhr.responseText cannot be accessed to
  2248. // retrieve the prefixed JSON data in order to strip the prefix. Thus, all JSON
  2249. // is parsed by first requesting text and then applying JSON.parse.
  2250. xhr.responseType = (responseType !== 'json' ? responseType : 'text');
  2251. }
  2252. // Serialize the request body if one is present. If not, this will be set to null.
  2253. const reqBody = req.serializeBody();
  2254. // If progress events are enabled, response headers will be delivered
  2255. // in two events - the HttpHeaderResponse event and the full HttpResponse
  2256. // event. However, since response headers don't change in between these
  2257. // two events, it doesn't make sense to parse them twice. So headerResponse
  2258. // caches the data extracted from the response whenever it's first parsed,
  2259. // to ensure parsing isn't duplicated.
  2260. let headerResponse = null;
  2261. // partialFromXhr extracts the HttpHeaderResponse from the current XMLHttpRequest
  2262. // state, and memoizes it into headerResponse.
  2263. const partialFromXhr = () => {
  2264. if (headerResponse !== null) {
  2265. return headerResponse;
  2266. }
  2267. const statusText = xhr.statusText || 'OK';
  2268. // Parse headers from XMLHttpRequest - this step is lazy.
  2269. const headers = new HttpHeaders(xhr.getAllResponseHeaders());
  2270. // Read the response URL from the XMLHttpResponse instance and fall back on the
  2271. // request URL.
  2272. const url = getResponseUrl(xhr) || req.url;
  2273. // Construct the HttpHeaderResponse and memoize it.
  2274. headerResponse = new HttpHeaderResponse({ headers, status: xhr.status, statusText, url });
  2275. return headerResponse;
  2276. };
  2277. // Next, a few closures are defined for the various events which XMLHttpRequest can
  2278. // emit. This allows them to be unregistered as event listeners later.
  2279. // First up is the load event, which represents a response being fully available.
  2280. const onLoad = () => {
  2281. // Read response state from the memoized partial data.
  2282. let { headers, status, statusText, url } = partialFromXhr();
  2283. // The body will be read out if present.
  2284. let body = null;
  2285. if (status !== HTTP_STATUS_CODE_NO_CONTENT) {
  2286. // Use XMLHttpRequest.response if set, responseText otherwise.
  2287. body = typeof xhr.response === 'undefined' ? xhr.responseText : xhr.response;
  2288. }
  2289. // Normalize another potential bug (this one comes from CORS).
  2290. if (status === 0) {
  2291. status = !!body ? HTTP_STATUS_CODE_OK : 0;
  2292. }
  2293. // ok determines whether the response will be transmitted on the event or
  2294. // error channel. Unsuccessful status codes (not 2xx) will always be errors,
  2295. // but a successful status code can still result in an error if the user
  2296. // asked for JSON data and the body cannot be parsed as such.
  2297. let ok = status >= 200 && status < 300;
  2298. // Check whether the body needs to be parsed as JSON (in many cases the browser
  2299. // will have done that already).
  2300. if (req.responseType === 'json' && typeof body === 'string') {
  2301. // Save the original body, before attempting XSSI prefix stripping.
  2302. const originalBody = body;
  2303. body = body.replace(XSSI_PREFIX, '');
  2304. try {
  2305. // Attempt the parse. If it fails, a parse error should be delivered to the
  2306. // user.
  2307. body = body !== '' ? JSON.parse(body) : null;
  2308. }
  2309. catch (error) {
  2310. // Since the JSON.parse failed, it's reasonable to assume this might not have
  2311. // been a JSON response. Restore the original body (including any XSSI prefix)
  2312. // to deliver a better error response.
  2313. body = originalBody;
  2314. // If this was an error request to begin with, leave it as a string, it
  2315. // probably just isn't JSON. Otherwise, deliver the parsing error to the user.
  2316. if (ok) {
  2317. // Even though the response status was 2xx, this is still an error.
  2318. ok = false;
  2319. // The parse error contains the text of the body that failed to parse.
  2320. body = { error, text: body };
  2321. }
  2322. }
  2323. }
  2324. if (ok) {
  2325. // A successful response is delivered on the event stream.
  2326. observer.next(new HttpResponse({
  2327. body,
  2328. headers,
  2329. status,
  2330. statusText,
  2331. url: url || undefined,
  2332. }));
  2333. // The full body has been received and delivered, no further events
  2334. // are possible. This request is complete.
  2335. observer.complete();
  2336. }
  2337. else {
  2338. // An unsuccessful request is delivered on the error channel.
  2339. observer.error(new HttpErrorResponse({
  2340. // The error in this case is the response body (error from the server).
  2341. error: body,
  2342. headers,
  2343. status,
  2344. statusText,
  2345. url: url || undefined,
  2346. }));
  2347. }
  2348. };
  2349. // The onError callback is called when something goes wrong at the network level.
  2350. // Connection timeout, DNS error, offline, etc. These are actual errors, and are
  2351. // transmitted on the error channel.
  2352. const onError = (error) => {
  2353. const { url } = partialFromXhr();
  2354. const res = new HttpErrorResponse({
  2355. error,
  2356. status: xhr.status || 0,
  2357. statusText: xhr.statusText || 'Unknown Error',
  2358. url: url || undefined,
  2359. });
  2360. observer.error(res);
  2361. };
  2362. // The sentHeaders flag tracks whether the HttpResponseHeaders event
  2363. // has been sent on the stream. This is necessary to track if progress
  2364. // is enabled since the event will be sent on only the first download
  2365. // progress event.
  2366. let sentHeaders = false;
  2367. // The download progress event handler, which is only registered if
  2368. // progress events are enabled.
  2369. const onDownProgress = (event) => {
  2370. // Send the HttpResponseHeaders event if it hasn't been sent already.
  2371. if (!sentHeaders) {
  2372. observer.next(partialFromXhr());
  2373. sentHeaders = true;
  2374. }
  2375. // Start building the download progress event to deliver on the response
  2376. // event stream.
  2377. let progressEvent = {
  2378. type: HttpEventType.DownloadProgress,
  2379. loaded: event.loaded,
  2380. };
  2381. // Set the total number of bytes in the event if it's available.
  2382. if (event.lengthComputable) {
  2383. progressEvent.total = event.total;
  2384. }
  2385. // If the request was for text content and a partial response is
  2386. // available on XMLHttpRequest, include it in the progress event
  2387. // to allow for streaming reads.
  2388. if (req.responseType === 'text' && !!xhr.responseText) {
  2389. progressEvent.partialText = xhr.responseText;
  2390. }
  2391. // Finally, fire the event.
  2392. observer.next(progressEvent);
  2393. };
  2394. // The upload progress event handler, which is only registered if
  2395. // progress events are enabled.
  2396. const onUpProgress = (event) => {
  2397. // Upload progress events are simpler. Begin building the progress
  2398. // event.
  2399. let progress = {
  2400. type: HttpEventType.UploadProgress,
  2401. loaded: event.loaded,
  2402. };
  2403. // If the total number of bytes being uploaded is available, include
  2404. // it.
  2405. if (event.lengthComputable) {
  2406. progress.total = event.total;
  2407. }
  2408. // Send the event.
  2409. observer.next(progress);
  2410. };
  2411. // By default, register for load and error events.
  2412. xhr.addEventListener('load', onLoad);
  2413. xhr.addEventListener('error', onError);
  2414. xhr.addEventListener('timeout', onError);
  2415. xhr.addEventListener('abort', onError);
  2416. // Progress events are only enabled if requested.
  2417. if (req.reportProgress) {
  2418. // Download progress is always enabled if requested.
  2419. xhr.addEventListener('progress', onDownProgress);
  2420. // Upload progress depends on whether there is a body to upload.
  2421. if (reqBody !== null && xhr.upload) {
  2422. xhr.upload.addEventListener('progress', onUpProgress);
  2423. }
  2424. }
  2425. // Fire the request, and notify the event stream that it was fired.
  2426. xhr.send(reqBody);
  2427. observer.next({ type: HttpEventType.Sent });
  2428. // This is the return from the Observable function, which is the
  2429. // request cancellation handler.
  2430. return () => {
  2431. // On a cancellation, remove all registered event listeners.
  2432. xhr.removeEventListener('error', onError);
  2433. xhr.removeEventListener('abort', onError);
  2434. xhr.removeEventListener('load', onLoad);
  2435. xhr.removeEventListener('timeout', onError);
  2436. if (req.reportProgress) {
  2437. xhr.removeEventListener('progress', onDownProgress);
  2438. if (reqBody !== null && xhr.upload) {
  2439. xhr.upload.removeEventListener('progress', onUpProgress);
  2440. }
  2441. }
  2442. // Finally, abort the in-flight request.
  2443. if (xhr.readyState !== xhr.DONE) {
  2444. xhr.abort();
  2445. }
  2446. };
  2447. });
  2448. }));
  2449. }
  2450. static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: HttpXhrBackend, deps: [{ token: i1.XhrFactory }], target: i0.ɵɵFactoryTarget.Injectable });
  2451. static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: HttpXhrBackend });
  2452. }
  2453. i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: HttpXhrBackend, decorators: [{
  2454. type: Injectable
  2455. }], ctorParameters: () => [{ type: i1.XhrFactory }] });
  2456. const XSRF_ENABLED = new InjectionToken(ngDevMode ? 'XSRF_ENABLED' : '');
  2457. const XSRF_DEFAULT_COOKIE_NAME = 'XSRF-TOKEN';
  2458. const XSRF_COOKIE_NAME = new InjectionToken(ngDevMode ? 'XSRF_COOKIE_NAME' : '', {
  2459. providedIn: 'root',
  2460. factory: () => XSRF_DEFAULT_COOKIE_NAME,
  2461. });
  2462. const XSRF_DEFAULT_HEADER_NAME = 'X-XSRF-TOKEN';
  2463. const XSRF_HEADER_NAME = new InjectionToken(ngDevMode ? 'XSRF_HEADER_NAME' : '', {
  2464. providedIn: 'root',
  2465. factory: () => XSRF_DEFAULT_HEADER_NAME,
  2466. });
  2467. /**
  2468. * Retrieves the current XSRF token to use with the next outgoing request.
  2469. *
  2470. * @publicApi
  2471. */
  2472. class HttpXsrfTokenExtractor {
  2473. }
  2474. /**
  2475. * `HttpXsrfTokenExtractor` which retrieves the token from a cookie.
  2476. */
  2477. class HttpXsrfCookieExtractor {
  2478. doc;
  2479. platform;
  2480. cookieName;
  2481. lastCookieString = '';
  2482. lastToken = null;
  2483. /**
  2484. * @internal for testing
  2485. */
  2486. parseCount = 0;
  2487. constructor(doc, platform, cookieName) {
  2488. this.doc = doc;
  2489. this.platform = platform;
  2490. this.cookieName = cookieName;
  2491. }
  2492. getToken() {
  2493. if (this.platform === 'server') {
  2494. return null;
  2495. }
  2496. const cookieString = this.doc.cookie || '';
  2497. if (cookieString !== this.lastCookieString) {
  2498. this.parseCount++;
  2499. this.lastToken = _parseCookieValue(cookieString, this.cookieName);
  2500. this.lastCookieString = cookieString;
  2501. }
  2502. return this.lastToken;
  2503. }
  2504. static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: HttpXsrfCookieExtractor, deps: [{ token: DOCUMENT }, { token: PLATFORM_ID }, { token: XSRF_COOKIE_NAME }], target: i0.ɵɵFactoryTarget.Injectable });
  2505. static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: HttpXsrfCookieExtractor });
  2506. }
  2507. i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: HttpXsrfCookieExtractor, decorators: [{
  2508. type: Injectable
  2509. }], ctorParameters: () => [{ type: undefined, decorators: [{
  2510. type: Inject,
  2511. args: [DOCUMENT]
  2512. }] }, { type: undefined, decorators: [{
  2513. type: Inject,
  2514. args: [PLATFORM_ID]
  2515. }] }, { type: undefined, decorators: [{
  2516. type: Inject,
  2517. args: [XSRF_COOKIE_NAME]
  2518. }] }] });
  2519. function xsrfInterceptorFn(req, next) {
  2520. const lcUrl = req.url.toLowerCase();
  2521. // Skip both non-mutating requests and absolute URLs.
  2522. // Non-mutating requests don't require a token, and absolute URLs require special handling
  2523. // anyway as the cookie set
  2524. // on our origin is not the same as the token expected by another origin.
  2525. if (!inject(XSRF_ENABLED) ||
  2526. req.method === 'GET' ||
  2527. req.method === 'HEAD' ||
  2528. lcUrl.startsWith('http://') ||
  2529. lcUrl.startsWith('https://')) {
  2530. return next(req);
  2531. }
  2532. const token = inject(HttpXsrfTokenExtractor).getToken();
  2533. const headerName = inject(XSRF_HEADER_NAME);
  2534. // Be careful not to overwrite an existing header of the same name.
  2535. if (token != null && !req.headers.has(headerName)) {
  2536. req = req.clone({ headers: req.headers.set(headerName, token) });
  2537. }
  2538. return next(req);
  2539. }
  2540. /**
  2541. * `HttpInterceptor` which adds an XSRF token to eligible outgoing requests.
  2542. */
  2543. class HttpXsrfInterceptor {
  2544. injector;
  2545. constructor(injector) {
  2546. this.injector = injector;
  2547. }
  2548. intercept(initialRequest, next) {
  2549. return runInInjectionContext(this.injector, () => xsrfInterceptorFn(initialRequest, (downstreamRequest) => next.handle(downstreamRequest)));
  2550. }
  2551. static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: HttpXsrfInterceptor, deps: [{ token: i0.EnvironmentInjector }], target: i0.ɵɵFactoryTarget.Injectable });
  2552. static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: HttpXsrfInterceptor });
  2553. }
  2554. i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: HttpXsrfInterceptor, decorators: [{
  2555. type: Injectable
  2556. }], ctorParameters: () => [{ type: i0.EnvironmentInjector }] });
  2557. /**
  2558. * Identifies a particular kind of `HttpFeature`.
  2559. *
  2560. * @publicApi
  2561. */
  2562. var HttpFeatureKind;
  2563. (function (HttpFeatureKind) {
  2564. HttpFeatureKind[HttpFeatureKind["Interceptors"] = 0] = "Interceptors";
  2565. HttpFeatureKind[HttpFeatureKind["LegacyInterceptors"] = 1] = "LegacyInterceptors";
  2566. HttpFeatureKind[HttpFeatureKind["CustomXsrfConfiguration"] = 2] = "CustomXsrfConfiguration";
  2567. HttpFeatureKind[HttpFeatureKind["NoXsrfProtection"] = 3] = "NoXsrfProtection";
  2568. HttpFeatureKind[HttpFeatureKind["JsonpSupport"] = 4] = "JsonpSupport";
  2569. HttpFeatureKind[HttpFeatureKind["RequestsMadeViaParent"] = 5] = "RequestsMadeViaParent";
  2570. HttpFeatureKind[HttpFeatureKind["Fetch"] = 6] = "Fetch";
  2571. })(HttpFeatureKind || (HttpFeatureKind = {}));
  2572. function makeHttpFeature(kind, providers) {
  2573. return {
  2574. ɵkind: kind,
  2575. ɵproviders: providers,
  2576. };
  2577. }
  2578. /**
  2579. * Configures Angular's `HttpClient` service to be available for injection.
  2580. *
  2581. * By default, `HttpClient` will be configured for injection with its default options for XSRF
  2582. * protection of outgoing requests. Additional configuration options can be provided by passing
  2583. * feature functions to `provideHttpClient`. For example, HTTP interceptors can be added using the
  2584. * `withInterceptors(...)` feature.
  2585. *
  2586. * <div class="docs-alert docs-alert-helpful">
  2587. *
  2588. * It's strongly recommended to enable
  2589. * [`fetch`](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) for applications that use
  2590. * Server-Side Rendering for better performance and compatibility. To enable `fetch`, add
  2591. * `withFetch()` feature to the `provideHttpClient()` call at the root of the application:
  2592. *
  2593. * ```ts
  2594. * provideHttpClient(withFetch());
  2595. * ```
  2596. *
  2597. * </div>
  2598. *
  2599. * @see {@link withInterceptors}
  2600. * @see {@link withInterceptorsFromDi}
  2601. * @see {@link withXsrfConfiguration}
  2602. * @see {@link withNoXsrfProtection}
  2603. * @see {@link withJsonpSupport}
  2604. * @see {@link withRequestsMadeViaParent}
  2605. * @see {@link withFetch}
  2606. */
  2607. function provideHttpClient(...features) {
  2608. if (ngDevMode) {
  2609. const featureKinds = new Set(features.map((f) => f.ɵkind));
  2610. if (featureKinds.has(HttpFeatureKind.NoXsrfProtection) &&
  2611. featureKinds.has(HttpFeatureKind.CustomXsrfConfiguration)) {
  2612. throw new Error(ngDevMode
  2613. ? `Configuration error: found both withXsrfConfiguration() and withNoXsrfProtection() in the same call to provideHttpClient(), which is a contradiction.`
  2614. : '');
  2615. }
  2616. }
  2617. const providers = [
  2618. HttpClient,
  2619. HttpXhrBackend,
  2620. HttpInterceptorHandler,
  2621. { provide: HttpHandler, useExisting: HttpInterceptorHandler },
  2622. {
  2623. provide: HttpBackend,
  2624. useFactory: () => {
  2625. return inject(FETCH_BACKEND, { optional: true }) ?? inject(HttpXhrBackend);
  2626. },
  2627. },
  2628. {
  2629. provide: HTTP_INTERCEPTOR_FNS,
  2630. useValue: xsrfInterceptorFn,
  2631. multi: true,
  2632. },
  2633. { provide: XSRF_ENABLED, useValue: true },
  2634. { provide: HttpXsrfTokenExtractor, useClass: HttpXsrfCookieExtractor },
  2635. ];
  2636. for (const feature of features) {
  2637. providers.push(...feature.ɵproviders);
  2638. }
  2639. return makeEnvironmentProviders(providers);
  2640. }
  2641. /**
  2642. * Adds one or more functional-style HTTP interceptors to the configuration of the `HttpClient`
  2643. * instance.
  2644. *
  2645. * @see {@link HttpInterceptorFn}
  2646. * @see {@link provideHttpClient}
  2647. * @publicApi
  2648. */
  2649. function withInterceptors(interceptorFns) {
  2650. return makeHttpFeature(HttpFeatureKind.Interceptors, interceptorFns.map((interceptorFn) => {
  2651. return {
  2652. provide: HTTP_INTERCEPTOR_FNS,
  2653. useValue: interceptorFn,
  2654. multi: true,
  2655. };
  2656. }));
  2657. }
  2658. const LEGACY_INTERCEPTOR_FN = new InjectionToken(ngDevMode ? 'LEGACY_INTERCEPTOR_FN' : '');
  2659. /**
  2660. * Includes class-based interceptors configured using a multi-provider in the current injector into
  2661. * the configured `HttpClient` instance.
  2662. *
  2663. * Prefer `withInterceptors` and functional interceptors instead, as support for DI-provided
  2664. * interceptors may be phased out in a later release.
  2665. *
  2666. * @see {@link HttpInterceptor}
  2667. * @see {@link HTTP_INTERCEPTORS}
  2668. * @see {@link provideHttpClient}
  2669. */
  2670. function withInterceptorsFromDi() {
  2671. // Note: the legacy interceptor function is provided here via an intermediate token
  2672. // (`LEGACY_INTERCEPTOR_FN`), using a pattern which guarantees that if these providers are
  2673. // included multiple times, all of the multi-provider entries will have the same instance of the
  2674. // interceptor function. That way, the `HttpINterceptorHandler` will dedup them and legacy
  2675. // interceptors will not run multiple times.
  2676. return makeHttpFeature(HttpFeatureKind.LegacyInterceptors, [
  2677. {
  2678. provide: LEGACY_INTERCEPTOR_FN,
  2679. useFactory: legacyInterceptorFnFactory,
  2680. },
  2681. {
  2682. provide: HTTP_INTERCEPTOR_FNS,
  2683. useExisting: LEGACY_INTERCEPTOR_FN,
  2684. multi: true,
  2685. },
  2686. ]);
  2687. }
  2688. /**
  2689. * Customizes the XSRF protection for the configuration of the current `HttpClient` instance.
  2690. *
  2691. * This feature is incompatible with the `withNoXsrfProtection` feature.
  2692. *
  2693. * @see {@link provideHttpClient}
  2694. */
  2695. function withXsrfConfiguration({ cookieName, headerName, }) {
  2696. const providers = [];
  2697. if (cookieName !== undefined) {
  2698. providers.push({ provide: XSRF_COOKIE_NAME, useValue: cookieName });
  2699. }
  2700. if (headerName !== undefined) {
  2701. providers.push({ provide: XSRF_HEADER_NAME, useValue: headerName });
  2702. }
  2703. return makeHttpFeature(HttpFeatureKind.CustomXsrfConfiguration, providers);
  2704. }
  2705. /**
  2706. * Disables XSRF protection in the configuration of the current `HttpClient` instance.
  2707. *
  2708. * This feature is incompatible with the `withXsrfConfiguration` feature.
  2709. *
  2710. * @see {@link provideHttpClient}
  2711. */
  2712. function withNoXsrfProtection() {
  2713. return makeHttpFeature(HttpFeatureKind.NoXsrfProtection, [
  2714. {
  2715. provide: XSRF_ENABLED,
  2716. useValue: false,
  2717. },
  2718. ]);
  2719. }
  2720. /**
  2721. * Add JSONP support to the configuration of the current `HttpClient` instance.
  2722. *
  2723. * @see {@link provideHttpClient}
  2724. */
  2725. function withJsonpSupport() {
  2726. return makeHttpFeature(HttpFeatureKind.JsonpSupport, [
  2727. JsonpClientBackend,
  2728. { provide: JsonpCallbackContext, useFactory: jsonpCallbackContext },
  2729. { provide: HTTP_INTERCEPTOR_FNS, useValue: jsonpInterceptorFn, multi: true },
  2730. ]);
  2731. }
  2732. /**
  2733. * Configures the current `HttpClient` instance to make requests via the parent injector's
  2734. * `HttpClient` instead of directly.
  2735. *
  2736. * By default, `provideHttpClient` configures `HttpClient` in its injector to be an independent
  2737. * instance. For example, even if `HttpClient` is configured in the parent injector with
  2738. * one or more interceptors, they will not intercept requests made via this instance.
  2739. *
  2740. * With this option enabled, once the request has passed through the current injector's
  2741. * interceptors, it will be delegated to the parent injector's `HttpClient` chain instead of
  2742. * dispatched directly, and interceptors in the parent configuration will be applied to the request.
  2743. *
  2744. * If there are several `HttpClient` instances in the injector hierarchy, it's possible for
  2745. * `withRequestsMadeViaParent` to be used at multiple levels, which will cause the request to
  2746. * "bubble up" until either reaching the root level or an `HttpClient` which was not configured with
  2747. * this option.
  2748. *
  2749. * @see {@link provideHttpClient}
  2750. * @publicApi
  2751. */
  2752. function withRequestsMadeViaParent() {
  2753. return makeHttpFeature(HttpFeatureKind.RequestsMadeViaParent, [
  2754. {
  2755. provide: HttpBackend,
  2756. useFactory: () => {
  2757. const handlerFromParent = inject(HttpHandler, { skipSelf: true, optional: true });
  2758. if (ngDevMode && handlerFromParent === null) {
  2759. throw new Error('withRequestsMadeViaParent() can only be used when the parent injector also configures HttpClient');
  2760. }
  2761. return handlerFromParent;
  2762. },
  2763. },
  2764. ]);
  2765. }
  2766. /**
  2767. * Configures the current `HttpClient` instance to make requests using the fetch API.
  2768. *
  2769. * Note: The Fetch API doesn't support progress report on uploads.
  2770. *
  2771. * @publicApi
  2772. */
  2773. function withFetch() {
  2774. return makeHttpFeature(HttpFeatureKind.Fetch, [
  2775. FetchBackend,
  2776. { provide: FETCH_BACKEND, useExisting: FetchBackend },
  2777. { provide: HttpBackend, useExisting: FetchBackend },
  2778. ]);
  2779. }
  2780. /**
  2781. * Configures XSRF protection support for outgoing requests.
  2782. *
  2783. * For a server that supports a cookie-based XSRF protection system,
  2784. * use directly to configure XSRF protection with the correct
  2785. * cookie and header names.
  2786. *
  2787. * If no names are supplied, the default cookie name is `XSRF-TOKEN`
  2788. * and the default header name is `X-XSRF-TOKEN`.
  2789. *
  2790. * @publicApi
  2791. * @deprecated Use withXsrfConfiguration({cookieName: 'XSRF-TOKEN', headerName: 'X-XSRF-TOKEN'}) as
  2792. * providers instead or `withNoXsrfProtection` if you want to disabled XSRF protection.
  2793. */
  2794. class HttpClientXsrfModule {
  2795. /**
  2796. * Disable the default XSRF protection.
  2797. */
  2798. static disable() {
  2799. return {
  2800. ngModule: HttpClientXsrfModule,
  2801. providers: [withNoXsrfProtection().ɵproviders],
  2802. };
  2803. }
  2804. /**
  2805. * Configure XSRF protection.
  2806. * @param options An object that can specify either or both
  2807. * cookie name or header name.
  2808. * - Cookie name default is `XSRF-TOKEN`.
  2809. * - Header name default is `X-XSRF-TOKEN`.
  2810. *
  2811. */
  2812. static withOptions(options = {}) {
  2813. return {
  2814. ngModule: HttpClientXsrfModule,
  2815. providers: withXsrfConfiguration(options).ɵproviders,
  2816. };
  2817. }
  2818. static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: HttpClientXsrfModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
  2819. static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.2.4", ngImport: i0, type: HttpClientXsrfModule });
  2820. static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: HttpClientXsrfModule, providers: [
  2821. HttpXsrfInterceptor,
  2822. { provide: HTTP_INTERCEPTORS, useExisting: HttpXsrfInterceptor, multi: true },
  2823. { provide: HttpXsrfTokenExtractor, useClass: HttpXsrfCookieExtractor },
  2824. withXsrfConfiguration({
  2825. cookieName: XSRF_DEFAULT_COOKIE_NAME,
  2826. headerName: XSRF_DEFAULT_HEADER_NAME,
  2827. }).ɵproviders,
  2828. { provide: XSRF_ENABLED, useValue: true },
  2829. ] });
  2830. }
  2831. i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: HttpClientXsrfModule, decorators: [{
  2832. type: NgModule,
  2833. args: [{
  2834. providers: [
  2835. HttpXsrfInterceptor,
  2836. { provide: HTTP_INTERCEPTORS, useExisting: HttpXsrfInterceptor, multi: true },
  2837. { provide: HttpXsrfTokenExtractor, useClass: HttpXsrfCookieExtractor },
  2838. withXsrfConfiguration({
  2839. cookieName: XSRF_DEFAULT_COOKIE_NAME,
  2840. headerName: XSRF_DEFAULT_HEADER_NAME,
  2841. }).ɵproviders,
  2842. { provide: XSRF_ENABLED, useValue: true },
  2843. ],
  2844. }]
  2845. }] });
  2846. /**
  2847. * Configures the dependency injector for `HttpClient`
  2848. * with supporting services for XSRF. Automatically imported by `HttpClientModule`.
  2849. *
  2850. * You can add interceptors to the chain behind `HttpClient` by binding them to the
  2851. * multiprovider for built-in DI token `HTTP_INTERCEPTORS`.
  2852. *
  2853. * @publicApi
  2854. * @deprecated use `provideHttpClient(withInterceptorsFromDi())` as providers instead
  2855. */
  2856. class HttpClientModule {
  2857. static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: HttpClientModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
  2858. static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.2.4", ngImport: i0, type: HttpClientModule });
  2859. static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: HttpClientModule, providers: [provideHttpClient(withInterceptorsFromDi())] });
  2860. }
  2861. i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: HttpClientModule, decorators: [{
  2862. type: NgModule,
  2863. args: [{
  2864. /**
  2865. * Configures the dependency injector where it is imported
  2866. * with supporting services for HTTP communications.
  2867. */
  2868. providers: [provideHttpClient(withInterceptorsFromDi())],
  2869. }]
  2870. }] });
  2871. /**
  2872. * Configures the dependency injector for `HttpClient`
  2873. * with supporting services for JSONP.
  2874. * Without this module, Jsonp requests reach the backend
  2875. * with method JSONP, where they are rejected.
  2876. *
  2877. * @publicApi
  2878. * @deprecated `withJsonpSupport()` as providers instead
  2879. */
  2880. class HttpClientJsonpModule {
  2881. static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: HttpClientJsonpModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
  2882. static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.2.4", ngImport: i0, type: HttpClientJsonpModule });
  2883. static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: HttpClientJsonpModule, providers: [withJsonpSupport().ɵproviders] });
  2884. }
  2885. i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: HttpClientJsonpModule, decorators: [{
  2886. type: NgModule,
  2887. args: [{
  2888. providers: [withJsonpSupport().ɵproviders],
  2889. }]
  2890. }] });
  2891. /**
  2892. * `httpResource` makes a reactive HTTP request and exposes the request status and response value as
  2893. * a `WritableResource`. By default, it assumes that the backend will return JSON data. To make a
  2894. * request that expects a different kind of data, you can use a sub-constructor of `httpResource`,
  2895. * such as `httpResource.text`.
  2896. *
  2897. * @experimental
  2898. * @initializerApiFunction
  2899. */
  2900. const httpResource = (() => {
  2901. const jsonFn = makeHttpResourceFn('json');
  2902. jsonFn.arrayBuffer = makeHttpResourceFn('arraybuffer');
  2903. jsonFn.blob = makeHttpResourceFn('blob');
  2904. jsonFn.text = makeHttpResourceFn('text');
  2905. return jsonFn;
  2906. })();
  2907. function makeHttpResourceFn(responseType) {
  2908. return function httpResourceRef(request, options) {
  2909. options?.injector || assertInInjectionContext(httpResource);
  2910. const injector = options?.injector ?? inject(Injector);
  2911. return new HttpResourceImpl(injector, () => normalizeRequest(request, responseType), options?.defaultValue, options?.parse, options?.equal);
  2912. };
  2913. }
  2914. function normalizeRequest(request, responseType) {
  2915. let unwrappedRequest = typeof request === 'function' ? request() : request;
  2916. if (unwrappedRequest === undefined) {
  2917. return undefined;
  2918. }
  2919. else if (typeof unwrappedRequest === 'string') {
  2920. unwrappedRequest = { url: unwrappedRequest };
  2921. }
  2922. const headers = unwrappedRequest.headers instanceof HttpHeaders
  2923. ? unwrappedRequest.headers
  2924. : new HttpHeaders(unwrappedRequest.headers);
  2925. const params = unwrappedRequest.params instanceof HttpParams
  2926. ? unwrappedRequest.params
  2927. : new HttpParams({ fromObject: unwrappedRequest.params });
  2928. return new HttpRequest(unwrappedRequest.method ?? 'GET', unwrappedRequest.url, unwrappedRequest.body ?? null, {
  2929. headers,
  2930. params,
  2931. reportProgress: unwrappedRequest.reportProgress,
  2932. withCredentials: unwrappedRequest.withCredentials,
  2933. responseType,
  2934. context: unwrappedRequest.context,
  2935. transferCache: unwrappedRequest.transferCache,
  2936. });
  2937. }
  2938. class HttpResourceImpl extends _ResourceImpl {
  2939. client;
  2940. _headers = linkedSignal({
  2941. source: this.extRequest,
  2942. computation: () => undefined,
  2943. });
  2944. _progress = linkedSignal({
  2945. source: this.extRequest,
  2946. computation: () => undefined,
  2947. });
  2948. _statusCode = linkedSignal({
  2949. source: this.extRequest,
  2950. computation: () => undefined,
  2951. });
  2952. headers = computed(() => this.status() === ResourceStatus.Resolved || this.status() === ResourceStatus.Error
  2953. ? this._headers()
  2954. : undefined);
  2955. progress = this._progress.asReadonly();
  2956. statusCode = this._statusCode.asReadonly();
  2957. constructor(injector, request, defaultValue, parse, equal) {
  2958. super(request, ({ request, abortSignal }) => {
  2959. let sub;
  2960. // Track the abort listener so it can be removed if the Observable completes (as a memory
  2961. // optimization).
  2962. const onAbort = () => sub.unsubscribe();
  2963. abortSignal.addEventListener('abort', onAbort);
  2964. // Start off stream as undefined.
  2965. const stream = signal({ value: undefined });
  2966. let resolve;
  2967. const promise = new Promise((r) => (resolve = r));
  2968. const send = (value) => {
  2969. stream.set(value);
  2970. resolve?.(stream);
  2971. resolve = undefined;
  2972. };
  2973. sub = this.client.request(request).subscribe({
  2974. next: (event) => {
  2975. switch (event.type) {
  2976. case HttpEventType.Response:
  2977. this._headers.set(event.headers);
  2978. this._statusCode.set(event.status);
  2979. try {
  2980. send({ value: parse ? parse(event.body) : event.body });
  2981. }
  2982. catch (error) {
  2983. send({ error });
  2984. }
  2985. break;
  2986. case HttpEventType.DownloadProgress:
  2987. this._progress.set(event);
  2988. break;
  2989. }
  2990. },
  2991. error: (error) => send({ error }),
  2992. complete: () => {
  2993. if (resolve) {
  2994. send({ error: new Error('Resource completed before producing a value') });
  2995. }
  2996. abortSignal.removeEventListener('abort', onAbort);
  2997. },
  2998. });
  2999. return promise;
  3000. }, defaultValue, equal, injector);
  3001. this.client = injector.get(HttpClient);
  3002. }
  3003. }
  3004. /**
  3005. * If your application uses different HTTP origins to make API calls (via `HttpClient`) on the server and
  3006. * on the client, the `HTTP_TRANSFER_CACHE_ORIGIN_MAP` token allows you to establish a mapping
  3007. * between those origins, so that `HttpTransferCache` feature can recognize those requests as the same
  3008. * ones and reuse the data cached on the server during hydration on the client.
  3009. *
  3010. * **Important note**: the `HTTP_TRANSFER_CACHE_ORIGIN_MAP` token should *only* be provided in
  3011. * the *server* code of your application (typically in the `app.server.config.ts` script). Angular throws an
  3012. * error if it detects that the token is defined while running on the client.
  3013. *
  3014. * @usageNotes
  3015. *
  3016. * When the same API endpoint is accessed via `http://internal-domain.com:8080` on the server and
  3017. * via `https://external-domain.com` on the client, you can use the following configuration:
  3018. * ```ts
  3019. * // in app.server.config.ts
  3020. * {
  3021. * provide: HTTP_TRANSFER_CACHE_ORIGIN_MAP,
  3022. * useValue: {
  3023. * 'http://internal-domain.com:8080': 'https://external-domain.com'
  3024. * }
  3025. * }
  3026. * ```
  3027. *
  3028. * @publicApi
  3029. */
  3030. const HTTP_TRANSFER_CACHE_ORIGIN_MAP = new InjectionToken(ngDevMode ? 'HTTP_TRANSFER_CACHE_ORIGIN_MAP' : '');
  3031. /**
  3032. * Keys within cached response data structure.
  3033. */
  3034. const BODY = 'b';
  3035. const HEADERS = 'h';
  3036. const STATUS = 's';
  3037. const STATUS_TEXT = 'st';
  3038. const REQ_URL = 'u';
  3039. const RESPONSE_TYPE = 'rt';
  3040. const CACHE_OPTIONS = new InjectionToken(ngDevMode ? 'HTTP_TRANSFER_STATE_CACHE_OPTIONS' : '');
  3041. /**
  3042. * A list of allowed HTTP methods to cache.
  3043. */
  3044. const ALLOWED_METHODS = ['GET', 'HEAD'];
  3045. function transferCacheInterceptorFn(req, next) {
  3046. const { isCacheActive, ...globalOptions } = inject(CACHE_OPTIONS);
  3047. const { transferCache: requestOptions, method: requestMethod } = req;
  3048. // In the following situations we do not want to cache the request
  3049. if (!isCacheActive ||
  3050. requestOptions === false ||
  3051. // POST requests are allowed either globally or at request level
  3052. (requestMethod === 'POST' && !globalOptions.includePostRequests && !requestOptions) ||
  3053. (requestMethod !== 'POST' && !ALLOWED_METHODS.includes(requestMethod)) ||
  3054. // Do not cache request that require authorization when includeRequestsWithAuthHeaders is falsey
  3055. (!globalOptions.includeRequestsWithAuthHeaders && hasAuthHeaders(req)) ||
  3056. globalOptions.filter?.(req) === false) {
  3057. return next(req);
  3058. }
  3059. const transferState = inject(TransferState);
  3060. const originMap = inject(HTTP_TRANSFER_CACHE_ORIGIN_MAP, {
  3061. optional: true,
  3062. });
  3063. if (typeof ngServerMode !== 'undefined' && !ngServerMode && originMap) {
  3064. throw new _RuntimeError(2803 /* RuntimeErrorCode.HTTP_ORIGIN_MAP_USED_IN_CLIENT */, ngDevMode &&
  3065. 'Angular detected that the `HTTP_TRANSFER_CACHE_ORIGIN_MAP` token is configured and ' +
  3066. 'present in the client side code. Please ensure that this token is only provided in the ' +
  3067. 'server code of the application.');
  3068. }
  3069. const requestUrl = typeof ngServerMode !== 'undefined' && ngServerMode && originMap
  3070. ? mapRequestOriginUrl(req.url, originMap)
  3071. : req.url;
  3072. const storeKey = makeCacheKey(req, requestUrl);
  3073. const response = transferState.get(storeKey, null);
  3074. let headersToInclude = globalOptions.includeHeaders;
  3075. if (typeof requestOptions === 'object' && requestOptions.includeHeaders) {
  3076. // Request-specific config takes precedence over the global config.
  3077. headersToInclude = requestOptions.includeHeaders;
  3078. }
  3079. if (response) {
  3080. const { [BODY]: undecodedBody, [RESPONSE_TYPE]: responseType, [HEADERS]: httpHeaders, [STATUS]: status, [STATUS_TEXT]: statusText, [REQ_URL]: url, } = response;
  3081. // Request found in cache. Respond using it.
  3082. let body = undecodedBody;
  3083. switch (responseType) {
  3084. case 'arraybuffer':
  3085. body = new TextEncoder().encode(undecodedBody).buffer;
  3086. break;
  3087. case 'blob':
  3088. body = new Blob([undecodedBody]);
  3089. break;
  3090. }
  3091. // We want to warn users accessing a header provided from the cache
  3092. // That HttpTransferCache alters the headers
  3093. // The warning will be logged a single time by HttpHeaders instance
  3094. let headers = new HttpHeaders(httpHeaders);
  3095. if (typeof ngDevMode === 'undefined' || ngDevMode) {
  3096. // Append extra logic in dev mode to produce a warning when a header
  3097. // that was not transferred to the client is accessed in the code via `get`
  3098. // and `has` calls.
  3099. headers = appendMissingHeadersDetection(req.url, headers, headersToInclude ?? []);
  3100. }
  3101. return of(new HttpResponse({
  3102. body,
  3103. headers,
  3104. status,
  3105. statusText,
  3106. url,
  3107. }));
  3108. }
  3109. // Request not found in cache. Make the request and cache it if on the server.
  3110. return next(req).pipe(tap((event) => {
  3111. if (event instanceof HttpResponse && typeof ngServerMode !== 'undefined' && ngServerMode) {
  3112. transferState.set(storeKey, {
  3113. [BODY]: event.body,
  3114. [HEADERS]: getFilteredHeaders(event.headers, headersToInclude),
  3115. [STATUS]: event.status,
  3116. [STATUS_TEXT]: event.statusText,
  3117. [REQ_URL]: requestUrl,
  3118. [RESPONSE_TYPE]: req.responseType,
  3119. });
  3120. }
  3121. }));
  3122. }
  3123. /** @returns true when the requests contains autorization related headers. */
  3124. function hasAuthHeaders(req) {
  3125. return req.headers.has('authorization') || req.headers.has('proxy-authorization');
  3126. }
  3127. function getFilteredHeaders(headers, includeHeaders) {
  3128. if (!includeHeaders) {
  3129. return {};
  3130. }
  3131. const headersMap = {};
  3132. for (const key of includeHeaders) {
  3133. const values = headers.getAll(key);
  3134. if (values !== null) {
  3135. headersMap[key] = values;
  3136. }
  3137. }
  3138. return headersMap;
  3139. }
  3140. function sortAndConcatParams(params) {
  3141. return [...params.keys()]
  3142. .sort()
  3143. .map((k) => `${k}=${params.getAll(k)}`)
  3144. .join('&');
  3145. }
  3146. function makeCacheKey(request, mappedRequestUrl) {
  3147. // make the params encoded same as a url so it's easy to identify
  3148. const { params, method, responseType } = request;
  3149. const encodedParams = sortAndConcatParams(params);
  3150. let serializedBody = request.serializeBody();
  3151. if (serializedBody instanceof URLSearchParams) {
  3152. serializedBody = sortAndConcatParams(serializedBody);
  3153. }
  3154. else if (typeof serializedBody !== 'string') {
  3155. serializedBody = '';
  3156. }
  3157. const key = [method, responseType, mappedRequestUrl, serializedBody, encodedParams].join('|');
  3158. const hash = generateHash(key);
  3159. return makeStateKey(hash);
  3160. }
  3161. /**
  3162. * A method that returns a hash representation of a string using a variant of DJB2 hash
  3163. * algorithm.
  3164. *
  3165. * This is the same hashing logic that is used to generate component ids.
  3166. */
  3167. function generateHash(value) {
  3168. let hash = 0;
  3169. for (const char of value) {
  3170. hash = (Math.imul(31, hash) + char.charCodeAt(0)) << 0;
  3171. }
  3172. // Force positive number hash.
  3173. // 2147483647 = equivalent of Integer.MAX_VALUE.
  3174. hash += 2147483647 + 1;
  3175. return hash.toString();
  3176. }
  3177. /**
  3178. * Returns the DI providers needed to enable HTTP transfer cache.
  3179. *
  3180. * By default, when using server rendering, requests are performed twice: once on the server and
  3181. * other one on the browser.
  3182. *
  3183. * When these providers are added, requests performed on the server are cached and reused during the
  3184. * bootstrapping of the application in the browser thus avoiding duplicate requests and reducing
  3185. * load time.
  3186. *
  3187. */
  3188. function withHttpTransferCache(cacheOptions) {
  3189. return [
  3190. {
  3191. provide: CACHE_OPTIONS,
  3192. useFactory: () => {
  3193. _performanceMarkFeature('NgHttpTransferCache');
  3194. return { isCacheActive: true, ...cacheOptions };
  3195. },
  3196. },
  3197. {
  3198. provide: HTTP_ROOT_INTERCEPTOR_FNS,
  3199. useValue: transferCacheInterceptorFn,
  3200. multi: true,
  3201. },
  3202. {
  3203. provide: APP_BOOTSTRAP_LISTENER,
  3204. multi: true,
  3205. useFactory: () => {
  3206. const appRef = inject(ApplicationRef);
  3207. const cacheState = inject(CACHE_OPTIONS);
  3208. return () => {
  3209. appRef.whenStable().then(() => {
  3210. cacheState.isCacheActive = false;
  3211. });
  3212. };
  3213. },
  3214. },
  3215. ];
  3216. }
  3217. /**
  3218. * This function will add a proxy to an HttpHeader to intercept calls to get/has
  3219. * and log a warning if the header entry requested has been removed
  3220. */
  3221. function appendMissingHeadersDetection(url, headers, headersToInclude) {
  3222. const warningProduced = new Set();
  3223. return new Proxy(headers, {
  3224. get(target, prop) {
  3225. const value = Reflect.get(target, prop);
  3226. const methods = new Set(['get', 'has', 'getAll']);
  3227. if (typeof value !== 'function' || !methods.has(prop)) {
  3228. return value;
  3229. }
  3230. return (headerName) => {
  3231. // We log when the key has been removed and a warning hasn't been produced for the header
  3232. const key = (prop + ':' + headerName).toLowerCase(); // e.g. `get:cache-control`
  3233. if (!headersToInclude.includes(headerName) && !warningProduced.has(key)) {
  3234. warningProduced.add(key);
  3235. const truncatedUrl = _truncateMiddle(url);
  3236. // TODO: create Error guide for this warning
  3237. console.warn(_formatRuntimeError(2802 /* RuntimeErrorCode.HEADERS_ALTERED_BY_TRANSFER_CACHE */, `Angular detected that the \`${headerName}\` header is accessed, but the value of the header ` +
  3238. `was not transferred from the server to the client by the HttpTransferCache. ` +
  3239. `To include the value of the \`${headerName}\` header for the \`${truncatedUrl}\` request, ` +
  3240. `use the \`includeHeaders\` list. The \`includeHeaders\` can be defined either ` +
  3241. `on a request level by adding the \`transferCache\` parameter, or on an application ` +
  3242. `level by adding the \`httpCacheTransfer.includeHeaders\` argument to the ` +
  3243. `\`provideClientHydration()\` call. `));
  3244. }
  3245. // invoking the original method
  3246. return value.apply(target, [headerName]);
  3247. };
  3248. },
  3249. });
  3250. }
  3251. function mapRequestOriginUrl(url, originMap) {
  3252. const origin = new URL(url, 'resolve://').origin;
  3253. const mappedOrigin = originMap[origin];
  3254. if (!mappedOrigin) {
  3255. return url;
  3256. }
  3257. if (typeof ngDevMode === 'undefined' || ngDevMode) {
  3258. verifyMappedOrigin(mappedOrigin);
  3259. }
  3260. return url.replace(origin, mappedOrigin);
  3261. }
  3262. function verifyMappedOrigin(url) {
  3263. if (new URL(url, 'resolve://').pathname !== '/') {
  3264. throw new _RuntimeError(2804 /* RuntimeErrorCode.HTTP_ORIGIN_MAP_CONTAINS_PATH */, 'Angular detected a URL with a path segment in the value provided for the ' +
  3265. `\`HTTP_TRANSFER_CACHE_ORIGIN_MAP\` token: ${url}. The map should only contain origins ` +
  3266. 'without any other segments.');
  3267. }
  3268. }
  3269. export { FetchBackend, HTTP_INTERCEPTORS, HTTP_TRANSFER_CACHE_ORIGIN_MAP, HttpBackend, HttpClient, HttpClientJsonpModule, HttpClientModule, HttpClientXsrfModule, HttpContext, HttpContextToken, HttpErrorResponse, HttpEventType, HttpFeatureKind, HttpHandler, HttpHeaderResponse, HttpHeaders, HttpParams, HttpRequest, HttpResponse, HttpResponseBase, HttpStatusCode, HttpUrlEncodingCodec, HttpXhrBackend, HttpXsrfTokenExtractor, JsonpClientBackend, JsonpInterceptor, httpResource, provideHttpClient, withFetch, withInterceptors, withInterceptorsFromDi, withJsonpSupport, withNoXsrfProtection, withRequestsMadeViaParent, withXsrfConfiguration, HTTP_ROOT_INTERCEPTOR_FNS as ɵHTTP_ROOT_INTERCEPTOR_FNS, HttpInterceptorHandler as ɵHttpInterceptingHandler, HttpInterceptorHandler as ɵHttpInterceptorHandler, REQUESTS_CONTRIBUTE_TO_STABILITY as ɵREQUESTS_CONTRIBUTE_TO_STABILITY, withHttpTransferCache as ɵwithHttpTransferCache };
  3270. //# sourceMappingURL=http.mjs.map