FetchHttpClient.js 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. "use strict";
  2. // Licensed to the .NET Foundation under one or more agreements.
  3. // The .NET Foundation licenses this file to you under the MIT license.
  4. Object.defineProperty(exports, "__esModule", { value: true });
  5. exports.FetchHttpClient = void 0;
  6. const Errors_1 = require("./Errors");
  7. const HttpClient_1 = require("./HttpClient");
  8. const ILogger_1 = require("./ILogger");
  9. const Utils_1 = require("./Utils");
  10. class FetchHttpClient extends HttpClient_1.HttpClient {
  11. constructor(logger) {
  12. super();
  13. this._logger = logger;
  14. if (typeof fetch === "undefined") {
  15. // In order to ignore the dynamic require in webpack builds we need to do this magic
  16. // @ts-ignore: TS doesn't know about these names
  17. const requireFunc = typeof __webpack_require__ === "function" ? __non_webpack_require__ : require;
  18. // Cookies aren't automatically handled in Node so we need to add a CookieJar to preserve cookies across requests
  19. this._jar = new (requireFunc("tough-cookie")).CookieJar();
  20. this._fetchType = requireFunc("node-fetch");
  21. // node-fetch doesn't have a nice API for getting and setting cookies
  22. // fetch-cookie will wrap a fetch implementation with a default CookieJar or a provided one
  23. this._fetchType = requireFunc("fetch-cookie")(this._fetchType, this._jar);
  24. }
  25. else {
  26. this._fetchType = fetch.bind(Utils_1.getGlobalThis());
  27. }
  28. if (typeof AbortController === "undefined") {
  29. // In order to ignore the dynamic require in webpack builds we need to do this magic
  30. // @ts-ignore: TS doesn't know about these names
  31. const requireFunc = typeof __webpack_require__ === "function" ? __non_webpack_require__ : require;
  32. // Node needs EventListener methods on AbortController which our custom polyfill doesn't provide
  33. this._abortControllerType = requireFunc("abort-controller");
  34. }
  35. else {
  36. this._abortControllerType = AbortController;
  37. }
  38. }
  39. /** @inheritDoc */
  40. async send(request) {
  41. // Check that abort was not signaled before calling send
  42. if (request.abortSignal && request.abortSignal.aborted) {
  43. throw new Errors_1.AbortError();
  44. }
  45. if (!request.method) {
  46. throw new Error("No method defined.");
  47. }
  48. if (!request.url) {
  49. throw new Error("No url defined.");
  50. }
  51. const abortController = new this._abortControllerType();
  52. let error;
  53. // Hook our abortSignal into the abort controller
  54. if (request.abortSignal) {
  55. request.abortSignal.onabort = () => {
  56. abortController.abort();
  57. error = new Errors_1.AbortError();
  58. };
  59. }
  60. // If a timeout has been passed in, setup a timeout to call abort
  61. // Type needs to be any to fit window.setTimeout and NodeJS.setTimeout
  62. let timeoutId = null;
  63. if (request.timeout) {
  64. const msTimeout = request.timeout;
  65. timeoutId = setTimeout(() => {
  66. abortController.abort();
  67. this._logger.log(ILogger_1.LogLevel.Warning, `Timeout from HTTP request.`);
  68. error = new Errors_1.TimeoutError();
  69. }, msTimeout);
  70. }
  71. if (request.content === "") {
  72. request.content = undefined;
  73. }
  74. if (request.content) {
  75. // Explicitly setting the Content-Type header for React Native on Android platform.
  76. request.headers = request.headers || {};
  77. if (Utils_1.isArrayBuffer(request.content)) {
  78. request.headers["Content-Type"] = "application/octet-stream";
  79. }
  80. else {
  81. request.headers["Content-Type"] = "text/plain;charset=UTF-8";
  82. }
  83. }
  84. let response;
  85. try {
  86. response = await this._fetchType(request.url, {
  87. body: request.content,
  88. cache: "no-cache",
  89. credentials: request.withCredentials === true ? "include" : "same-origin",
  90. headers: {
  91. "X-Requested-With": "XMLHttpRequest",
  92. ...request.headers,
  93. },
  94. method: request.method,
  95. mode: "cors",
  96. redirect: "follow",
  97. signal: abortController.signal,
  98. });
  99. }
  100. catch (e) {
  101. if (error) {
  102. throw error;
  103. }
  104. this._logger.log(ILogger_1.LogLevel.Warning, `Error from HTTP request. ${e}.`);
  105. throw e;
  106. }
  107. finally {
  108. if (timeoutId) {
  109. clearTimeout(timeoutId);
  110. }
  111. if (request.abortSignal) {
  112. request.abortSignal.onabort = null;
  113. }
  114. }
  115. if (!response.ok) {
  116. const errorMessage = await deserializeContent(response, "text");
  117. throw new Errors_1.HttpError(errorMessage || response.statusText, response.status);
  118. }
  119. const content = deserializeContent(response, request.responseType);
  120. const payload = await content;
  121. return new HttpClient_1.HttpResponse(response.status, response.statusText, payload);
  122. }
  123. getCookieString(url) {
  124. let cookies = "";
  125. if (Utils_1.Platform.isNode && this._jar) {
  126. // @ts-ignore: unused variable
  127. this._jar.getCookies(url, (e, c) => cookies = c.join("; "));
  128. }
  129. return cookies;
  130. }
  131. }
  132. exports.FetchHttpClient = FetchHttpClient;
  133. function deserializeContent(response, responseType) {
  134. let content;
  135. switch (responseType) {
  136. case "arraybuffer":
  137. content = response.arrayBuffer();
  138. break;
  139. case "text":
  140. content = response.text();
  141. break;
  142. case "blob":
  143. case "document":
  144. case "json":
  145. throw new Error(`${responseType} is not supported.`);
  146. default:
  147. content = response.text();
  148. break;
  149. }
  150. return content;
  151. }
  152. //# sourceMappingURL=FetchHttpClient.js.map