UNPKG

@xpresser/events-server

Version:

Xpresser's Official Events Server Plugin.

345 lines (344 loc) 13.7 kB
"use strict"; var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) { if (kind === "m") throw new TypeError("Private method is not writable"); if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter"); if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it"); return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value; }; var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) { if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter"); if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it"); return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; var _EventsServer_secretKey; const router_1 = __importDefault(require("@xpresser/router")); const functions_1 = require("./functions"); const EventsServerDb_1 = __importDefault(require("./EventsServerDb")); const nanoid_1 = require("nanoid"); const net_1 = require("net"); const PlaneSocket_1 = __importDefault(require("./PlaneSocket")); class EventsServer { /** * Take xpresser instance and port. * Create our server. * @param secretKey * @param $ */ constructor(secretKey, $) { _EventsServer_secretKey.set(this, void 0); if ($.engineData.has("hasBooted")) $.logErrorAndExit(`$.boot() was called before reaching events server.`); if (!$.config.has("eventsServer")) $.logErrorAndExit(`{eventsServer} is not defined in config.`); const [err, eventsServerConfig] = (0, functions_1.loadEventServerConfig)($, true); if (err) $.logErrorAndExit(`Config: ${err.message}`); $.config .set("eventsServer", eventsServerConfig) // remove secret key. .unset("eventsServer.secretKey"); // Set SecretKey __classPrivateFieldSet(this, _EventsServer_secretKey, (0, functions_1.md5)(secretKey), "f"); // Set Port this.port = $.config.get("eventsServer.port"); // Disable expose $ $.options.exposeDollarSign = false; // Set isConsole = true; $.options.isConsole = true; // set isEventsServer = true; $.engineData.set("isEventsServer", true); // Change backend path $.config.set("paths.backend", "base://events-server"); // xpresser instance this.$ = $; // New Router Instance. this.$router = new router_1.default(); // Check launch type this.isCliCommand = $.engineData.get("LaunchType") === "cli"; // if launch type is cli, change controller stub path. if (this.isCliCommand) { let customControllerStubPath = __dirname + "/controller.hbs"; /** * Since Tsc does not move .hbs files we have to check. */ if (customControllerStubPath.indexOf("/js/src/") > 0) { customControllerStubPath = customControllerStubPath.replace("/js/src/", "/src/"); } $.config.set("artisan.factory.controller", customControllerStubPath); } else { // Set To requireOnly $.options.requireOnly = true; // Set Db this.db = new EventsServerDb_1.default($); // Initialize Server on Start $.on.start((next) => this.initializeSocket() && next()); } } /** * Map event to function or controller * @param event * @param fn */ on(event, fn) { this.$router.any(event, fn); return this; } /** * Get all loaded events */ getAllEvents() { return this.$.engineData.get("EventsServerEvents"); } /** * Start Listening for events */ startListening() { if (!this.isCliCommand) { // Process Routes on boot. this.$.on.boot((next) => this.processRoutes() && next()); /** * Start listening for events */ this.$.on.boot((next) => { this.$.logCalmly(`Waiting for authenticated connection...`); this.addConnectionListener().server.listen(this.port); return next(); }); } // Boot Xpresser this.$.boot(); } /** * Initialize Socket * @private */ initializeSocket() { this.server = (0, net_1.createServer)(); this.server.on("error", () => { this.$.logErrorAndExit("Events Server failed to start!"); }); return this; } processRoutes() { // Abbreviate $; const $ = this.$; // Log current backend Folder. $.log(`Backend Folder: ${$.config.get("paths.backend")}`); // Load Xpresser Routes Loader. require("xpresser/dist/src/Routes/Loader"); // Process all defined routes $.routerEngine.processRoutes(this.$router.routes); // Get all processed routes const routes = $.routerEngine.allProcessedRoutes(); // Load Xpresser's own controller getter. const ControllerGetter = require("xpresser/dist/src/ControllerEngine"); const events = []; /** * Loop through routes, get commands and bind then to the appropriate functions. */ for (const route of routes) { if (typeof route.controller === "string") { const { $controller, method } = ControllerGetter(route, null, true); if (!$controller.hasOwnProperty(method)) { const nameOfController = route.controller.split("@")[0] || "UNNAMED_CONTROLLER"; $.logErrorAndExit(`Method '${method}' does not exist in {${$controller.name || nameOfController}}`); break; } events.push({ event: route.url, handler: this.wrapControllerFunction($controller, route.url, method), controller: route.controller }); } else { events.push({ event: route.url, handler: this.wrapControllerFunction(route.controller, route.url), controller: route.controller }); } } $.engineData.set("EventsServerEvents", events); return this; } addConnectionListener() { this.server.on("connection", (socket) => { const pSocket = new PlaneSocket_1.default(socket); pSocket.on("Authorize", (data) => { if (data.secretKey && data.secretKey === __classPrivateFieldGet(this, _EventsServer_secretKey, "f")) { if (data.name) { this.$.logCalmly(`>>>>>>>>>>>>>>>>>>> LISTENING TO [${data.name}] <<<<<<<<<<<<<<<<<<<<`); } else { this.$.logCalmly(">>>>>>>>>>>>>>>>>>> LISTENING <<<<<<<<<<<<<<<<<<<<"); } return this.listenToAllRoutes(pSocket); } return socket.emit("error", "Authorization Failed, Invalid SECRET_KEY!"); }); pSocket.$setupListeners(); }); return this; } listenToAllRoutes(socket) { const events = this.getAllEvents(); for (const event of events) { socket.on(event.event, (...args) => event.handler(socket, ...args)); } socket.on("$retryFailedEvents", () => this.retryFailedEvents(socket, true)); socket.on("$runPendingEvents", () => this.runPendingEvents(socket)); socket.emit(`Authorized:${__classPrivateFieldGet(this, _EventsServer_secretKey, "f")}`); this.retryFailedEvents(socket); this.runPendingEvents(socket); } triggerRetryFailedEvents(socket, secs = 10) { // Clear all old retry events clearTimeout(this.retryTimeout); this.retryTimeout = setTimeout(() => { this.retryFailedEvents(socket); }, secs * 1000); } /** * Wrap a function around Events Server Before and After events. * @param $controller * @param event * @param method * @private */ wrapControllerFunction($controller, event, method) { const $ = this.$; const logArgs = $.config.get("eventsServer.log.args", true); const WrappedHandler = async (socket, ...args) => { let id = (0, nanoid_1.nanoid)(10); let isRetry = false; if (Array.isArray(socket)) { isRetry = true; id = socket[0]; socket = socket[1]; } // Log Received if (logArgs) { try { $.logCalmly(`RECEIVED|${(0, functions_1.now)()}| ${id} | ${event} | ` + "Args:" + JSON.stringify(args)); } catch (e) { $.logCalmly(`Could not parse args: ${e.message}`); } } else { $.logCalmly(`RECEIVED|${(0, functions_1.now)()}| ${id} | ${event}`); } // Run Controllers function try { if (typeof $controller === "function") { await $controller(this.makeControllerContext(id, socket, event), ...args); } else { await $controller[method](this.makeControllerContext(id, socket, event), ...args); } if (isRetry) { this.db.markAsSuccessful(id); socket.emit(`RemoveFromPending:${__classPrivateFieldGet(this, _EventsServer_secretKey, "f")}`, id); } // Log Completed $.logSuccess(` DONE|${(0, functions_1.now)()}| ${id} | ${event}`); } catch (e) { if (isRetry) { socket.emit(`RemoveFromPending:${__classPrivateFieldGet(this, _EventsServer_secretKey, "f")}`, id); } this.db.recordFailedEvent({ event, eventId: id, args }, e); this.triggerRetryFailedEvents(socket); $.logError(`‼️ ERROR|${(0, functions_1.now)()}| ${id} | ${event} --- ${e.message}`); } }; // Set Function name to event name. Object.defineProperty(WrappedHandler, "name", { value: `Wrapped_${event}` }); return WrappedHandler; } /** * Make Controller Context using socket passed. * @param id * @param socket * @param event * @private */ makeControllerContext(id, socket, event) { return { id, event, $: this.$, runEvent: (event, ...args) => { return this.runEvent(socket, event, ...args); }, reply(severSideEvent, ...args) { return socket.emit(severSideEvent, ...args); } }; } /** * Run an event. * @param socket * @param event * @param args * @private */ runEvent(socket, event, ...args) { const events = this.getAllEvents(); const eventData = events.find((e) => e.event === event); if (!eventData) throw Error(`RunEvent: "${event}" does not exist!, check spelling and try again.`); return eventData.handler(socket, ...args); } retryFailedEvents(socket, force = false) { const $ = this.$; // Get all failed Events const failedEvents = this.db.failedEvents(); // Get Ids const failedEventsIds = failedEvents.keys(); if (!failedEventsIds.length) return this; let retried = 0; for (const key of failedEventsIds) { const { event, args, retries } = failedEvents.get(key); if (!force && retries.length >= 3) continue; $.logWarning(`RETRYING|${(0, functions_1.now)()}| ${key} | ${event}`); retried++; setTimeout(() => { this.runEvent([key, socket], event, ...args); }, 1000); } if (retried) $.logWarning(`Retried (${retried}) failed events.`); return this; } runPendingEvents(socket, silently = false) { const $ = this.$; const db = new EventsServerDb_1.default($, true); // Get all failed Events const pendingEvents = db.pendingEvents(); // Get Ids const keys = pendingEvents.keys(); if (!keys.length) return this; // log if (!silently) $.logWarning(`(${keys.length}) Pending Events!`); for (const key of keys) { const { event, args } = pendingEvents.get(key); this.runEvent([key, socket], event, ...args); } return this; } } _EventsServer_secretKey = new WeakMap(); module.exports = EventsServer;