fetch.js 13 KB

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