@copperjs/copper
Version:
A lightweight chromium grid
142 lines • 6.72 kB
JavaScript
;
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