UNPKG

@copperjs/copper

Version:
142 lines 6.72 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.sessionManager = exports.SessionManager = void 0; const os = require("os"); const path = require("path"); const crypto = require("crypto"); const stream = require("stream"); const unzipper = require("unzipper"); const mkdirp = require("mkdirp"); const uuid = require("uuid"); const puppeteer = require("puppeteer-core"); const node_fetch_1 = require("node-fetch"); const chrome_launcher_1 = require("chrome-launcher"); const logger_1 = require("../logger"); const errors_1 = require("../common/errors"); const config_1 = require("./config"); const chromeOptionsPath = ['chromeOptions', 'goog:chromeOptions']; class SessionManager { constructor() { this.sessions = new Map(); this.extensions = new Map(); this.extensionsPending = new Map(); } serializeSession(id, session) { return Object.assign(Object.assign({}, session.wsInfo), { id, port: session.chrome.port, pid: session.chrome.pid, webSocketDebuggerUrl: undefined }); } getChromeOptions(desiredCapabilities) { return desiredCapabilities['goog:chromeOptions'] || desiredCapabilities.chromeOptions; } async saveExtensionLocally(extension, directory) { const data = Buffer.from(extension, 'base64'); const checksum = crypto.createHash('md5').update(data).digest('hex'); if (this.extensions.has(checksum)) { return this.extensions.get(checksum); } const promise = this.extensionsPending.get(checksum) || new Promise((resolve, reject) => { const file = path.join(directory, uuid.v4().toUpperCase()); const readStream = stream.Readable.from(data); logger_1.logger.info(`writing extension to ${file}`); readStream .pipe(unzipper.Extract({ path: file })) .on('error', (err) => reject(err)) .on('close', () => { this.extensions.set(checksum, file); this.extensionsPending.delete(checksum); resolve(file); }); }); this.extensionsPending.set(checksum, promise); return await promise; } async handleExtensions(desiredCapabilities, sessionId) { var _a; // https://github.com/chromium/chromium/blob/d7da0240cae77824d1eda25745c4022757499131/chrome/test/chromedriver/chrome_launcher.cc#L905 const chromeOptions = this.getChromeOptions(desiredCapabilities); if (!((_a = chromeOptions === null || chromeOptions === void 0 ? void 0 : chromeOptions.extensions) === null || _a === void 0 ? void 0 : _a.length)) { return; } const extDir = path.join(os.tmpdir(), sessionId); await mkdirp(extDir); chromeOptions.args = chromeOptions.args || []; const extensions = chromeOptions.extensions; await Promise.all(extensions.map((extension) => this.saveExtensionLocally(extension, extDir).then((file) => chromeOptions.args.push(`--load-extension=${file}`)))); } async handleChromeProfile(desiredCapabilities, sessionId) { // https://github.com/chromium/chromium/blob/d7da0240cae77824d1eda25745c4022757499131/chrome/test/chromedriver/chrome_launcher.cc#L1096 const profilePath = path.join(os.tmpdir(), 'puppeteer_dev_chrome_profile-'); await mkdirp(profilePath); } parseSessionRequest(args = {}) { var _a, _b, _c; const capabilities = ((_a = args.capabilities) === null || _a === void 0 ? void 0 : _a.alwaysMatch) || ((_c = (_b = args.capabilities) === null || _b === void 0 ? void 0 : _b.firstMatch) === null || _c === void 0 ? void 0 : _c.find(() => true)) || args.desiredCapabilities || { browserName: 'chrome' }; const chromeOptions = args.chromeOptions; return { chromeOptions, capabilities }; } async createSession(args = {}) { var _a; const id = uuid.v4().toUpperCase(); const { capabilities, chromeOptions } = this.parseSessionRequest(args); try { await this.handleExtensions(capabilities, id); const w3cArgs = [...(((_a = this.getChromeOptions(capabilities)) === null || _a === void 0 ? void 0 : _a.args) || [])]; const options = Object.assign({}, chromeOptions, w3cArgs.length ? { chromeFlags: w3cArgs, ignoreDefaultFlags: true } : {}); const chrome = await chrome_launcher_1.launch(options); const wsInfo = await node_fetch_1.default(`http://localhost:${chrome.port}/json/version`).then((res) => res.json()); const wsUrl = wsInfo.webSocketDebuggerUrl; const session = { chrome, wsInfo, wsUrl }; if (config_1.copperConfig.value.enableW3CProtocol) { const browser = await puppeteer.connect({ browserWSEndpoint: wsUrl }); const page = (await browser.pages())[0]; session.puppeteer = { browser, page }; } this.sessions.set(id, session); return this.serializeSession(id, session); } catch (err) { logger_1.logger.error({ err, id }, 'error creating a session'); throw new errors_1.CreateSessionError(err); } } async removeSession(id) { var _a, _b; this.getSession(id); // throw if no session try { const session = this.sessions.get(id); await ((_b = (_a = session.puppeteer) === null || _a === void 0 ? void 0 : _a.browser) === null || _b === void 0 ? void 0 : _b.disconnect()); await session.chrome.kill(); } catch (err) { logger_1.logger.error({ err, id }, 'error removing a session'); } finally { this.sessions.delete(id); } } getSession(id) { if (!this.sessions.has(id)) { throw new errors_1.SessionNotFound(id); } const session = this.sessions.get(id); return this.serializeSession(id, session); } getWebSocketUrl(id) { this.getSession(id); // throw if no session const session = this.sessions.get(id); return session.wsUrl; } getPuppeteer(id) { this.getSession(id); // throw if no session const session = this.sessions.get(id); return session.puppeteer; } listSessions() { return Array.from(this.sessions.entries()).map(([id, session]) => this.serializeSession(id, session)); } } exports.SessionManager = SessionManager; exports.sessionManager = new SessionManager(); //# sourceMappingURL=sessionManager.js.map