@copperjs/copper
Version:
A lightweight chromium grid
155 lines • 5.46 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.grid = exports.Grid = exports.Node = void 0;
const node_fetch_1 = require("node-fetch");
const errors_1 = require("../common/errors");
const utils_1 = require("../common/utils");
const logger_1 = require("../logger");
const config_1 = require("../standalone/config");
class Node {
constructor(config) {
var _a;
this.config = config;
this.sessions = new Set();
this.shouldCheckIsAlive = true;
this.isAlive = false;
this.config.urlPrefix = config_1.copperConfig.value.routesPrefix;
this.config.maxSession = (_a = this.config.maxSession) !== null && _a !== void 0 ? _a : Number.POSITIVE_INFINITY;
this.config.nodePolling = this.config.nodePolling || 10000;
if (this.config.maxSession < 1) {
this.config.maxSession = Number.POSITIVE_INFINITY;
}
this.checkIsAlive();
}
static getId(config) {
return `${config.host}:${config.port}`;
}
get id() {
return Node.getId(this.config);
}
get URL() {
return `http://${this.config.host}:${this.config.port}`;
}
get webSocketURL() {
return `ws://${this.config.host}:${this.config.port}`;
}
get urlPrefix() {
return this.config.urlPrefix;
}
get freeSlots() {
return this.config.maxSession - this.sessions.size;
}
get canCreateSession() {
return this.freeSlots > 0 && this.isAlive;
}
getSessions() {
return Array.from(this.sessions);
}
registerSession(id) {
this.sessions.add(id);
}
deregisterSession(id) {
this.sessions.delete(id);
}
deregister() {
this.isAlive = false;
this.shouldCheckIsAlive = false;
}
async checkIsAlive() {
if (!this.shouldCheckIsAlive) {
return;
}
try {
await node_fetch_1.default(`${this.URL}${this.config.urlPrefix}status`, { timeout: 1000 });
if (!this.isAlive) {
logger_1.logger.warn(`node ${this.id} connected`);
}
this.isAlive = true;
}
catch (err) {
if (this.isAlive) {
logger_1.logger.warn(`node ${this.id} disconnected`);
}
this.isAlive = false;
}
await utils_1.delay(this.config.nodePolling);
process.nextTick(() => this.checkIsAlive());
}
}
exports.Node = Node;
class Grid {
constructor() {
this.nodes = new Map();
this.sessionNodeMap = new Map();
this.sessions = new Map();
}
registerNode(config) {
const node = new Node(config);
this.nodes.set(node.id, node);
logger_1.logger.info(`registered node ${node.id}`);
return node;
}
deregisterNode(host, port) {
const nodeId = Node.getId({ host, port });
if (!this.nodes.has(nodeId)) {
logger_1.logger.error(`failed deregistering node ${nodeId}`);
throw new errors_1.NoMatchingNode(`node ${nodeId} not registered`);
}
const node = this.nodes.get(nodeId);
this.nodes.delete(nodeId);
node.deregister();
node.getSessions().map((sessionId) => this._removeSession(sessionId));
logger_1.logger.info(`deregistered node ${node.id}`);
}
async createSession(body = {}) {
const candidates = Array.from(this.nodes.values()).filter((node) => node.canCreateSession);
const node = candidates.length
? candidates.reduce((prev, curr) => (curr.freeSlots > prev.freeSlots ? curr : prev))
: null;
if (!node) {
throw new errors_1.NoMatchingNode('cannot find a free node to create a session on');
}
const session = await node_fetch_1.default(`${node.URL}${node.urlPrefix}session`, {
method: 'POST',
body: JSON.stringify(body),
headers: { 'Content-Type': 'application/json' },
}).then((res) => res.json());
node.registerSession(session.sessionId);
this.sessionNodeMap.set(session.sessionId, node.id);
this.sessions.set(session.sessionId, utils_1.removeWsUrl(session.value));
return session.value;
}
getWebSocketUrl(sessionId) {
return `${this.getNode(sessionId).webSocketURL}/ws/${sessionId}`;
}
listSessions() {
return Array.from(this.sessions.values());
}
getSession(id) {
if (!this.sessions.has(id)) {
throw new errors_1.SessionNotFound(id);
}
return this.sessions.get(id);
}
getNode(sessionId) {
const nodeId = this.sessionNodeMap.get(this.getSession(sessionId).id);
return this.nodes.get(nodeId);
}
_removeSession(sessionId) {
this.getSession(sessionId); // throw if no session
this.sessionNodeMap.delete(sessionId);
this.sessions.delete(sessionId);
}
async removeSession(sessionId) {
const node = this.getNode(sessionId);
node.deregisterSession(sessionId);
this._removeSession(sessionId);
return await node_fetch_1.default(`${node.URL}${node.urlPrefix}session/${sessionId}`, {
method: 'DELETE',
headers: { 'Content-Type': 'application/json' },
}).then((res) => res.json());
}
}
exports.Grid = Grid;
exports.grid = new Grid();
//# sourceMappingURL=grid.js.map