UNPKG

simple-headless-chrome

Version:

Headless Chrome abstraction to simplify the interaction with the browser. It may be used for crawling sites, test automation, etc

306 lines (237 loc) 10.1 kB
'use strict'; var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } var debug = require('debug')('HeadlessChrome:tab'); var EventEmitter = require('events'); var chrome = require('./chrome'); var setupHandlers = require('./setupHandlers'); var setupViewport = require('./setupViewport'); var setupActions = require('./setupActions'); var Tab = function (_EventEmitter) { _inherits(Tab, _EventEmitter); function Tab(host, port, tabOptions, browserInstance) { _classCallCheck(this, Tab); var _this = _possibleConstructorReturn(this, (Tab.__proto__ || Object.getPrototypeOf(Tab)).call(this)); EventEmitter.call(_this); _this._host = host; _this._port = port; _this._tabOptions = tabOptions; _this._browser = browserInstance; // Get the browser actions options _this.options = { browser: browserInstance.options.browser, deviceMetrics: browserInstance.options.deviceMetrics }; return _this; } _createClass(Tab, [{ key: 'init', value: function () { var _ref = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee() { var Target, params; return regeneratorRuntime.wrap(function _callee$(_context) { while (1) { switch (_context.prev = _context.next) { case 0: debug(`:: init => Initializating new tab...`); Target = this._browser.client.Target; /** * Creates new tab and assign it with CDP */ _context.prev = 2; params = { url: this._tabOptions.startingUrl /** * Creates private BrowserContext (if needed) * NOTE: private tabs can only be created in headless mode */ }; if (!this._tabOptions.privateTab) { _context.next = 10; break; } if (this._browser.options.headless) { _context.next = 7; break; } throw new Error(`Can't open a private target/tab if browser is not in headless mode`); case 7: _context.next = 9; return Target.createBrowserContext(); case 9: this._privateTab = params.browserContextId = _context.sent; case 10: _context.next = 12; return Target.createTarget(params); case 12: this._target = _context.sent; // Get the target/tab ID this._targetId = this._target.targetId; // Attach the tab to the CDP debug(`:: init => Attaching tab "${this._targetId}" to the CDP...`); _context.next = 17; return chrome.attachCdpToTarget(this._host, this._port, this._targetId); case 17: this.client = _context.sent; // Setup Actions, Handlers and Viewport debug(`:: init => Preparing Actions, Handlers and Viewport for tab "${this._targetId}"...`); _context.next = 21; return setupHandlers.call(this); case 21: _context.next = 23; return setupViewport.call(this); case 23: setupActions.call(this); debug(`:: init => Tab "${this._targetId}" initialized successfully!`); return _context.abrupt('return', this); case 28: _context.prev = 28; _context.t0 = _context['catch'](2); debug(`:: init => Could not initialize new target/tab. Error code: ${_context.t0.code}`, _context.t0); if (this._targetId) { Target.closeTarget(this._targetId); } if (this._privateTab) { Target.disposeBrowserContext(this._privateTab); } throw _context.t0; case 34: case 'end': return _context.stop(); } } }, _callee, this, [[2, 28]]); })); function init() { return _ref.apply(this, arguments); } return init; }() /** * Tells if the target/tab is initializated */ }, { key: 'isInitialized', value: function isInitialized() { return !!this.client; } /** * Tells if the target/tab is a private tab */ }, { key: 'isPrivate', value: function isPrivate() { return typeof this._privateTab !== 'undefined'; } /** * Tells the targe/tab ID */ }, { key: 'getId', value: function getId() { return this._targetId; } /** * Tells the private tab BrowserContext id */ }, { key: 'getBrowserContextId', value: function getBrowserContextId() { if (this._privateTab) { return this._privateTab.browserContextId; } } /** * Support attaching actions * @param {String} name - Method name * @param {Function} fn - Method implementation */ }, { key: 'addAction', value: function addAction(name, fn) { if (typeof fn === 'function') { this[name] = fn; } return this; } }, { key: 'close', value: function () { var _ref2 = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee2() { var Target, tabId, tabContextId; return regeneratorRuntime.wrap(function _callee2$(_context2) { while (1) { switch (_context2.prev = _context2.next) { case 0: debug(`:: close => Closing target/tab "${this._targetId}"...`); if (this.isInitialized()) { _context2.next = 3; break; } throw new Error(`Cannot close a tab that it's not initialized`); case 3: Target = this._browser.client.Target; tabId = this.getId(); _context2.prev = 5; _context2.next = 8; return Target.closeTarget({ targetId: tabId }); case 8: if (!this.isPrivate()) { _context2.next = 12; break; } tabContextId = this.getBrowserContextId(); _context2.next = 12; return Target.disposeBrowserContext({ browserContextId: tabContextId }); case 12: this._closedAt = new Date().toISOString(); this.client = false; // Tell the browser instance that a target/tab was closed this._browser._closeTarget(tabId); debug(`:: close => Target/tab "${this._targetId}" closed successfully!`); return _context2.abrupt('return', true); case 19: _context2.prev = 19; _context2.t0 = _context2['catch'](5); _context2.t0.message = `There was a problem closing target/tab. ${_context2.t0.message}`; _context2.t0.tabId = tabId; throw _context2.t0; case 24: case 'end': return _context2.stop(); } } }, _callee2, this, [[5, 19]]); })); function close() { return _ref2.apply(this, arguments); } return close; }() /** * Logs a text in the console * @param {string} text - The text to log */ }, { key: 'log', value: function log(text) { console.log(`[Tab ID: ${this._targetId}] ${text}`); return this; } /** * Logs a text in the console, only in debug mode * @param {string} text - The text to log */ }, { key: 'debugLog', value: function debugLog(text) { debug(`[Tab ID: ${this._targetId}] ${text}`); return this; } }]); return Tab; }(EventEmitter); module.exports = Tab;