1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
|
import { yellow, red, cyan, magenta, green } from "colors";
import { isMaster, on, fork, setupMaster, Worker } from "cluster";
import InputManager from "./session_manager/input_manager";
import { execSync } from "child_process";
import { Email } from "./session_manager/email";
import { get } from "request-promise";
import { WebSocket } from "./Websocket/Websocket";
import { Utils } from "../Utils";
import { MessageStore } from "./Message";
const onWindows = process.platform === "win32";
const heartbeat = `http://localhost:1050/serverHeartbeat`;
const admin = ["samuel_wilkins@brown.edu"];
export namespace Session {
export let key: string;
export const signature = "Best,\nServer Session Manager";
let activeWorker: Worker;
let listening = false;
const masterIdentifier = `${yellow("__master__")}:`;
const workerIdentifier = `${magenta("__worker__")}:`;
function log(message?: any, ...optionalParams: any[]) {
const identifier = isMaster ? masterIdentifier : workerIdentifier;
console.log(identifier, message, ...optionalParams);
}
export async function distributeKey() {
key = Utils.GenerateGuid();
const timestamp = new Date().toUTCString();
const content = `The key for this session (started @ ${timestamp}) is ${key}.\n\n${signature}`;
return Promise.all(admin.map(recipient => Email.dispatch(recipient, "Server Termination Key", content)));
}
function tryKillActiveWorker() {
if (activeWorker && !activeWorker.isDead()) {
activeWorker.process.kill();
return true;
}
return false;
}
function logLifecycleEvent(lifecycle: string) {
process.send?.({ lifecycle });
}
function messageHandler({ lifecycle, action }: any) {
if (action) {
console.log(`${workerIdentifier} action requested (${action})`);
switch (action) {
case "kill":
log(red("An authorized user has ended the server from the /kill route"));
tryKillActiveWorker();
process.exit(0);
}
} else if (lifecycle) {
console.log(`${workerIdentifier} lifecycle phase (${lifecycle})`);
}
}
async function activeExit(error: Error) {
if (!listening) {
return;
}
listening = false;
await Promise.all(admin.map(recipient => Email.dispatch(recipient, "Dash Web Server Crash", crashReport(error))));
const { _socket } = WebSocket;
if (_socket) {
Utils.Emit(_socket, MessageStore.ConnectionTerminated, "Manual");
}
logLifecycleEvent(red(`Crash event detected @ ${new Date().toUTCString()}`));
logLifecycleEvent(red(error.message));
process.exit(1);
}
function crashReport({ name, message, stack }: Error) {
return [
"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.",
signature
].join("\n\n");
}
export async function initialize(work: Function) {
if (isMaster) {
process.on("uncaughtException", error => {
if (error.message !== "Channel closed") {
log(red(error.message));
if (error.stack) {
log(`\n${red(error.stack)}`);
}
}
});
setupMaster({ silent: true });
const spawn = () => {
tryKillActiveWorker();
activeWorker = fork();
activeWorker.on("message", messageHandler);
};
spawn();
on("exit", ({ process: { pid } }, code, signal) => {
const prompt = `Server worker with process id ${pid} has exited with code ${code}${signal === null ? "" : `, having encountered signal ${signal}`}.`;
log(cyan(prompt));
spawn();
});
const { registerCommand } = new InputManager({ identifier: masterIdentifier });
registerCommand("exit", [], () => execSync(onWindows ? "taskkill /f /im node.exe" : "killall -9 node"));
registerCommand("pull", [], () => execSync("git pull", { stdio: ["ignore", "inherit", "inherit"] }));
registerCommand("restart", [], () => {
listening = false;
tryKillActiveWorker();
});
} else {
logLifecycleEvent(green("initializing..."));
process.on('uncaughtException', activeExit);
const checkHeartbeat = async () => {
await new Promise<void>(resolve => {
setTimeout(async () => {
try {
await get(heartbeat);
if (!listening) {
logLifecycleEvent(green("listening..."));
}
listening = true;
resolve();
} catch (error) {
await activeExit(error);
}
}, 1000 * 15);
});
checkHeartbeat();
};
work();
checkHeartbeat();
}
}
}
|