UNPKG

rebrowser-playwright-core

Version:

A drop-in replacement for playwright-core patched with rebrowser-patches. It allows to pass modern automation detection tests.

391 lines (390 loc) 14.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.APIResponse = exports.APIRequestContext = exports.APIRequest = void 0; var _fs = _interopRequireDefault(require("fs")); var _path = _interopRequireDefault(require("path")); var util = _interopRequireWildcard(require("util")); var _utils = require("../utils"); var _fileUtils = require("../utils/fileUtils"); var _channelOwner = require("./channelOwner"); var _network = require("./network"); var _tracing = require("./tracing"); var _errors = require("./errors"); var _browserContext = require("./browserContext"); let _Symbol$asyncDispose, _Symbol$asyncDispose2, _util$inspect$custom; /** * Copyright (c) Microsoft Corporation. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); } function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } class APIRequest { constructor(playwright) { this._playwright = void 0; this._contexts = new Set(); // Instrumentation. this._defaultContextOptions = void 0; this._playwright = playwright; } async newContext(options = {}) { var _this$_defaultContext; options = { ...this._defaultContextOptions, ...options }; const storageState = typeof options.storageState === 'string' ? JSON.parse(await _fs.default.promises.readFile(options.storageState, 'utf8')) : options.storageState; // We do not expose tracesDir in the API, so do not allow options to accidentally override it. const tracesDir = (_this$_defaultContext = this._defaultContextOptions) === null || _this$_defaultContext === void 0 ? void 0 : _this$_defaultContext.tracesDir; const context = APIRequestContext.from((await this._playwright._channel.newRequest({ ...options, extraHTTPHeaders: options.extraHTTPHeaders ? (0, _utils.headersObjectToArray)(options.extraHTTPHeaders) : undefined, storageState, tracesDir, clientCertificates: await (0, _browserContext.toClientCertificatesProtocol)(options.clientCertificates) })).request); this._contexts.add(context); context._request = this; context._tracing._tracesDir = tracesDir; await context._instrumentation.runAfterCreateRequestContext(context); return context; } } exports.APIRequest = APIRequest; _Symbol$asyncDispose = Symbol.asyncDispose; class APIRequestContext extends _channelOwner.ChannelOwner { static from(channel) { return channel._object; } constructor(parent, type, guid, initializer) { super(parent, type, guid, initializer); this._request = void 0; this._tracing = void 0; this._closeReason = void 0; this._tracing = _tracing.Tracing.from(initializer.tracing); } async [_Symbol$asyncDispose]() { await this.dispose(); } async dispose(options = {}) { var _this$_request; this._closeReason = options.reason; await this._instrumentation.runBeforeCloseRequestContext(this); try { await this._channel.dispose(options); } catch (e) { if ((0, _errors.isTargetClosedError)(e)) return; throw e; } this._tracing._resetStackCounter(); (_this$_request = this._request) === null || _this$_request === void 0 || _this$_request._contexts.delete(this); } async delete(url, options) { return await this.fetch(url, { ...options, method: 'DELETE' }); } async head(url, options) { return await this.fetch(url, { ...options, method: 'HEAD' }); } async get(url, options) { return await this.fetch(url, { ...options, method: 'GET' }); } async patch(url, options) { return await this.fetch(url, { ...options, method: 'PATCH' }); } async post(url, options) { return await this.fetch(url, { ...options, method: 'POST' }); } async put(url, options) { return await this.fetch(url, { ...options, method: 'PUT' }); } async fetch(urlOrRequest, options = {}) { const url = (0, _utils.isString)(urlOrRequest) ? urlOrRequest : undefined; const request = (0, _utils.isString)(urlOrRequest) ? undefined : urlOrRequest; return await this._innerFetch({ url, request, ...options }); } async _innerFetch(options = {}) { return await this._wrapApiCall(async () => { var _options$request, _options$request2, _options$request3; if (this._closeReason) throw new _errors.TargetClosedError(this._closeReason); (0, _utils.assert)(options.request || typeof options.url === 'string', 'First argument must be either URL string or Request'); (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`); (0, _utils.assert)(options.maxRedirects === undefined || options.maxRedirects >= 0, `'maxRedirects' must be greater than or equal to '0'`); (0, _utils.assert)(options.maxRetries === undefined || options.maxRetries >= 0, `'maxRetries' must be greater than or equal to '0'`); const url = options.url !== undefined ? options.url : options.request.url(); const method = options.method || ((_options$request = options.request) === null || _options$request === void 0 ? void 0 : _options$request.method()); let encodedParams = undefined; if (typeof options.params === 'string') encodedParams = options.params;else if (options.params instanceof URLSearchParams) encodedParams = options.params.toString(); // Cannot call allHeaders() here as the request may be paused inside route handler. const headersObj = options.headers || ((_options$request2 = options.request) === null || _options$request2 === void 0 ? void 0 : _options$request2.headers()); const headers = headersObj ? (0, _utils.headersObjectToArray)(headersObj) : undefined; let jsonData; let formData; let multipartData; let postDataBuffer; if (options.data !== undefined) { if ((0, _utils.isString)(options.data)) { if (isJsonContentType(headers)) jsonData = isJsonParsable(options.data) ? options.data : JSON.stringify(options.data);else postDataBuffer = Buffer.from(options.data, 'utf8'); } else if (Buffer.isBuffer(options.data)) { postDataBuffer = options.data; } else if (typeof options.data === 'object' || typeof options.data === 'number' || typeof options.data === 'boolean') { jsonData = JSON.stringify(options.data); } else { throw new Error(`Unexpected 'data' type`); } } else if (options.form) { if (globalThis.FormData && options.form instanceof FormData) { formData = []; for (const [name, value] of options.form.entries()) { if (typeof value !== 'string') throw new Error(`Expected string for options.form["${name}"], found File. Please use options.multipart instead.`); formData.push({ name, value }); } } else { formData = objectToArray(options.form); } } else if (options.multipart) { multipartData = []; if (globalThis.FormData && options.multipart instanceof FormData) { const form = options.multipart; for (const [name, value] of form.entries()) { if ((0, _utils.isString)(value)) { multipartData.push({ name, value }); } else { const file = { name: value.name, mimeType: value.type, buffer: Buffer.from(await value.arrayBuffer()) }; multipartData.push({ name, file }); } } } else { // Convert file-like values to ServerFilePayload structs. for (const [name, value] of Object.entries(options.multipart)) multipartData.push(await toFormField(name, value)); } } 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; const fixtures = { __testHookLookup: options.__testHookLookup }; const result = await this._channel.fetch({ url, params: typeof options.params === 'object' ? objectToArray(options.params) : undefined, encodedParams, method, headers, postData: postDataBuffer, jsonData, formData, multipartData, timeout: options.timeout, failOnStatusCode: options.failOnStatusCode, ignoreHTTPSErrors: options.ignoreHTTPSErrors, maxRedirects: options.maxRedirects, maxRetries: options.maxRetries, ...fixtures }); return new APIResponse(this, result.response); }); } async storageState(options = {}) { const state = await this._channel.storageState(); if (options.path) { await (0, _fileUtils.mkdirIfNeeded)(options.path); await _fs.default.promises.writeFile(options.path, JSON.stringify(state, undefined, 2), 'utf8'); } return state; } } exports.APIRequestContext = APIRequestContext; async function toFormField(name, value) { if (isFilePayload(value)) { const payload = value; if (!Buffer.isBuffer(payload.buffer)) throw new Error(`Unexpected buffer type of 'data.${name}'`); return { name, file: filePayloadToJson(payload) }; } else if (value instanceof _fs.default.ReadStream) { return { name, file: await readStreamToJson(value) }; } else { return { name, value: String(value) }; } } function isJsonParsable(value) { if (typeof value !== 'string') return false; try { JSON.parse(value); return true; } catch (e) { if (e instanceof SyntaxError) return false;else throw e; } } _Symbol$asyncDispose2 = Symbol.asyncDispose; _util$inspect$custom = util.inspect.custom; class APIResponse { constructor(context, initializer) { this._initializer = void 0; this._headers = void 0; this._request = void 0; this._request = context; this._initializer = initializer; this._headers = new _network.RawHeaders(this._initializer.headers); } ok() { return this._initializer.status >= 200 && this._initializer.status <= 299; } url() { return this._initializer.url; } status() { return this._initializer.status; } statusText() { return this._initializer.statusText; } headers() { return this._headers.headers(); } headersArray() { return this._headers.headersArray(); } async body() { try { const result = await this._request._channel.fetchResponseBody({ fetchUid: this._fetchUid() }); if (result.binary === undefined) throw new Error('Response has been disposed'); return result.binary; } catch (e) { if ((0, _errors.isTargetClosedError)(e)) throw new Error('Response has been disposed'); throw e; } } async text() { const content = await this.body(); return content.toString('utf8'); } async json() { const content = await this.text(); return JSON.parse(content); } async [_Symbol$asyncDispose2]() { await this.dispose(); } async dispose() { await this._request._channel.disposeAPIResponse({ fetchUid: this._fetchUid() }); } [_util$inspect$custom]() { const headers = this.headersArray().map(({ name, value }) => ` ${name}: ${value}`); return `APIResponse: ${this.status()} ${this.statusText()}\n${headers.join('\n')}`; } _fetchUid() { return this._initializer.fetchUid; } async _fetchLog() { const { log } = await this._request._channel.fetchLog({ fetchUid: this._fetchUid() }); return log; } } exports.APIResponse = APIResponse; function filePayloadToJson(payload) { return { name: payload.name, mimeType: payload.mimeType, buffer: payload.buffer }; } async function readStreamToJson(stream) { const buffer = await new Promise((resolve, reject) => { const chunks = []; stream.on('data', chunk => chunks.push(chunk)); stream.on('end', () => resolve(Buffer.concat(chunks))); stream.on('error', err => reject(err)); }); const streamPath = Buffer.isBuffer(stream.path) ? stream.path.toString('utf8') : stream.path; return { name: _path.default.basename(streamPath), buffer }; } function isJsonContentType(headers) { if (!headers) return false; for (const { name, value } of headers) { if (name.toLocaleLowerCase() === 'content-type') return value === 'application/json'; } return false; } function objectToArray(map) { if (!map) return undefined; const result = []; for (const [name, value] of Object.entries(map)) result.push({ name, value: String(value) }); return result; } function isFilePayload(value) { return typeof value === 'object' && value['name'] && value['mimeType'] && value['buffer']; }