aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/client/DocServer.ts22
-rw-r--r--src/client/cognitive_services/CognitiveServices.ts9
-rw-r--r--src/new_fields/InkField.ts1
-rw-r--r--src/server/DashSession.ts7
-rw-r--r--src/server/Session/session.ts64
5 files changed, 64 insertions, 39 deletions
diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts
index ed7fbd7ba..12fed3e46 100644
--- a/src/client/DocServer.ts
+++ b/src/client/DocServer.ts
@@ -64,6 +64,24 @@ export namespace DocServer {
}
}
+ const instructions = "This page will automatically refresh after this alert is closed. Expect to reconnect after about 30 seconds.";
+ function alertUser(connectionTerminationReason: string) {
+ switch (connectionTerminationReason) {
+ case "crash":
+ alert(`Dash has temporarily crashed. Administrators have been notified and the server is restarting itself. ${instructions}`);
+ break;
+ case "temporary":
+ alert(`An administrator has chosen to restart the server. ${instructions}`);
+ break;
+ case "exit":
+ alert("An administrator has chosen to kill the server. Do not expect to reconnect until administrators start the server.");
+ break;
+ default:
+ console.log(`Received an unknown ConnectionTerminated message: ${connectionTerminationReason}`);
+ }
+ window.location.reload();
+ }
+
export function init(protocol: string, hostname: string, port: number, identifier: string) {
_cache = {};
GUID = identifier;
@@ -82,9 +100,7 @@ export namespace DocServer {
Utils.AddServerHandler(_socket, MessageStore.UpdateField, respondToUpdate);
Utils.AddServerHandler(_socket, MessageStore.DeleteField, respondToDelete);
Utils.AddServerHandler(_socket, MessageStore.DeleteFields, respondToDelete);
- Utils.AddServerHandler(_socket, MessageStore.ConnectionTerminated, () => {
- alert("Your connection to the server has been terminated.");
- });
+ Utils.AddServerHandler(_socket, MessageStore.ConnectionTerminated, alertUser);
}
function errorFunc(): never {
diff --git a/src/client/cognitive_services/CognitiveServices.ts b/src/client/cognitive_services/CognitiveServices.ts
index 02eff3b25..57296c961 100644
--- a/src/client/cognitive_services/CognitiveServices.ts
+++ b/src/client/cognitive_services/CognitiveServices.ts
@@ -137,7 +137,7 @@ export namespace CognitiveServices {
let id = 0;
const strokes: AzureStrokeData[] = inkData.map(points => ({
id: id++,
- points: points.map(({ x, y }) => `${x},${y}`).join(","),
+ points: points.map(({ X: x, Y: y }) => `${x},${y}`).join(","),
language: "en-US"
}));
return JSON.stringify({
@@ -153,7 +153,7 @@ export namespace CognitiveServices {
const serverAddress = "https://api.cognitive.microsoft.com";
const endpoint = serverAddress + "/inkrecognizer/v1.0-preview/recognize";
- const promisified = (resolve: any, reject: any) => {
+ return new Promise<string>((resolve, reject) => {
xhttp.onreadystatechange = function () {
if (this.readyState === 4) {
const result = xhttp.responseText;
@@ -171,11 +171,8 @@ export namespace CognitiveServices {
xhttp.setRequestHeader('Ocp-Apim-Subscription-Key', apiKey);
xhttp.setRequestHeader('Content-Type', 'application/json');
xhttp.send(body);
- };
-
- return new Promise<any>(promisified);
+ });
},
-
};
export namespace Appliers {
diff --git a/src/new_fields/InkField.ts b/src/new_fields/InkField.ts
index 83d631958..e2aa7ee16 100644
--- a/src/new_fields/InkField.ts
+++ b/src/new_fields/InkField.ts
@@ -2,7 +2,6 @@ import { Deserializable } from "../client/util/SerializationHelper";
import { serializable, custom, createSimpleSchema, list, object, map } from "serializr";
import { ObjectField } from "./ObjectField";
import { Copy, ToScriptString } from "./FieldSymbols";
-import { DeepCopy } from "../Utils";
export enum InkTool {
None,
diff --git a/src/server/DashSession.ts b/src/server/DashSession.ts
index a0e00adda..1c70ae1dc 100644
--- a/src/server/DashSession.ts
+++ b/src/server/DashSession.ts
@@ -63,9 +63,12 @@ export class DashSessionAgent extends Session.AppliedSessionAgent {
protected async launchServerWorker() {
const worker = Session.ServerWorker.Create(launchServer); // server initialization delegated to worker
- worker.addExitHandler(() => {
+ worker.addExitHandler(reason => {
const { _socket } = WebSocket;
- _socket && Utils.Emit(_socket, MessageStore.ConnectionTerminated, "Manual");
+ if (_socket) {
+ const message = typeof reason === "boolean" ? (reason ? "exit" : "temporary") : "crash";
+ Utils.Emit(_socket, MessageStore.ConnectionTerminated, message);
+ }
});
return worker;
}
diff --git a/src/server/Session/session.ts b/src/server/Session/session.ts
index 6967ece52..d46e6b6e7 100644
--- a/src/server/Session/session.ts
+++ b/src/server/Session/session.ts
@@ -131,7 +131,7 @@ export namespace Session {
}
};
- export type ExitHandler = (reason: Error | null) => void | Promise<void>;
+ export type ExitHandler = (reason: Error | boolean) => void | Promise<void>;
export namespace Monitor {
@@ -193,9 +193,9 @@ export namespace Session {
*/
public killSession = async (reason: string, graceful = true, errorCode = 0) => {
this.mainLog(cyan(`exiting session ${graceful ? "clean" : "immediate"}ly`));
- this.mainLog(`reason: ${(red(reason))}`);
- await this.executeExitHandlers(null);
- this.killActiveWorker(graceful);
+ this.mainLog(`session exit reason: ${(red(reason))}`);
+ await this.executeExitHandlers(true);
+ this.killActiveWorker(graceful, true);
process.exit(errorCode);
}
@@ -341,18 +341,25 @@ export namespace Session {
* is not specified by the configuration is given the default counterpart. If, within an object,
* one peer is given by configuration and two are not, the one is preserved while the two are given
* the default value.
+ * @returns the composition of all of the assigned objects, much like Object.assign(), but with more
+ * granularity in the overwriting of nested objects
*/
- private assign = (defaultObject: any, specifiedObject: any, collector: any) => {
- Array.from(new Set([...Object.keys(defaultObject), ...Object.keys(specifiedObject)])).map(property => {
- let defaultValue: any, specifiedValue: any;
- if (specifiedValue = specifiedObject[property]) {
- if (typeof specifiedValue === "object" && typeof (defaultValue = defaultObject[property]) === "object") {
- this.assign(defaultValue, specifiedValue, collector[property] = {});
+ private preciseAssign = (target: any, ...sources: any[]): any => {
+ for (const source of sources) {
+ this.preciseAssignHelper(target, source);
+ }
+ return target;
+ }
+
+ private preciseAssignHelper = (target: any, source: any) => {
+ Array.from(new Set([...Object.keys(target), ...Object.keys(source)])).map(property => {
+ let targetValue: any, sourceValue: any;
+ if (sourceValue = source[property]) {
+ if (typeof sourceValue === "object" && typeof (targetValue = target[property]) === "object") {
+ this.preciseAssignHelper(targetValue, sourceValue);
} else {
- collector[property] = specifiedValue;
+ target[property] = sourceValue;
}
- } else {
- collector[property] = defaultObject[property];
}
});
}
@@ -372,9 +379,7 @@ export namespace Session {
};
// ensure all necessary and no excess information is specified by the configuration file
validate(config, configurationSchema, options);
- const results: any = {};
- this.assign(defaultConfig, config, results);
- config = results;
+ config = this.preciseAssign({}, defaultConfig, config);
} catch (error) {
if (error instanceof ValidationError) {
console.log(red("\nSession configuration failed."));
@@ -384,7 +389,7 @@ export namespace Session {
} else if (error.code === "ENOENT" && error.path === "./session.config.json") {
console.log(cyan("Loading default session parameters..."));
console.log("Consider including a session.config.json configuration file in your project root for customization.");
- config = { ...defaultConfig };
+ config = this.preciseAssign({}, defaultConfig);
} else {
console.log(red("\nSession configuration failed."));
console.log("The following unknown error occurred during configuration.");
@@ -429,15 +434,15 @@ export namespace Session {
return repl;
}
- private executeExitHandlers = async (reason: Error | null) => Promise.all(this.exitHandlers.map(handler => handler(reason)));
+ private executeExitHandlers = async (reason: Error | boolean) => Promise.all(this.exitHandlers.map(handler => handler(reason)));
/**
* Attempts to kill the active worker gracefully, unless otherwise specified.
*/
- private killActiveWorker = (graceful = true): void => {
+ private killActiveWorker = (graceful = true, isSessionEnd = false): void => {
if (this.activeWorker && !this.activeWorker.isDead()) {
if (graceful) {
- this.activeWorker.send({ manualExit: true });
+ this.activeWorker.send({ manualExit: { isSessionEnd } });
} else {
this.activeWorker.process.kill();
}
@@ -577,7 +582,7 @@ export namespace Session {
public sendMonitorAction = (message: string, args?: any) => process.send!({ action: { message, args } });
private constructor(work: Function) {
- this.lifecycleNotification(green(`initializing process... (${white(`${process.execPath} ${process.execArgv.join(" ")}`)})`));
+ this.lifecycleNotification(green(`initializing process... ${white(`[${process.execPath} ${process.execArgv.join(" ")}]`)}`));
const { pollingRoute, serverPort, pollingIntervalSeconds, pollingFailureTolerance } = process.env;
this.serverPort = Number(serverPort);
@@ -601,21 +606,25 @@ export namespace Session {
this.pollingIntervalSeconds = newPollingIntervalSeconds;
}
if (manualExit !== undefined) {
- await this.executeExitHandlers(null);
+ const { isSessionEnd } = manualExit;
+ await this.executeExitHandlers(isSessionEnd);
process.exit(0);
}
});
// one reason to exit, as the process might be in an inconsistent state after such an exception
process.on('uncaughtException', this.proactiveUnplannedExit);
- process.on('unhandledRejection', this.proactiveUnplannedExit);
+ process.on('unhandledRejection', reason => {
+ const appropriateError = reason instanceof Error ? reason : new Error(`unhandled rejection: ${reason}`);
+ this.proactiveUnplannedExit(appropriateError);
+ });
}
/**
* Execute the list of functions registered to be called
* whenever the process exits.
*/
- private executeExitHandlers = async (reason: Error | null) => Promise.all(this.exitHandlers.map(handler => handler(reason)));
+ private executeExitHandlers = async (reason: Error | boolean) => Promise.all(this.exitHandlers.map(handler => handler(reason)));
/**
* Notify master thread (which will log update in the console) of initialization via IPC.
@@ -626,14 +635,14 @@ export namespace Session {
* Called whenever the process has a reason to terminate, either through an uncaught exception
* in the process (potentially inconsistent state) or the server cannot be reached.
*/
- private proactiveUnplannedExit = async (error: any): Promise<void> => {
+ private proactiveUnplannedExit = async (error: Error): Promise<void> => {
this.shouldServerBeResponsive = false;
// communicates via IPC to the master thread that it should dispatch a crash notification email
this.sendMonitorAction("notify_crash", { error });
await this.executeExitHandlers(error);
// notify master thread (which will log update in the console) of crash event via IPC
this.lifecycleNotification(red(`crash event detected @ ${new Date().toUTCString()}`));
- this.lifecycleNotification(red(error.message || error));
+ this.lifecycleNotification(red(error.message));
process.exit(1);
}
@@ -651,7 +660,6 @@ export namespace Session {
this.lifecycleNotification(green(`listening on ${this.serverPort}...`));
}
this.shouldServerBeResponsive = true;
- resolve();
} catch (error) {
// if we expect the server to be unavailable, i.e. during compilation,
// the listening variable is false, activeExit will return early and the child
@@ -663,6 +671,8 @@ export namespace Session {
this.lifecycleNotification(yellow(`the server has encountered ${this.pollingFailureCount} of ${this.pollingFailureTolerance} tolerable failures`));
}
}
+ } finally {
+ resolve();
}
}, 1000 * this.pollingIntervalSeconds);
});