UNPKG

@sectester/repeater

Version:

Package for managing repeaters, which are mandatory for scanning targets on a local network.

204 lines 9.38 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.DefaultRepeaterServer = exports.DefaultRepeaterServerOptions = void 0; const tslib_1 = require("tslib"); const RepeaterServer_1 = require("./RepeaterServer"); const core_1 = require("@sectester/core"); const tsyringe_1 = require("tsyringe"); const socket_io_client_1 = tslib_1.__importDefault(require("socket.io-client")); const socket_io_msgpack_parser_1 = tslib_1.__importDefault(require("socket.io-msgpack-parser")); const events_1 = require("events"); const os_1 = require("os"); exports.DefaultRepeaterServerOptions = Symbol('DefaultRepeaterServerOptions'); let DefaultRepeaterServer = class DefaultRepeaterServer { get socket() { if (!this._socket) { throw new Error('Please make sure that repeater established a connection with host.'); } return this._socket; } constructor(logger, options) { this.logger = logger; this.options = options; this.MAX_DEPLOYMENT_TIMEOUT = 60000; this.MAX_RECONNECTION_ATTEMPTS = 20; this.MIN_RECONNECTION_DELAY = 1000; this.MAX_RECONNECTION_DELAY = 86400000; this.connectionAttempts = 0; this.events = new events_1.EventEmitter(); this.handlerMap = new WeakMap(); this.handleConnectionError = (err) => { const { data } = err; // If the error is not related to the repeater, we should ignore it if (!(data === null || data === void 0 ? void 0 : data.code)) { this.logConnectionError(err); return; } if (this.suppressConnectionError(data)) { this.events.emit("error" /* RepeaterServerEvents.ERROR */, { ...data, message: err.message }); return; } if (this.connectionAttempts >= this.MAX_RECONNECTION_ATTEMPTS) { this.events.emit("reconnection_failed" /* RepeaterServerEvents.RECONNECTION_FAILED */, { error: err }); return; } // If the error is not related to the authentication, we should manually reconnect this.scheduleReconnection(); }; this.handleConnect = () => { this.connectionAttempts = 0; this.clearConnectionTimer(); this.events.emit("connected" /* RepeaterServerEvents.CONNECTED */); }; this.handleDisconnect = (reason) => { if (reason !== 'io client disconnect') { this.events.emit("disconnected" /* RepeaterServerEvents.DISCONNECTED */); } // the disconnection was initiated by the server, you need to reconnect manually if (reason === 'io server disconnect') { this.socket.connect(); } }; } disconnect() { var _a, _b; this.events.removeAllListeners(); this.clearConnectionTimer(); (_a = this._socket) === null || _a === void 0 ? void 0 : _a.disconnect(); (_b = this._socket) === null || _b === void 0 ? void 0 : _b.removeAllListeners(); this._socket = undefined; } async deploy(options = {}) { process.nextTick(() => this.socket.emit("deploy" /* SocketEvents.DEPLOY */, options)); const [result] = await Promise.race([ (0, events_1.once)(this.socket, "deployed" /* SocketEvents.DEPLOYED */), new Promise((_, reject) => setTimeout(reject, this.MAX_DEPLOYMENT_TIMEOUT, new Error('No response.')).unref()) ]); return result; } async connect(namePrefix = (0, os_1.hostname)()) { this._socket = (0, socket_io_client_1.default)(this.options.uri, { parser: socket_io_msgpack_parser_1.default, path: '/api/ws/v1', transports: ['websocket'], reconnectionAttempts: this.MAX_RECONNECTION_ATTEMPTS, auth: { domain: namePrefix, token: this.options.token } }); this.listenToReservedEvents(); this.listenToApplicationEvents(); await (0, events_1.once)(this.socket, 'connect'); this.logger.debug('Repeater connected to %s', this.options.uri); } off(event, handler) { const wrappedHandler = this.handlerMap.get(handler); if (wrappedHandler) { this.events.off(event, wrappedHandler); this.handlerMap.delete(handler); } } on(event, handler) { const wrappedHandler = (...args) => this.wrapEventListener(event, handler, ...args); this.handlerMap.set(handler, wrappedHandler); this.events.on(event, wrappedHandler); } async wrapEventListener(event, handler, ...args) { try { const callback = this.extractLastArgument(args); // eslint-disable-next-line @typescript-eslint/return-await const response = await handler(...args); callback === null || callback === void 0 ? void 0 : callback(response); } catch (err) { this.handleEventError(err, event, args); } } extractLastArgument(args) { const lastArg = args.pop(); if (typeof lastArg === 'function') { return lastArg; } else { // If the last argument is not a function, add it back to the args array args.push(lastArg); return undefined; } } listenToApplicationEvents() { this.socket.on("deployed" /* SocketEvents.DEPLOYED */, event => { this.events.emit("deploy" /* RepeaterServerEvents.DEPLOY */, event); }); this.socket.on("request" /* SocketEvents.REQUEST */, (event, callback) => this.events.emit("request" /* RepeaterServerEvents.REQUEST */, event, callback)); this.socket.on("error" /* SocketEvents.ERROR */, event => { this.events.emit("error" /* RepeaterServerEvents.ERROR */, event); }); this.socket.on("update-available" /* SocketEvents.UPDATE_AVAILABLE */, event => this.events.emit("update_available" /* RepeaterServerEvents.UPDATE_AVAILABLE */, event)); } listenToReservedEvents() { this.socket.on('connect', this.handleConnect); this.socket.on('connect_error', this.handleConnectionError); this.socket.on('disconnect', this.handleDisconnect); this.socket.io.on('reconnect', () => { this.latestReconnectionError = undefined; }); this.socket.io.on('reconnect_error', error => (this.latestReconnectionError = error)); this.socket.io.on('reconnect_failed', () => this.events.emit("reconnection_failed" /* RepeaterServerEvents.RECONNECTION_FAILED */, { error: this.latestReconnectionError })); this.socket.io.on('reconnect_attempt', attempt => this.events.emit("reconnect_attempt" /* RepeaterServerEvents.RECONNECT_ATTEMPT */, { attempt, maxAttempts: this.MAX_RECONNECTION_ATTEMPTS })); this.socket.io.on('reconnect', () => this.events.emit("reconnection_succeeded" /* RepeaterServerEvents.RECONNECTION_SUCCEEDED */)); } suppressConnectionError(data) { return [ RepeaterServer_1.RepeaterErrorCodes.REPEATER_UNAUTHORIZED, RepeaterServer_1.RepeaterErrorCodes.REPEATER_NOT_PERMITTED ].includes(data.code); } scheduleReconnection() { let delay = Math.max(this.MIN_RECONNECTION_DELAY * 2 ** this.connectionAttempts, this.MIN_RECONNECTION_DELAY); delay += delay * 0.3 * Math.random(); delay = Math.min(delay, this.MAX_RECONNECTION_DELAY); this.connectionAttempts++; this.events.emit("reconnect_attempt" /* RepeaterServerEvents.RECONNECT_ATTEMPT */, { attempt: this.connectionAttempts, maxAttempts: this.MAX_RECONNECTION_ATTEMPTS }); this.connectionTimer = setTimeout(() => this.socket.connect(), delay); } logConnectionError(err) { var _a; this.logger.debug('An error occurred while connecting to the repeater: %s', err.message); const { description, cause } = err; const nestedError = (_a = description === null || description === void 0 ? void 0 : description.error) !== null && _a !== void 0 ? _a : cause; if (nestedError) { this.logger.debug('The error cause: %s', nestedError.message); } } clearConnectionTimer() { if (this.connectionTimer) { clearTimeout(this.connectionTimer); } } handleEventError(error, event, args) { this.logger.debug('An error occurred while processing the %s event with the following payload: %j', event, args); this.logger.error('An error occurred', error); } }; exports.DefaultRepeaterServer = DefaultRepeaterServer; exports.DefaultRepeaterServer = DefaultRepeaterServer = tslib_1.__decorate([ (0, tsyringe_1.scoped)(tsyringe_1.Lifecycle.ContainerScoped), (0, tsyringe_1.injectable)(), tslib_1.__param(1, (0, tsyringe_1.inject)(exports.DefaultRepeaterServerOptions)), tslib_1.__metadata("design:paramtypes", [core_1.Logger, Object]) ], DefaultRepeaterServer); //# sourceMappingURL=DefaultRepeaterServer.js.map