import { yellow } from 'colors'; import * as dotenv from 'dotenv'; import * as mobileDetect from 'mobile-detect'; import * as path from 'path'; import { logExecution } from './ActionUtilities'; import AssistantManager from './ApiManagers/AssistantManager'; import FlashcardManager from './ApiManagers/FlashcardManager'; import DataVizManager from './ApiManagers/DataVizManager'; import DeleteManager from './ApiManagers/DeleteManager'; import DownloadManager from './ApiManagers/DownloadManager'; import FireflyManager from './ApiManagers/FireflyManager'; import GeneralGoogleManager from './ApiManagers/GeneralGoogleManager'; import SessionManager from './ApiManagers/SessionManager'; import UploadManager from './ApiManagers/UploadManager'; import UserManager from './ApiManagers/UserManager'; import UtilManager from './ApiManagers/UtilManager'; import { DashSessionAgent } from './DashSession/DashSessionAgent'; import { AppliedSessionAgent } from './DashSession/Session/agents/applied_session_agent'; import { DashStats } from './DashStats'; import { DashUploadUtils } from './DashUploadUtils'; import { Logger } from './ProcessFactory'; import RouteManager, { Method, PublicHandler } from './RouteManager'; import RouteSubscriber from './RouteSubscriber'; import { AdminPrivileges, resolvedPorts } from './SocketData'; import { GoogleCredentialsLoader, SSL } from './apis/google/CredentialsLoader'; import { GoogleApiServerUtils } from './apis/google/GoogleApiServerUtils'; import { Database } from './database'; import initializeServer from './server_Initialization'; // import GooglePhotosManager from './ApiManagers/GooglePhotosManager'; dotenv.config(); export const onWindows = process.platform === 'win32'; export let sessionAgent: AppliedSessionAgent; /** * These are the functions run before the server starts * listening. Anything that must be complete * before clients can access the server should be run or awaited here. */ async function preliminaryFunctions() { // Utils.TraceConsoleLog(); await DashUploadUtils.buildFileDirectories(); await Logger.initialize(); await GoogleCredentialsLoader.loadCredentials(); SSL.loadCredentials(); GoogleApiServerUtils.processProjectCredentials(); if (process.env.DB !== 'MEM') { await logExecution({ startMessage: 'attempting to initialize mongodb connection', endMessage: 'connection outcome determined', action: Database.tryInitializeConnection, }); } } /** * Either clustered together as an API manager * or individually referenced below, by the completion * of this function's execution, all routes will * be registered on the server * @param router the instance of the route manager * that will manage the registration of new routes * with the server */ function routeSetter({ addSupervisedRoute, logRegistrationOutcome }: RouteManager) { const managers = [ new SessionManager(), new UserManager(), new UploadManager(), new DownloadManager(), new DeleteManager(), new UtilManager(), new GeneralGoogleManager(), /* new GooglePhotosManager(), */ new DataVizManager(), new AssistantManager(), new FlashcardManager(), new FireflyManager(), ]; // initialize API Managers console.log(yellow('\nregistering server routes...')); managers.forEach(manager => manager.register(addSupervisedRoute)); /** * Accessing root index redirects to home */ addSupervisedRoute({ method: Method.GET, subscription: '/', secureHandler: ({ res }) => res.redirect('/home'), }); addSupervisedRoute({ method: Method.GET, subscription: '/serverHeartbeat', secureHandler: ({ res }) => res.send(true), }); addSupervisedRoute({ method: Method.GET, subscription: '/stats', secureHandler: ({ res }) => DashStats.handleStats(res), }); addSupervisedRoute({ method: Method.GET, subscription: '/statsview', secureHandler: ({ res }) => DashStats.handleStatsView(res), }); addSupervisedRoute({ method: Method.GET, subscription: '/resolvedPorts', secureHandler: ({ res }) => res.send(resolvedPorts), publicHandler: ({ res }) => res.send(resolvedPorts), }); const serve: PublicHandler = ({ req, res }) => { const detector = new mobileDetect(req.headers['user-agent'] || ''); const filename = detector.mobile() !== null ? 'mobile/image.html' : 'index.html'; res.sendFile(path.join(__dirname, '../../deploy/' + filename)); }; /** * Serves a simple password input box for any */ addSupervisedRoute({ method: Method.GET, subscription: new RouteSubscriber('admin').add('previous_target'), secureHandler: ({ res, isRelease }) => { const { PASSWORD } = process.env; if (!(isRelease && PASSWORD)) { res.redirect('/home'); } else res.render('admin.pug', { title: 'Enter Administrator Password' }); }, }); addSupervisedRoute({ method: Method.POST, subscription: new RouteSubscriber('admin').add('previous_target'), secureHandler: async ({ req, res, isRelease, user: { id } }) => { const { PASSWORD } = process.env; if (!(isRelease && PASSWORD)) { res.redirect('/home'); } else { const { password } = req.body; const { previous_target: previousTarget } = req.params; let redirect: string; if (password === PASSWORD) { AdminPrivileges.set(id, true); redirect = `/${previousTarget.replace(':', '/')}`; } else { redirect = `/admin/${previousTarget}`; } res.redirect(redirect); } }, }); addSupervisedRoute({ method: Method.GET, subscription: ['/home', new RouteSubscriber('doc').add('docId')], secureHandler: serve, publicHandler: ({ req, res, ...remaining }) => { const { originalUrl: target } = req; const docAccess = target.startsWith('/doc/'); // since this is the public handler, there's no meaning of '/home' to speak of // since there's no user logged in, so the only viable operation // for a guest is to look at a shared document if (docAccess) { serve({ req, res, ...remaining }); } else { res.redirect('/login'); } }, }); logRegistrationOutcome(); } /** * This function can be used in two different ways. If not in release mode, * this is simply the logic that is invoked to start the server. In release mode, * however, this becomes the logic invoked by a single worker thread spawned by * the main monitor (master) thread. */ export async function launchServer() { await logExecution({ startMessage: '\nstarting execution of preliminary functions', endMessage: 'completed preliminary functions\n', action: preliminaryFunctions, }); await initializeServer(routeSetter); } /** * If you're in development mode, you won't need to run a session. * The session spawns off new server processes each time an error is encountered, and doesn't * log the output of the server process, so it's not ideal for development. * So, the 'else' clause is exactly what we've always run when executing npm start. */ (Database.Instance as Database.Database).doConnect(); if (process.env.MONITORED) { (sessionAgent = new DashSessionAgent()).launch(); } else { launchServer(); }