UNPKG

nats-micro

Version:

NATS micro compatible extra-lightweight microservice library

236 lines 11.7 kB
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __asyncValues = (this && this.__asyncValues) || function (o) { if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined."); var m = o[Symbol.asyncIterator], i; return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i); function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; } function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); } }; import EventEmitter from 'events'; import { debug } from './debug.js'; import { MicroserviceRegistrationSubject, } from './types/index.js'; import { wrapMethod, attachThreadContext } from './utils/index.js'; export class Monitor { constructor(broker, systemBroker, options = {}) { this.broker = broker; this.systemBroker = systemBroker; this.services = []; this.connections = {}; this.ee = new EventEmitter(); this.options = Object.assign({ discoveryTimeout: 5000 }, options); const handleServiceRegistration = wrapMethod(this.broker, attachThreadContext('monitor', this.handleServiceRegistration.bind(this)), { method: 'handleServiceStatus', }); broker.on(MicroserviceRegistrationSubject, handleServiceRegistration); if (systemBroker) { systemBroker.on('$SYS.ACCOUNT.*.CONNECT', this.handleAccountConnect.bind(this)); systemBroker.on('$SYS.ACCOUNT.*.DISCONNECT', this.handleAccountDisconnect.bind(this)); } else { debug.monitor.error('Connection established/dropped monitoring disabled: no system broker'); } this.discoverConnections(); this.discover(this.options.discoveryTimeout); } discoverConnections() { var _a, e_1, _b, _c; return __awaiter(this, void 0, void 0, function* () { if (!this.systemBroker) { debug.monitor.error('Failed to discover current connections: no system broker'); return; } try { for (var _d = true, _e = __asyncValues(this.systemBroker.requestMany('$SYS.REQ.SERVER.PING', {}, { timeout: 3000, })), _f; _f = yield _e.next(), _a = _f.done, !_a;) { _c = _f.value; _d = false; try { const { data: server } = _c; debug.monitor.info(`Found server ${server.server.id}`); const { data: connz } = yield this.systemBroker.request(`$SYS.REQ.SERVER.${server.server.id}.CONNZ`, { auth: true }); if (!connz) { debug.monitor.error(`Server ${server.server.id} did not response CONNZ request`); return; } debug.monitor.debug(`Server ${server.server.id} connections: ${connz.data.connections.map((c) => c.cid)}`); for (const connection of connz.data.connections) { this.connections[connection.cid] = { client: { id: connection.cid, host: connection.ip, start: connection.start, acc: connection.account, user: connection.authorized_user, }, server: { name: server.server.name, id: server.server.id, host: server.server.host, ver: server.server.ver, }, }; } for (const service of this.services) { const clientId = this.getServiceClientId(service); if (clientId) { const conn = this.connections[clientId]; if (conn) { service.connection = conn; debug.monitor.debug(`Updated microservice ${service.name}.${service.id} to client ${clientId}'s connection`); this.emit('added', service); } } } } finally { _d = true; } } } catch (e_1_1) { e_1 = { error: e_1_1 }; } finally { try { if (!_d && !_a && (_b = _e.return)) yield _b.call(_e); } finally { if (e_1) throw e_1.error; } } }); } handleServiceRegistration(req, res) { if (req.data.state === 'down') this.removeService(req.data.info); else this.saveService(req.data.info); res.sendNoResponse(); } handleAccountConnect(msg) { return __awaiter(this, void 0, void 0, function* () { const connection = msg.data; this.connections[connection.client.id] = msg.data; }); } handleAccountDisconnect(msg) { var _a; return __awaiter(this, void 0, void 0, function* () { const connection = msg.data; const clientId = connection.client.id; let count = 0; let idx = 0; while (idx < this.services.length) { const service = this.services[idx]; if (((_a = this.getServiceConnectionInfo(service)) === null || _a === void 0 ? void 0 : _a.client.id) === clientId) { const removed = this.services.splice(idx, 1); this.emit('removed', removed[0]); count++; } else idx++; } debug.monitor.debug(`Client ${clientId} disconnected, removing ${count} microservices`); delete (this.connections[connection.client.id]); this.emit('change', this.services); }); } // eslint-disable-next-line @typescript-eslint/no-explicit-any emit(event, ...args) { this.ee.emit(event, ...args); } // eslint-disable-next-line @typescript-eslint/no-explicit-any on(event, listener) { this.ee.on(event, listener); } getServiceClientId(service) { const clientId = Number(service.metadata['_nats.client.id']); return Number.isNaN(clientId) ? undefined : clientId; } getServiceConnectionInfo(service) { const clientId = this.getServiceClientId(service); return clientId ? this.connections[clientId] : undefined; } saveService(service) { const idx = this.services.findIndex((svc) => svc.id === service.id); const clientId = this.getServiceClientId(service); const connection = this.getServiceConnectionInfo(service); debug.monitor.debug(`${idx >= 0 ? 'Updated' : 'New'} microservice ${service.name}.${service.id} on client ${clientId}${connection ? '' : ' (unknown connection)'}`); if (idx >= 0) { this.services[idx] = Object.assign(Object.assign(Object.assign({}, this.services[idx]), service), { lastFoundAt: new Date() }); } else this.services.push(Object.assign({ firstFoundAt: new Date(), lastFoundAt: new Date(), connection: connection }, service)); this.emit('added', service); this.emit('change', this.services); } removeService(service) { const idx = this.services.findIndex((svc) => svc.id === service.id); if (idx >= 0) { debug.monitor.debug(`Removing microservice ${service.name}.${service.id}`); this.services.splice(idx, 1); this.emit('removed', service); this.emit('change', this.services); } } discover(timeout, options) { var _a, e_2, _b, _c; return __awaiter(this, void 0, void 0, function* () { const servicesSnapshot = [...this.services]; const servicesIterator = this.broker.requestMany('$SRV.INFO', '', { limit: -1, timeout: timeout !== null && timeout !== void 0 ? timeout : this.options.discoveryTimeout, }); const services = []; try { for (var _d = true, servicesIterator_1 = __asyncValues(servicesIterator), servicesIterator_1_1; servicesIterator_1_1 = yield servicesIterator_1.next(), _a = servicesIterator_1_1.done, !_a;) { _c = servicesIterator_1_1.value; _d = false; try { const service = _c; services.push(service.data); this.saveService(service.data); } finally { _d = true; } } } catch (e_2_1) { e_2 = { error: e_2_1 }; } finally { try { if (!_d && !_a && (_b = servicesIterator_1.return)) yield _b.call(servicesIterator_1); } finally { if (e_2) throw e_2.error; } } if (!(options === null || options === void 0 ? void 0 : options.doNotClear)) { // compare to this.services BEFORE requestMany was made // as there can be new additions after $SRV.INFO sent and // before we get to this point const servicesToForget = servicesSnapshot .filter((oldSvc) => !services.some((newSvc) => newSvc.id === oldSvc.id)); if (servicesToForget.length > 0) { debug.monitor.info(`Removing microservices ${servicesToForget.map((svc) => `${svc.name}.${svc.id}`)} that have not responded during discovery`); for (const serviceToForger of servicesToForget) this.removeService(serviceToForger); } } }); } startPeriodicDiscovery(interval, discoveryTimeout) { this.stopPeriodicDiscovery(); this.discoveryInterval = setInterval(() => this.discover(discoveryTimeout), interval); } stopPeriodicDiscovery() { if (this.discoveryInterval) { clearInterval(this.discoveryInterval); this.discoveryInterval = undefined; } } } //# sourceMappingURL=monitor.js.map