UNPKG

reldens

Version:
435 lines (405 loc) 17.7 kB
/** * * Reldens - ServerManager * */ const dotenv = require('dotenv'); const { WebSocketTransport } = require('@colyseus/ws-transport'); const ReldensASCII = require('../reldens-ascii'); const PackageData = require('./../../../package.json'); const { GameServer } = require('./game-server'); const { AppServerFactory } = require('./app-server-factory'); const { ConfigManager } = require('../../config/server/manager'); const { DataServerInitializer } = require('./data-server-initializer'); const { FeaturesManager } = require('../../features/server/manager'); const { UsersManager } = require('../../users/server/manager'); const { LoginManager } = require('./login-manager'); const { RoomsManager } = require('../../rooms/server/manager'); const { Mailer } = require('./mailer'); const { ThemeManager } = require('./theme-manager'); const { MapsLoader } = require('./maps-loader'); const { ForgotPassword } = require('./forgot-password'); const { Installer } = require('./installer'); const { RoomScene } = require('../../rooms/server/scene'); const { GameConst } = require('../constants'); const { ChatConst } = require('../../chat/constants'); const { EventsManagerSingleton, Logger, sc } = require('@reldens/utils'); class ServerManager { express = false; app = false; appServer = {}; gameServer = false; events = false; dataServerDriver = false; dataServerConfig = {}; dataServer = false; autoGenerateEntities = true; rawConfig = false; configManager = {}; projectRoot = './'; configServer = false; mailer = false; featuresManager = false; roomsManager = false; loginManager = false; usersManager = false; translations = {}; constructor(config, eventsManager, dataServerDriver) { Logger.debug('Main server instance creation.', {config}); this.rawConfig = config; this.dataServerDriver = dataServerDriver; this.events = eventsManager || EventsManagerSingleton; this.themeManager = new ThemeManager(config); dotenv.config({debug: process.env.DEBUG, path: this.themeManager.envFilePath}); this.appServerFactory = new AppServerFactory(); this.installer = new Installer({ themeManager: this.themeManager, startCallback: async () => { // after installation, we need to reload the environment variables: dotenv.config({debug: process.env.DEBUG, path: this.themeManager.envFilePath}); // and start the server: return await this.start(); } }); this.initializeConfiguration(config); } setupCustomServerPlugin(config) { let customPluginClass = sc.get(config, 'customPlugin', false); if(!customPluginClass){ return false; } this.customPlugin = new customPluginClass(); this.customPlugin.setup({events: this.events}); } initializeConfiguration(config) { Logger.info('Initialize Configuration.'); this.configManager = new ConfigManager({events: this.events, customClasses: (config.customClasses || {})}); this.projectRoot = sc.get(config, 'projectRoot', './'); Logger.info('Project root: '+this.projectRoot, 'Module root: '+__dirname); this.configServer = this.fetchConfigServerFromEnvironmentVariables(); this.isHotPlugEnabled = 1 === Number(process.env.RELDENS_HOT_PLUG || 1); this.guestsEmailDomain = String(process.env.RELDENS_GUESTS_EMAIL_DOMAIN || '@guest-reldens.com'); } fetchConfigServerFromEnvironmentVariables() { let host = String(process.env.RELDENS_APP_HOST || 'http://localhost'); let port = Number(process.env.PORT || 0); if(0 === port){ port = Number(process.env.RELDENS_APP_PORT || 0); if(0 === port){ port = 8080; } } let environmentConfig = { appServerConfig: { encoding: String(process.env.RELDENS_DEFAULT_ENCODING || 'utf-8'), useHttps: 1 === Number(process.env.RELDENS_EXPRESS_USE_HTTPS || 0), passphrase: String(process.env.RELDENS_EXPRESS_HTTPS_PASSPHRASE || ''), httpsChain: String(process.env.RELDENS_EXPRESS_HTTPS_CHAIN || ''), keyPath: String(process.env.RELDENS_EXPRESS_HTTPS_PRIVATE_KEY || ''), certPath: String(process.env.RELDENS_EXPRESS_HTTPS_CERT || ''), trustedProxy: String(process.env.RELDENS_EXPRESS_TRUSTED_PROXY || ''), windowMs: Number(process.env.RELDENS_EXPRESS_RATE_LIMIT_MS || 60000), maxRequests: Number(process.env.RELDENS_EXPRESS_RATE_LIMIT_MAX_REQUESTS || 30), applyKeyGenerator: 1 === Number(process.env.RELDENS_EXPRESS_RATE_LIMIT_APPLY_KEY_GENERATOR || 0) }, host, port, publicUrl: String(process.env.RELDENS_PUBLIC_URL || host+':'+port), monitor: { enabled: 1 === Number(process.env.RELDENS_MONITOR || 0) || false, auth: Boolean(process.env.RELDENS_MONITOR_AUTH || false), user: process.env.RELDENS_MONITOR_USER || '', pass: process.env.RELDENS_MONITOR_PASS || '', } }; Logger.info('Server environment configuration:', environmentConfig); return environmentConfig; } async initializeStorage(config, dataServerDriver) { Logger.info('Initialize Storage.'); let {dataServerConfig, dataServer} = DataServerInitializer.initializeEntitiesAndDriver({ config, dataServerDriver, serverManager: this }); this.dataServerConfig = dataServerConfig; this.dataServer = dataServer; if(!await dataServer.connect()){ Logger.critical('Data Server could not be connected.'); process.exit(); } Logger.info('Storage connected.', {config: dataServerConfig.config, initialized: dataServer.initialized}); if(this.autoGenerateEntities){ await dataServer.generateEntities(); Logger.info('Storage entities generated.', {entities: Object.keys(dataServerConfig.loadedEntities)}); } } async start() { if(!this.installer.isInstalled()){ Logger.critical('Reldens not installed.'); await this.appServer.listen(this.configServer.port); Logger.info('Server listening on '+this.configServer.host+':'+this.configServer.port); return false; } Logger.info('Starting Server!'); if(this.appServer?.listening){ await this.appServer.close(); // @NOTE: at this point the environment variables could be changed by the .env created by the installer. this.configServer = this.fetchConfigServerFromEnvironmentVariables(); Object.assign(this, this.appServerFactory.createAppServer(this.configServer.appServerConfig)); } this.setupCustomServerPlugin(this.rawConfig); await this.themeManager.validateOrCreateTheme(); await this.initializeStorage(this.rawConfig, this.dataServerDriver); this.configManager.dataServer = this.dataServer; await this.initializeConfigManager(); await this.enableServeStaticsAndHomePage(); return await this.startGameServerInstance(); } async startGameServerInstance() { MapsLoader.reloadMaps(this.themeManager.projectThemePath, this.configManager); await this.createGameServer(); if(!this.validateServer()){ return false; } if(!await this.initializeManagers()){ return false; } await this.events.emit('reldens.serverBeforeListen', {serverManager: this}); await this.gameServer.listen(this.configServer.port); this.configManager.configList.server.baseUrl = this.configServer.host + ':' + this.configServer.port; this.configManager.configList.server.publicUrl = this.configServer.publicUrl; this.showInfoLogs(); await this.events.emit('reldens.serverReady', {serverManager: this}); return this; } validateServer() { if(!this.appServer){ Logger.critical('App Server is not defined.'); return false; } if(!this.gameServer){ Logger.critical('Game Server is not defined.'); return false; } return true; } showInfoLogs() { Logger.log('info', 'Server ready.' + ReldensASCII); Logger.info('Main packages:', [ 'parcel: ' + PackageData.dependencies['@parcel/core'], 'colyseus: ' + PackageData.dependencies['@colyseus/core'], 'phaser: ' + PackageData.dependencies['phaser'], 'firebase: ' + PackageData.dependencies['firebase'], 'firebaseui: ' + PackageData.dependencies['firebaseui'], 'reldens/utils: ' + PackageData.dependencies['@reldens/utils'], 'reldens/storage: ' + PackageData.dependencies['@reldens/storage'], 'reldens/modifiers: ' + PackageData.dependencies['@reldens/modifiers'], 'reldens/items-system: ' + PackageData.dependencies['@reldens/items-system'], 'reldens/skills: ' + PackageData.dependencies['@reldens/skills'], ]); Logger.info('Server listening on ' + this.configServer.host + ':' + this.configServer.port); } async createServers() { await this.createAppServer(); if(!this.installer.isInstalled()){ Logger.info('Reldens not installed, preparing setup procedure.'); return await this.installer.prepareSetup(this.app); } return true; } async createAppServer() { // @TODO - BETA - Pass AppServerFactory to the ServerManager constructor to avoid the other libraries require // if are not needed. Modify theme/index.js.dist to pass it on the default installation. let event = {serverManager: this, continueProcess: true}; await this.events.emit('reldens.createAppServer', event); if(!event.continueProcess){ return false; } Object.assign(this, this.appServerFactory.createAppServer()); } enableServeStaticsAndHomePage() { // @TODO - BETA - Make these configurable from the storage. if(1 === Number(process.env.RELDENS_EXPRESS_SERVE_HOME || 0)){ let storedClientConfig = {client: this.configManager.client}; let clientFullConfig = Object.assign({}, this.configManager.gameEngine, storedClientConfig); this.appServerFactory.enableServeHome(this.app, this.themeManager.distPath, clientFullConfig); } if(1 === Number(process.env.RELDENS_EXPRESS_SERVE_STATICS || 0)){ this.appServerFactory.enableServeStatics(this.app, this.themeManager.distPath); } } async createGameServer() { // @TODO - BETA - Extract into a GameServerFactory, pass it to the ServerManager constructor to avoid the other // libraries require if are not needed. Modify theme/index.js.dist to pass it on the default installation. let options = { pingInterval: process.env.RELDENS_PING_INTERVAL || 5000, pingMaxRetries: process.env.RELDENS_PING_MAX_RETRIES || 3 }; if(this.appServer){ options.server = this.appServer; } let event = {options, continueProcess: true}; await this.events.emit('reldens.createGameServer', event); if(!event.continueProcess){ return false; } this.gameServer = new GameServer({ transport: new WebSocketTransport(options) }); if(this.configServer.monitor.enabled){ this.gameServer.attachMonitor(this.app, this.configServer.monitor); } } async initializeManagers() { let event = {serverManager: this, continueProcess: true}; await this.events.emit('reldens.beforeInitializeManagers', event); if(!event.continueProcess){ return false; } Logger.info('Initialize Managers.'); await this.initializeMailer(); await this.initializeFeaturesManager(); this.initializeUsersManager(); await this.initializeRoomsManager(); this.initializeLoginManager(); await this.defineServerRooms(); return true; } async defineServerRooms() { await this.events.emit('reldens.serverBeforeDefineRooms', {serverManager: this}); await this.roomsManager.defineRoomsInGameServer(this.gameServer, { loginManager: this.loginManager, config: this.configManager, dataServer: this.dataServer, featuresManager: this.featuresManager }); } initializeLoginManager() { this.loginManager = new LoginManager({ config: this.configManager, usersManager: this.usersManager, roomsManager: this.roomsManager, mailer: this.mailer, themeManager: this.themeManager, events: this.events, configServer: this.configServer, appServer: this.appServer }); } async initializeRoomsManager() { // the "rooms" manager will receive the features rooms to be defined: this.roomsManager = new RoomsManager({events: this.events, dataServer: this.dataServer}); await this.events.emit('reldens.serverBeforeLoginManager', {serverManager: this}); } initializeUsersManager() { this.usersManager = new UsersManager({ events: this.events, dataServer: this.dataServer, config: this.configManager }); } async initializeFeaturesManager() { this.featuresManager = new FeaturesManager({ events: this.events, dataServer: this.dataServer, config: this.configManager, themeManager: this.themeManager }); this.configManager.availableFeaturesList = await this.featuresManager.loadFeatures(); await this.events.emit('reldens.serverConfigFeaturesReady', { serverManager: this, configProcessor: this.configManager }); } async initializeMailer(mailer) { // @TODO - BETA - Extract and pass to the ServerManager in the constructor. this.mailer = mailer || new Mailer(); if(this.mailer.readyForSetup){ let result = await this.mailer.setupTransporter(); if(!result){ Logger.error('Mailer setup failed.'); return false; } } Logger.info('Mailer: '+(this.mailer?.isEnabled() ? 'enabled' : 'disabled')); // @TODO - BETA - Check if the forgot password is enabled or not before add the calls to the server. await ForgotPassword.defineRequestOnServerManagerApp(this); } async initializeConfigManager() { await this.configManager.loadConfigurations(); this.configManager.projectPaths = this.themeManager.paths(); this.configGuestEmailDomain(); await this.configRoomsServerUrl(); await this.events.emit('reldens.serverConfigReady', {serverManager: this, configProcessor: this.configManager}); } configGuestEmailDomain() { let customGuestEmailDomain = this.configManager.getWithoutLogs('server/players/guestsUser/emailDomain', ''); if('' === customGuestEmailDomain){ sc.deepMergeProperties( this.configManager, {server: {players: {guestsUser: {emailDomain: this.guestsEmailDomain}}}} ); return; } this.guestsEmailDomain = customGuestEmailDomain; } async configRoomsServerUrl() { let roomsRepository = this.dataServer.getEntity('rooms'); let rooms = await roomsRepository.loadAll(); if(!rooms || 0 === rooms.length){ return; } let servers = {}; for(let room of rooms){ servers[room.name] = room.server_url || this.configServer.publicUrl || this.configServer.host+':'+this.configServer.port; } sc.deepMergeProperties(this.configManager, {client: {rooms: {servers}}}); } async serverBroadcast(props) { if(!props.message){ return false; } let messageSendModel = { [GameConst.ACTION_KEY]: ChatConst.CHAT_ACTION, [ChatConst.TYPES.KEY]: ChatConst.TYPES.ERROR, [ChatConst.MESSAGE.KEY]: props.message, }; let roomsKeys = Object.keys(this.roomsManager.createdInstances); for(let roomKey of roomsKeys){ let room = this.roomsManager.createdInstances[roomKey]; if(room instanceof RoomScene){ room.broadcast('*', messageSendModel); } } return true; } } module.exports.ServerManager = ServerManager;