fetch.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", {
  3. value: true
  4. });
  5. exports.APIResponse = exports.APIRequestContext = exports.APIRequest = void 0;
  6. var _fs = _interopRequireDefault(require("fs"));
  7. var _path = _interopRequireDefault(require("path"));
  8. var util = _interopRequireWildcard(require("util"));
  9. var _utils = require("../utils");
  10. var _fileUtils = require("../utils/fileUtils");
  11. var _channelOwner = require("./channelOwner");
  12. var _network = require("./network");
  13. var _tracing = require("./tracing");
  14. var _errors = require("./errors");
  15. let _Symbol$asyncDispose, _Symbol$asyncDispose2, _util$inspect$custom;
  16. /**
  17. * Copyright (c) Microsoft Corporation.
  18. *
  19. * Licensed under the Apache License, Version 2.0 (the "License");
  20. * you may not use this file except in compliance with the License.
  21. * You may obtain a copy of the License at
  22. *
  23. * http://www.apache.org/licenses/LICENSE-2.0
  24. *
  25. * Unless required by applicable law or agreed to in writing, software
  26. * distributed under the License is distributed on an "AS IS" BASIS,
  27. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  28. * See the License for the specific language governing permissions and
  29. * limitations under the License.
  30. */
  31. function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
  32. function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
  33. function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  34. class APIRequest {
  35. constructor(playwright) {
  36. this._playwright = void 0;
  37. this._contexts = new Set();
  38. // Instrumentation.
  39. this._defaultContextOptions = void 0;
  40. this._playwright = playwright;
  41. }
  42. async newContext(options = {}) {
  43. var _this$_defaultContext;
  44. options = {
  45. ...this._defaultContextOptions,
  46. ...options
  47. };
  48. const storageState = typeof options.storageState === 'string' ? JSON.parse(await _fs.default.promises.readFile(options.storageState, 'utf8')) : options.storageState;
  49. // We do not expose tracesDir in the API, so do not allow options to accidentally override it.
  50. const tracesDir = (_this$_defaultContext = this._defaultContextOptions) === null || _this$_defaultContext === void 0 ? void 0 : _this$_defaultContext.tracesDir;
  51. const context = APIRequestContext.from((await this._playwright._channel.newRequest({
  52. ...options,
  53. extraHTTPHeaders: options.extraHTTPHeaders ? (0, _utils.headersObjectToArray)(options.extraHTTPHeaders) : undefined,
  54. storageState,
  55. tracesDir
  56. })).request);
  57. this._contexts.add(context);
  58. context._request = this;
  59. context._tracing._tracesDir = tracesDir;
  60. await context._instrumentation.onDidCreateRequestContext(context);
  61. return context;
  62. }
  63. }
  64. exports.APIRequest = APIRequest;
  65. _Symbol$asyncDispose = Symbol.asyncDispose;
  66. class APIRequestContext extends _channelOwner.ChannelOwner {
  67. static from(channel) {
  68. return channel._object;
  69. }
  70. constructor(parent, type, guid, initializer) {
  71. super(parent, type, guid, initializer);
  72. this._request = void 0;
  73. this._tracing = void 0;
  74. this._tracing = _tracing.Tracing.from(initializer.tracing);
  75. }
  76. async [_Symbol$asyncDispose]() {
  77. await this.dispose();
  78. }
  79. async dispose() {
  80. var _this$_request;
  81. await this._instrumentation.onWillCloseRequestContext(this);
  82. await this._channel.dispose();
  83. (_this$_request = this._request) === null || _this$_request === void 0 ? void 0 : _this$_request._contexts.delete(this);
  84. }
  85. async delete(url, options) {
  86. return await this.fetch(url, {
  87. ...options,
  88. method: 'DELETE'
  89. });
  90. }
  91. async head(url, options) {
  92. return await this.fetch(url, {
  93. ...options,
  94. method: 'HEAD'
  95. });
  96. }
  97. async get(url, options) {
  98. return await this.fetch(url, {
  99. ...options,
  100. method: 'GET'
  101. });
  102. }
  103. async patch(url, options) {
  104. return await this.fetch(url, {
  105. ...options,
  106. method: 'PATCH'
  107. });
  108. }
  109. async post(url, options) {
  110. return await this.fetch(url, {
  111. ...options,
  112. method: 'POST'
  113. });
  114. }
  115. async put(url, options) {
  116. return await this.fetch(url, {
  117. ...options,
  118. method: 'PUT'
  119. });
  120. }
  121. async fetch(urlOrRequest, options = {}) {
  122. const url = (0, _utils.isString)(urlOrRequest) ? urlOrRequest : undefined;
  123. const request = (0, _utils.isString)(urlOrRequest) ? undefined : urlOrRequest;
  124. return await this._innerFetch({
  125. url,
  126. request,
  127. ...options
  128. });
  129. }
  130. async _innerFetch(options = {}) {
  131. return await this._wrapApiCall(async () => {
  132. var _options$request, _options$request2, _options$request3;
  133. (0, _utils.assert)(options.request || typeof options.url === 'string', 'First argument must be either URL string or Request');
  134. (0, _utils.assert)((options.data === undefined ? 0 : 1) + (options.form === undefined ? 0 : 1) + (options.multipart === undefined ? 0 : 1) <= 1, `Only one of 'data', 'form' or 'multipart' can be specified`);
  135. (0, _utils.assert)(options.maxRedirects === undefined || options.maxRedirects >= 0, `'maxRedirects' should be greater than or equal to '0'`);
  136. const url = options.url !== undefined ? options.url : options.request.url();
  137. const params = objectToArray(options.params);
  138. const method = options.method || ((_options$request = options.request) === null || _options$request === void 0 ? void 0 : _options$request.method());
  139. const maxRedirects = options.maxRedirects;
  140. // Cannot call allHeaders() here as the request may be paused inside route handler.
  141. const headersObj = options.headers || ((_options$request2 = options.request) === null || _options$request2 === void 0 ? void 0 : _options$request2.headers());
  142. const headers = headersObj ? (0, _utils.headersObjectToArray)(headersObj) : undefined;
  143. let jsonData;
  144. let formData;
  145. let multipartData;
  146. let postDataBuffer;
  147. if (options.data !== undefined) {
  148. if ((0, _utils.isString)(options.data)) {
  149. if (isJsonContentType(headers)) jsonData = isJsonParsable(options.data) ? options.data : JSON.stringify(options.data);else postDataBuffer = Buffer.from(options.data, 'utf8');
  150. } else if (Buffer.isBuffer(options.data)) {
  151. postDataBuffer = options.data;
  152. } else if (typeof options.data === 'object' || typeof options.data === 'number' || typeof options.data === 'boolean') {
  153. jsonData = JSON.stringify(options.data);
  154. } else {
  155. throw new Error(`Unexpected 'data' type`);
  156. }
  157. } else if (options.form) {
  158. formData = objectToArray(options.form);
  159. } else if (options.multipart) {
  160. multipartData = [];
  161. // Convert file-like values to ServerFilePayload structs.
  162. for (const [name, value] of Object.entries(options.multipart)) {
  163. if (isFilePayload(value)) {
  164. const payload = value;
  165. if (!Buffer.isBuffer(payload.buffer)) throw new Error(`Unexpected buffer type of 'data.${name}'`);
  166. multipartData.push({
  167. name,
  168. file: filePayloadToJson(payload)
  169. });
  170. } else if (value instanceof _fs.default.ReadStream) {
  171. multipartData.push({
  172. name,
  173. file: await readStreamToJson(value)
  174. });
  175. } else {
  176. multipartData.push({
  177. name,
  178. value: String(value)
  179. });
  180. }
  181. }
  182. }
  183. if (postDataBuffer === undefined && jsonData === undefined && formData === undefined && multipartData === undefined) postDataBuffer = ((_options$request3 = options.request) === null || _options$request3 === void 0 ? void 0 : _options$request3.postDataBuffer()) || undefined;
  184. const fixtures = {
  185. __testHookLookup: options.__testHookLookup
  186. };
  187. const result = await this._channel.fetch({
  188. url,
  189. params,
  190. method,
  191. headers,
  192. postData: postDataBuffer,
  193. jsonData,
  194. formData,
  195. multipartData,
  196. timeout: options.timeout,
  197. failOnStatusCode: options.failOnStatusCode,
  198. ignoreHTTPSErrors: options.ignoreHTTPSErrors,
  199. maxRedirects: maxRedirects,
  200. ...fixtures
  201. });
  202. return new APIResponse(this, result.response);
  203. });
  204. }
  205. async storageState(options = {}) {
  206. const state = await this._channel.storageState();
  207. if (options.path) {
  208. await (0, _fileUtils.mkdirIfNeeded)(options.path);
  209. await _fs.default.promises.writeFile(options.path, JSON.stringify(state, undefined, 2), 'utf8');
  210. }
  211. return state;
  212. }
  213. }
  214. exports.APIRequestContext = APIRequestContext;
  215. function isJsonParsable(value) {
  216. if (typeof value !== 'string') return false;
  217. try {
  218. JSON.parse(value);
  219. return true;
  220. } catch (e) {
  221. if (e instanceof SyntaxError) return false;else throw e;
  222. }
  223. }
  224. _Symbol$asyncDispose2 = Symbol.asyncDispose;
  225. _util$inspect$custom = util.inspect.custom;
  226. class APIResponse {
  227. constructor(context, initializer) {
  228. this._initializer = void 0;
  229. this._headers = void 0;
  230. this._request = void 0;
  231. this._request = context;
  232. this._initializer = initializer;
  233. this._headers = new _network.RawHeaders(this._initializer.headers);
  234. }
  235. ok() {
  236. return this._initializer.status >= 200 && this._initializer.status <= 299;
  237. }
  238. url() {
  239. return this._initializer.url;
  240. }
  241. status() {
  242. return this._initializer.status;
  243. }
  244. statusText() {
  245. return this._initializer.statusText;
  246. }
  247. headers() {
  248. return this._headers.headers();
  249. }
  250. headersArray() {
  251. return this._headers.headersArray();
  252. }
  253. async body() {
  254. try {
  255. const result = await this._request._channel.fetchResponseBody({
  256. fetchUid: this._fetchUid()
  257. });
  258. if (result.binary === undefined) throw new Error('Response has been disposed');
  259. return result.binary;
  260. } catch (e) {
  261. if ((0, _errors.isTargetClosedError)(e)) throw new Error('Response has been disposed');
  262. throw e;
  263. }
  264. }
  265. async text() {
  266. const content = await this.body();
  267. return content.toString('utf8');
  268. }
  269. async json() {
  270. const content = await this.text();
  271. return JSON.parse(content);
  272. }
  273. async [_Symbol$asyncDispose2]() {
  274. await this.dispose();
  275. }
  276. async dispose() {
  277. await this._request._channel.disposeAPIResponse({
  278. fetchUid: this._fetchUid()
  279. });
  280. }
  281. [_util$inspect$custom]() {
  282. const headers = this.headersArray().map(({
  283. name,
  284. value
  285. }) => ` ${name}: ${value}`);
  286. return `APIResponse: ${this.status()} ${this.statusText()}\n${headers.join('\n')}`;
  287. }
  288. _fetchUid() {
  289. return this._initializer.fetchUid;
  290. }
  291. async _fetchLog() {
  292. const {
  293. log
  294. } = await this._request._channel.fetchLog({
  295. fetchUid: this._fetchUid()
  296. });
  297. return log;
  298. }
  299. }
  300. exports.APIResponse = APIResponse;
  301. function filePayloadToJson(payload) {
  302. return {
  303. name: payload.name,
  304. mimeType: payload.mimeType,
  305. buffer: payload.buffer
  306. };
  307. }
  308. async function readStreamToJson(stream) {
  309. const buffer = await new Promise((resolve, reject) => {
  310. const chunks = [];
  311. stream.on('data', chunk => chunks.push(chunk));
  312. stream.on('end', () => resolve(Buffer.concat(chunks)));
  313. stream.on('error', err => reject(err));
  314. });
  315. const streamPath = Buffer.isBuffer(stream.path) ? stream.path.toString('utf8') : stream.path;
  316. return {
  317. name: _path.default.basename(streamPath),
  318. buffer
  319. };
  320. }
  321. function isJsonContentType(headers) {
  322. if (!headers) return false;
  323. for (const {
  324. name,
  325. value
  326. } of headers) {
  327. if (name.toLocaleLowerCase() === 'content-type') return value === 'application/json';
  328. }
  329. return false;
  330. }
  331. function objectToArray(map) {
  332. if (!map) return undefined;
  333. const result = [];
  334. for (const [name, value] of Object.entries(map)) result.push({
  335. name,
  336. value: String(value)
  337. });
  338. return result;
  339. }
  340. function isFilePayload(value) {
  341. return typeof value === 'object' && value['name'] && value['mimeType'] && value['buffer'];
  342. }