@xpresser/events-server
Version:
Xpresser's Official Events Server Plugin.
345 lines (344 loc) • 13.7 kB
JavaScript
"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;