fetch.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368
  1. "use strict";
  2. var __defProp = Object.defineProperty;
  3. var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
  4. var __getOwnPropNames = Object.getOwnPropertyNames;
  5. var __hasOwnProp = Object.prototype.hasOwnProperty;
  6. var __export = (target, all) => {
  7. for (var name in all)
  8. __defProp(target, name, { get: all[name], enumerable: true });
  9. };
  10. var __copyProps = (to, from, except, desc) => {
  11. if (from && typeof from === "object" || typeof from === "function") {
  12. for (let key of __getOwnPropNames(from))
  13. if (!__hasOwnProp.call(to, key) && key !== except)
  14. __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
  15. }
  16. return to;
  17. };
  18. var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
  19. var fetch_exports = {};
  20. __export(fetch_exports, {
  21. APIRequest: () => APIRequest,
  22. APIRequestContext: () => APIRequestContext,
  23. APIResponse: () => APIResponse
  24. });
  25. module.exports = __toCommonJS(fetch_exports);
  26. var import_browserContext = require("./browserContext");
  27. var import_channelOwner = require("./channelOwner");
  28. var import_errors = require("./errors");
  29. var import_network = require("./network");
  30. var import_tracing = require("./tracing");
  31. var import_assert = require("../utils/isomorphic/assert");
  32. var import_fileUtils = require("./fileUtils");
  33. var import_headers = require("../utils/isomorphic/headers");
  34. var import_rtti = require("../utils/isomorphic/rtti");
  35. var import_timeoutSettings = require("./timeoutSettings");
  36. class APIRequest {
  37. constructor(playwright) {
  38. this._contexts = /* @__PURE__ */ new Set();
  39. this._playwright = playwright;
  40. }
  41. async newContext(options = {}) {
  42. options = { ...options };
  43. await this._playwright._instrumentation.runBeforeCreateRequestContext(options);
  44. const storageState = typeof options.storageState === "string" ? JSON.parse(await this._playwright._platform.fs().promises.readFile(options.storageState, "utf8")) : options.storageState;
  45. const context = APIRequestContext.from((await this._playwright._channel.newRequest({
  46. ...options,
  47. extraHTTPHeaders: options.extraHTTPHeaders ? (0, import_headers.headersObjectToArray)(options.extraHTTPHeaders) : void 0,
  48. storageState,
  49. tracesDir: this._playwright._defaultLaunchOptions?.tracesDir,
  50. // We do not expose tracesDir in the API, so do not allow options to accidentally override it.
  51. clientCertificates: await (0, import_browserContext.toClientCertificatesProtocol)(this._playwright._platform, options.clientCertificates)
  52. })).request);
  53. this._contexts.add(context);
  54. context._request = this;
  55. context._timeoutSettings.setDefaultTimeout(options.timeout ?? this._playwright._defaultContextTimeout);
  56. context._tracing._tracesDir = this._playwright._defaultLaunchOptions?.tracesDir;
  57. await context._instrumentation.runAfterCreateRequestContext(context);
  58. return context;
  59. }
  60. }
  61. class APIRequestContext extends import_channelOwner.ChannelOwner {
  62. static from(channel) {
  63. return channel._object;
  64. }
  65. constructor(parent, type, guid, initializer) {
  66. super(parent, type, guid, initializer);
  67. this._tracing = import_tracing.Tracing.from(initializer.tracing);
  68. this._timeoutSettings = new import_timeoutSettings.TimeoutSettings(this._platform);
  69. }
  70. async [Symbol.asyncDispose]() {
  71. await this.dispose();
  72. }
  73. async dispose(options = {}) {
  74. this._closeReason = options.reason;
  75. await this._instrumentation.runBeforeCloseRequestContext(this);
  76. try {
  77. await this._channel.dispose(options);
  78. } catch (e) {
  79. if ((0, import_errors.isTargetClosedError)(e))
  80. return;
  81. throw e;
  82. }
  83. this._tracing._resetStackCounter();
  84. this._request?._contexts.delete(this);
  85. }
  86. async delete(url, options) {
  87. return await this.fetch(url, {
  88. ...options,
  89. method: "DELETE"
  90. });
  91. }
  92. async head(url, options) {
  93. return await this.fetch(url, {
  94. ...options,
  95. method: "HEAD"
  96. });
  97. }
  98. async get(url, options) {
  99. return await this.fetch(url, {
  100. ...options,
  101. method: "GET"
  102. });
  103. }
  104. async patch(url, options) {
  105. return await this.fetch(url, {
  106. ...options,
  107. method: "PATCH"
  108. });
  109. }
  110. async post(url, options) {
  111. return await this.fetch(url, {
  112. ...options,
  113. method: "POST"
  114. });
  115. }
  116. async put(url, options) {
  117. return await this.fetch(url, {
  118. ...options,
  119. method: "PUT"
  120. });
  121. }
  122. async fetch(urlOrRequest, options = {}) {
  123. const url = (0, import_rtti.isString)(urlOrRequest) ? urlOrRequest : void 0;
  124. const request = (0, import_rtti.isString)(urlOrRequest) ? void 0 : urlOrRequest;
  125. return await this._innerFetch({ url, request, ...options });
  126. }
  127. async _innerFetch(options = {}) {
  128. return await this._wrapApiCall(async () => {
  129. if (this._closeReason)
  130. throw new import_errors.TargetClosedError(this._closeReason);
  131. (0, import_assert.assert)(options.request || typeof options.url === "string", "First argument must be either URL string or Request");
  132. (0, import_assert.assert)((options.data === void 0 ? 0 : 1) + (options.form === void 0 ? 0 : 1) + (options.multipart === void 0 ? 0 : 1) <= 1, `Only one of 'data', 'form' or 'multipart' can be specified`);
  133. (0, import_assert.assert)(options.maxRedirects === void 0 || options.maxRedirects >= 0, `'maxRedirects' must be greater than or equal to '0'`);
  134. (0, import_assert.assert)(options.maxRetries === void 0 || options.maxRetries >= 0, `'maxRetries' must be greater than or equal to '0'`);
  135. const url = options.url !== void 0 ? options.url : options.request.url();
  136. this._checkUrlAllowed?.(url);
  137. const method = options.method || options.request?.method();
  138. let encodedParams = void 0;
  139. if (typeof options.params === "string")
  140. encodedParams = options.params;
  141. else if (options.params instanceof URLSearchParams)
  142. encodedParams = options.params.toString();
  143. const headersObj = options.headers || options.request?.headers();
  144. const headers = headersObj ? (0, import_headers.headersObjectToArray)(headersObj) : void 0;
  145. let jsonData;
  146. let formData;
  147. let multipartData;
  148. let postDataBuffer;
  149. if (options.data !== void 0) {
  150. if ((0, import_rtti.isString)(options.data)) {
  151. if (isJsonContentType(headers))
  152. jsonData = isJsonParsable(options.data) ? options.data : JSON.stringify(options.data);
  153. else
  154. postDataBuffer = Buffer.from(options.data, "utf8");
  155. } else if (Buffer.isBuffer(options.data)) {
  156. postDataBuffer = options.data;
  157. } else if (typeof options.data === "object" || typeof options.data === "number" || typeof options.data === "boolean") {
  158. jsonData = JSON.stringify(options.data);
  159. } else {
  160. throw new Error(`Unexpected 'data' type`);
  161. }
  162. } else if (options.form) {
  163. if (globalThis.FormData && options.form instanceof FormData) {
  164. formData = [];
  165. for (const [name, value] of options.form.entries()) {
  166. if (typeof value !== "string")
  167. throw new Error(`Expected string for options.form["${name}"], found File. Please use options.multipart instead.`);
  168. formData.push({ name, value });
  169. }
  170. } else {
  171. formData = objectToArray(options.form);
  172. }
  173. } else if (options.multipart) {
  174. multipartData = [];
  175. if (globalThis.FormData && options.multipart instanceof FormData) {
  176. const form = options.multipart;
  177. for (const [name, value] of form.entries()) {
  178. if ((0, import_rtti.isString)(value)) {
  179. multipartData.push({ name, value });
  180. } else {
  181. const file = {
  182. name: value.name,
  183. mimeType: value.type,
  184. buffer: Buffer.from(await value.arrayBuffer())
  185. };
  186. multipartData.push({ name, file });
  187. }
  188. }
  189. } else {
  190. for (const [name, value] of Object.entries(options.multipart))
  191. multipartData.push(await toFormField(this._platform, name, value));
  192. }
  193. }
  194. if (postDataBuffer === void 0 && jsonData === void 0 && formData === void 0 && multipartData === void 0)
  195. postDataBuffer = options.request?.postDataBuffer() || void 0;
  196. const fixtures = {
  197. __testHookLookup: options.__testHookLookup
  198. };
  199. const result = await this._channel.fetch({
  200. url,
  201. params: typeof options.params === "object" ? objectToArray(options.params) : void 0,
  202. encodedParams,
  203. method,
  204. headers,
  205. postData: postDataBuffer,
  206. jsonData,
  207. formData,
  208. multipartData,
  209. timeout: this._timeoutSettings.timeout(options),
  210. failOnStatusCode: options.failOnStatusCode,
  211. ignoreHTTPSErrors: options.ignoreHTTPSErrors,
  212. maxRedirects: options.maxRedirects,
  213. maxRetries: options.maxRetries,
  214. ...fixtures
  215. });
  216. return new APIResponse(this, result.response);
  217. });
  218. }
  219. async storageState(options = {}) {
  220. const state = await this._channel.storageState({ indexedDB: options.indexedDB });
  221. if (options.path) {
  222. await (0, import_fileUtils.mkdirIfNeeded)(this._platform, options.path);
  223. await this._platform.fs().promises.writeFile(options.path, JSON.stringify(state, void 0, 2), "utf8");
  224. }
  225. return state;
  226. }
  227. }
  228. async function toFormField(platform, name, value) {
  229. const typeOfValue = typeof value;
  230. if (isFilePayload(value)) {
  231. const payload = value;
  232. if (!Buffer.isBuffer(payload.buffer))
  233. throw new Error(`Unexpected buffer type of 'data.${name}'`);
  234. return { name, file: filePayloadToJson(payload) };
  235. } else if (typeOfValue === "string" || typeOfValue === "number" || typeOfValue === "boolean") {
  236. return { name, value: String(value) };
  237. } else {
  238. return { name, file: await readStreamToJson(platform, value) };
  239. }
  240. }
  241. function isJsonParsable(value) {
  242. if (typeof value !== "string")
  243. return false;
  244. try {
  245. JSON.parse(value);
  246. return true;
  247. } catch (e) {
  248. if (e instanceof SyntaxError)
  249. return false;
  250. else
  251. throw e;
  252. }
  253. }
  254. class APIResponse {
  255. constructor(context, initializer) {
  256. this._request = context;
  257. this._initializer = initializer;
  258. this._headers = new import_network.RawHeaders(this._initializer.headers);
  259. if (context._platform.inspectCustom)
  260. this[context._platform.inspectCustom] = () => this._inspect();
  261. }
  262. ok() {
  263. return this._initializer.status >= 200 && this._initializer.status <= 299;
  264. }
  265. url() {
  266. return this._initializer.url;
  267. }
  268. status() {
  269. return this._initializer.status;
  270. }
  271. statusText() {
  272. return this._initializer.statusText;
  273. }
  274. headers() {
  275. return this._headers.headers();
  276. }
  277. headersArray() {
  278. return this._headers.headersArray();
  279. }
  280. async body() {
  281. return await this._request._wrapApiCall(async () => {
  282. try {
  283. const result = await this._request._channel.fetchResponseBody({ fetchUid: this._fetchUid() });
  284. if (result.binary === void 0)
  285. throw new Error("Response has been disposed");
  286. return result.binary;
  287. } catch (e) {
  288. if ((0, import_errors.isTargetClosedError)(e))
  289. throw new Error("Response has been disposed");
  290. throw e;
  291. }
  292. }, { internal: true });
  293. }
  294. async text() {
  295. const content = await this.body();
  296. return content.toString("utf8");
  297. }
  298. async json() {
  299. const content = await this.text();
  300. return JSON.parse(content);
  301. }
  302. async [Symbol.asyncDispose]() {
  303. await this.dispose();
  304. }
  305. async dispose() {
  306. await this._request._channel.disposeAPIResponse({ fetchUid: this._fetchUid() });
  307. }
  308. _inspect() {
  309. const headers = this.headersArray().map(({ name, value }) => ` ${name}: ${value}`);
  310. return `APIResponse: ${this.status()} ${this.statusText()}
  311. ${headers.join("\n")}`;
  312. }
  313. _fetchUid() {
  314. return this._initializer.fetchUid;
  315. }
  316. async _fetchLog() {
  317. const { log } = await this._request._channel.fetchLog({ fetchUid: this._fetchUid() });
  318. return log;
  319. }
  320. }
  321. function filePayloadToJson(payload) {
  322. return {
  323. name: payload.name,
  324. mimeType: payload.mimeType,
  325. buffer: payload.buffer
  326. };
  327. }
  328. async function readStreamToJson(platform, stream) {
  329. const buffer = await new Promise((resolve, reject) => {
  330. const chunks = [];
  331. stream.on("data", (chunk) => chunks.push(chunk));
  332. stream.on("end", () => resolve(Buffer.concat(chunks)));
  333. stream.on("error", (err) => reject(err));
  334. });
  335. const streamPath = Buffer.isBuffer(stream.path) ? stream.path.toString("utf8") : stream.path;
  336. return {
  337. name: platform.path().basename(streamPath),
  338. buffer
  339. };
  340. }
  341. function isJsonContentType(headers) {
  342. if (!headers)
  343. return false;
  344. for (const { name, value } of headers) {
  345. if (name.toLocaleLowerCase() === "content-type")
  346. return value === "application/json";
  347. }
  348. return false;
  349. }
  350. function objectToArray(map) {
  351. if (!map)
  352. return void 0;
  353. const result = [];
  354. for (const [name, value] of Object.entries(map)) {
  355. if (value !== void 0)
  356. result.push({ name, value: String(value) });
  357. }
  358. return result;
  359. }
  360. function isFilePayload(value) {
  361. return typeof value === "object" && value["name"] && value["mimeType"] && value["buffer"];
  362. }
  363. // Annotate the CommonJS export names for ESM import in node:
  364. 0 && (module.exports = {
  365. APIRequest,
  366. APIRequestContext,
  367. APIResponse
  368. });