Source

puppeteer/server.js

const SocketIoServer = require("socket.io");
const http = require("http");
const getPort = require("get-port");
const { paramsForUrl } = require("../utils");
const { Socket } = require("engine.io");

/**
 * This class is a socket.io server which will listen for browser connections
 * whith the atrica-extension plugin and will create a socket through which
 * the worker can communicate with the browser.
 */
class Server {
	/**
	 * Create a WorkerServer instance
	 * @param {number} port - The port on which the socket.io server should listen.
	 * 	If the port is busy, then will try port + 1, then port + 2, etc...
	 */
	constructor(port = 3000) {
		this.preferedPort = port;
		// Create an http server returning a simple html page
		this.httpServer = http.createServer(requestHandler);

		// Create the socket.io server and listen for connections
		/**
		 * @type {SocketIoServer} socket.io server
		 */
		this.server = SocketIoServer(this.httpServer);
		this._is_running = false;
	}

	/**
	 * Start the socket.io server
	 * @param {function} [listener] - Function executed for listening.
	 * 	Mostly usefull for printing messages when server start
	 * @returns {Promise}
	 */
	async run(listener) {
		if(this._is_running) return;
		// Try to launch the server on one of the following port or random port otherwise
		this.port = await getPort({
			port: [0, 1, 2, 3].map(v => v + this.preferedPort)
		});

		listener = listener || (() => {});
		this.httpServer.listen(this.port, listener);
		this._is_running = true;
	}

	/**
	 * @returns {number} - The port where the socket.io server is running
	 */
	getPort() {
		return this.port;
	}

	/**
	 * Returns a promise resolved when the browser with browserId connects to the server.
	 * The promise returns a socket toward the browser once resolved.
	 * @param {number} browserId - The ID of the browser to await
	 * @returns {Promise<Socket>} - Promise for the socket toward the browser
	 */
	browserConnection(browserId) {
		return new Promise((resolve, reject) => {
			const onBrowserConnection = browser => {
				const onBrowserReady = ({ bid }) => {
					if (bid == browserId) {
						this.server.removeListener("connect", onBrowserConnection);
						browser.removeListener("browser-ready", onBrowserReady);
						resolve(browser);
					} else {
						browser.removeListener("browser-ready", onBrowserReady);
					}
				};
				browser.on("browser-ready", onBrowserReady);
			};
			this.server.on("connect", onBrowserConnection);
		});
	}
}

/*
 * Handle the requests and return a simple html page containing the informations
 * necessary to bootstrap the atrica-extension and etablish connection
 */
function requestHandler(req, res) {
	let params = paramsForUrl(req.url);

	// USED BY ATRICA EXTENSION TO GET INFORMATIONS
	let title = `atrica::${params.port}::${params.bid}`;

	res.writeHead(200, { "Content-Type": "text/html" });
	res.write(
		`<html>
			<head>
				<title>${title}</title>
			</head>
			<body>${title}</body>
		</html>`
	);
	res.end();
}

module.exports = Server;