diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/server/ActionUtilities.ts | 8 | ||||
-rw-r--r-- | src/server/Session/session.ts | 58 | ||||
-rw-r--r-- | src/server/Session/session_config_schema.ts | 23 | ||||
-rw-r--r-- | src/server/index.ts | 43 |
4 files changed, 47 insertions, 85 deletions
diff --git a/src/server/ActionUtilities.ts b/src/server/ActionUtilities.ts index 3125f8683..30aed32e6 100644 --- a/src/server/ActionUtilities.ts +++ b/src/server/ActionUtilities.ts @@ -119,7 +119,13 @@ export namespace Email { }); export async function dispatchAll(recipients: string[], subject: string, content: string) { - return Promise.all(recipients.map((recipient: string) => Email.dispatch(recipient, subject, content))); + const failures: string[] = []; + await Promise.all(recipients.map(async (recipient: string) => { + if (!await Email.dispatch(recipient, subject, content)) { + failures.push(recipient); + } + })); + return failures; } export async function dispatch(recipient: string, subject: string, content: string): Promise<boolean> { diff --git a/src/server/Session/session.ts b/src/server/Session/session.ts index d07bd13a2..5c74af511 100644 --- a/src/server/Session/session.ts +++ b/src/server/Session/session.ts @@ -3,7 +3,6 @@ import { on, fork, setupMaster, Worker } from "cluster"; import { execSync } from "child_process"; import { get } from "request-promise"; import { Utils } from "../../Utils"; -import { Email } from "../ActionUtilities"; import Repl, { ReplAction } from "../repl"; import { readFileSync } from "fs"; import { validate, ValidationError } from "jsonschema"; @@ -24,16 +23,10 @@ const onWindows = process.platform === "win32"; */ export namespace Session { - interface EmailOptions { - recipients: string[]; - signature?: string; - } - interface Configuration { showServerOutput: boolean; masterIdentifier: string; workerIdentifier: string; - email: EmailOptions | undefined; ports: { [description: string]: number }; pollingRoute: string; pollingIntervalSeconds: number; @@ -44,19 +37,21 @@ export namespace Session { showServerOutput: false, masterIdentifier: yellow("__monitor__:"), workerIdentifier: magenta("__server__:"), - email: undefined, ports: { server: 3000 }, pollingRoute: "/", pollingIntervalSeconds: 30 }; - const defaultSignature = "-Server Session Manager"; - interface MasterCustomizer { addReplCommand: (basename: string, argPatterns: (RegExp | string)[], action: ReplAction) => void; addChildMessageHandler: (message: string, handler: ActionHandler) => void; } + export interface NotifierHooks { + key: (key: string) => boolean | Promise<boolean>; + crash: (error: Error) => boolean | Promise<boolean>; + } + export interface SessionAction { message: string; args: any; @@ -68,20 +63,6 @@ export namespace Session { subject: string; body: string; } - export type CrashEmailGenerator = (error: Error) => EmailTemplate | Promise<EmailTemplate>; - - function defaultEmailGenerator({ name, message, stack }: Error) { - return { - subject: "Server Crash Event", - body: [ - "You are being notified of a server crash event. Here's what we know:", - `name:\n${name}`, - `message:\n${message}`, - `stack:\n${stack}`, - "The server is already restarting itself automatically" - ].join("\n\n") - }; - } function loadAndValidateConfiguration(): any { try { @@ -138,10 +119,9 @@ export namespace Session { * Validates and reads the configuration file, accordingly builds a child process factory * and spawns off an initial process that will respawn as predecessors die. */ - export async function initializeMonitorThread(custom?: CrashEmailGenerator): Promise<MasterCustomizer> { + export async function initializeMonitorThread(notifiers?: NotifierHooks): Promise<MasterCustomizer> { let activeWorker: Worker; const childMessageHandlers: { [message: string]: (action: SessionAction, args: any) => void } = {}; - const crashEmailGenerator = custom || defaultEmailGenerator; // read in configuration .json file only once, in the master thread // pass down any variables the pertinent to the child processes as environment variables @@ -149,7 +129,6 @@ export namespace Session { masterIdentifier, workerIdentifier, ports, - email, pollingRoute, showServerOutput, pollingIntervalSeconds @@ -160,12 +139,10 @@ export namespace Session { // this sends a pseudorandomly generated guid to the configuration's recipients, allowing them alone // to kill the server via the /kill/:key route let key: string | undefined; - if (email) { - const { recipients, signature } = email; + if (notifiers && notifiers.key) { key = Utils.GenerateGuid(); - const content = `The key for this session (started @ ${new Date().toUTCString()}) is ${key}.\n\n${signature || defaultSignature}`; - const results = await Email.dispatchAll(recipients, "Server Termination Key", content); - const statement = results.some(success => !success) ? red("distribution of session key failed") : green("distributed session key to recipients"); + const success = await notifiers.key(key); + const statement = success ? green("distributed session key to recipients") : red("distribution of session key failed"); masterLog(statement); } @@ -233,17 +210,14 @@ export namespace Session { console.log(timestamp(), `${workerIdentifier} action requested (${cyan(message)})`); switch (message) { case "kill": - masterLog(red("An authorized user has manually ended the server session")); + masterLog(red("an authorized user has manually ended the server session")); tryKillActiveWorker(true); process.exit(0); case "notify_crash": - if (email) { - const { recipients, signature } = email; + if (notifiers && notifiers.crash) { const { error } = args; - const { subject, body } = await crashEmailGenerator(error); - const content = `${body}\n\n${signature || defaultSignature}`; - const results = await Email.dispatchAll(recipients, subject, content); - const statement = results.some(success => !success) ? red("distribution of crash notification failed") : green("distributed crash notification to recipients"); + const success = await notifiers.crash(error); + const statement = success ? green("distributed crash notification to recipients") : red("distribution of crash notification failed"); masterLog(statement); } case "set_port": @@ -273,9 +247,7 @@ export namespace Session { const repl = new Repl({ identifier: () => `${timestamp()} ${masterIdentifier}` }); repl.registerCommand("exit", [], () => execSync(onWindows ? "taskkill /f /im node.exe" : "killall -9 node")); repl.registerCommand("restart", [], restart); - repl.registerCommand("set", [/[a-zA-Z]+/g, "port", /\d+/g, /true|false/g], args => { - setPort(args[0], Number(args[2]), args[3] === "true"); - }); + repl.registerCommand("set", [/[a-zA-Z]+/, "port", /\d+/, /true|false/], args => setPort(args[0], Number(args[2]), args[3] === "true")); // finally, set things in motion by spawning off the first child (server) process spawn(); @@ -318,7 +290,7 @@ export namespace Session { }); await Promise.all(exitHandlers.map(handler => handler(error))); // notify master thread (which will log update in the console) of crash event via IPC - process.send?.({ lifecycle: red(`Crash event detected @ ${new Date().toUTCString()}`) }); + process.send?.({ lifecycle: red(`crash event detected @ ${new Date().toUTCString()}`) }); process.send?.({ lifecycle: red(error.message) }); process.exit(1); }; diff --git a/src/server/Session/session_config_schema.ts b/src/server/Session/session_config_schema.ts index 72b8d388a..76af04b9f 100644 --- a/src/server/Session/session_config_schema.ts +++ b/src/server/Session/session_config_schema.ts @@ -1,8 +1,5 @@ import { Schema } from "jsonschema"; -const emailPattern = /^(([a-zA-Z0-9_.-])+@([a-zA-Z0-9_.-])+\.([a-zA-Z])+([a-zA-Z])+)?$/g; -const routePattern = /\/[a-zA-Z]*/g; - export const configurationSchema: Schema = { id: "/configuration", type: "object", @@ -18,25 +15,7 @@ export const configurationSchema: Schema = { }, pollingRoute: { type: "string", - pattern: routePattern - }, - email: { - type: "object", - properties: { - recipients: { - type: "array", - items: { - type: "string", - pattern: emailPattern - }, - minLength: 1 - }, - signature: { - type: "string", - minLength: 1 - } - }, - required: ["recipients"] + pattern: /\/[a-zA-Z]*/g }, masterIdentifier: { type: "string", diff --git a/src/server/index.ts b/src/server/index.ts index bd339d65a..5e411aa3a 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -18,7 +18,7 @@ import { GoogleCredentialsLoader } from './credentials/CredentialsLoader'; import DeleteManager from "./ApiManagers/DeleteManager"; import PDFManager from "./ApiManagers/PDFManager"; import UploadManager from "./ApiManagers/UploadManager"; -import { log_execution } from "./ActionUtilities"; +import { log_execution, Email } from "./ActionUtilities"; import GeneralGoogleManager from "./ApiManagers/GeneralGoogleManager"; import GooglePhotosManager from "./ApiManagers/GooglePhotosManager"; import { Logger } from "./ProcessFactory"; @@ -146,30 +146,35 @@ async function launchServer() { } /** - * A function to dictate the format of the message sent on crash - * @param error the error that caused the crash - */ -function crashEmailGenerator(error: Error) { - const subject = "Dash Web Server Crash"; - const { name, message, stack } = error; - const body = [ - "You, as a Dash Administrator, are being notified of a server crash event. Here's what we know:", - `name:\n${name}`, - `message:\n${message}`, - `stack:\n${stack}`, - "The server is already restarting itself, but if you're concerned, use the Remote Desktop Connection to monitor progress.", - ].join("\n\n"); - return { subject, body }; -} - -/** * If we're the monitor (master) thread, we should launch the monitor logic for the session. * Otherwise, we must be on a worker thread that was spawned *by* the monitor (master) thread, and thus * our job should be to run the server. */ async function launchMonitoredSession() { if (isMaster) { - const customizer = await Session.initializeMonitorThread(crashEmailGenerator); + const recipients = ["samuel_wilkins@brown.edu"]; + const signature = "-Dash Server Session Manager"; + const customizer = await Session.initializeMonitorThread({ + key: async (key: string) => { + const content = `The key for this session (started @ ${new Date().toUTCString()}) is ${key}.\n\n${signature}`; + const failures = await Email.dispatchAll(recipients, "Server Termination Key", content); + return failures.length === 0; + }, + crash: async (error: Error) => { + const subject = "Dash Web Server Crash"; + const { name, message, stack } = error; + const body = [ + "You, as a Dash Administrator, are being notified of a server crash event. Here's what we know:", + `name:\n${name}`, + `message:\n${message}`, + `stack:\n${stack}`, + "The server is already restarting itself, but if you're concerned, use the Remote Desktop Connection to monitor progress.", + ].join("\n\n"); + const content = `${body}\n\n${signature}`; + const failures = await Email.dispatchAll(recipients, subject, content); + return failures.length === 0; + } + }); customizer.addReplCommand("pull", [], () => execSync("git pull", { stdio: ["ignore", "inherit", "inherit"] })); customizer.addReplCommand("solr", [/start|stop/g], args => SolrManager.SetRunning(args[0] === "start")); } else { |