From 80d4bf70cb8e29f15d8e51b1e0bd378a26d68fcf Mon Sep 17 00:00:00 2001
From: vellichora <fangrui_tong@brown.edu>
Date: Tue, 7 Jan 2020 03:07:18 -0500
Subject: converted tool tip text menu to react component with basic marks and
 dropdown

---
 src/client/util/ProseMirrorEditorView.tsx | 74 +++++++++++++++++++++++++++++++
 1 file changed, 74 insertions(+)
 create mode 100644 src/client/util/ProseMirrorEditorView.tsx

(limited to 'src/client/util/ProseMirrorEditorView.tsx')

diff --git a/src/client/util/ProseMirrorEditorView.tsx b/src/client/util/ProseMirrorEditorView.tsx
new file mode 100644
index 000000000..3e5fd0604
--- /dev/null
+++ b/src/client/util/ProseMirrorEditorView.tsx
@@ -0,0 +1,74 @@
+import React from "react";
+import { EditorView } from "prosemirror-view";
+import { EditorState } from "prosemirror-state";
+
+export interface ProseMirrorEditorViewProps {
+    /* EditorState instance to use. */
+    editorState: EditorState;
+    /* Called when EditorView produces new EditorState. */
+    onEditorState: (editorState: EditorState) => any;
+}
+
+/**
+ * This wraps ProseMirror's EditorView into React component.
+ * This code was found on https://discuss.prosemirror.net/t/using-with-react/904
+ */
+export class ProseMirrorEditorView extends React.Component<ProseMirrorEditorViewProps>  {
+
+    private _editorView?: EditorView;
+
+    _createEditorView = (element: HTMLDivElement | null) => {
+        if (element != null) {
+            this._editorView = new EditorView(element, {
+                state: this.props.editorState,
+                dispatchTransaction: this.dispatchTransaction,
+            });
+        }
+    };
+
+    dispatchTransaction = (tx: any) => {
+        // In case EditorView makes any modification to a state we funnel those
+        // modifications up to the parent and apply to the EditorView itself.
+        const editorState = this.props.editorState.apply(tx);
+        if (this._editorView != null) {
+            this._editorView.updateState(editorState);
+        }
+        this.props.onEditorState(editorState);
+    };
+
+    focus() {
+        if (this._editorView) {
+            this._editorView.focus();
+        }
+    }
+
+    componentWillReceiveProps(nextProps: { editorState: EditorState<any>; }) {
+        // In case we receive new EditorState through props — we apply it to the
+        // EditorView instance.
+        if (this._editorView) {
+            if (nextProps.editorState !== this.props.editorState) {
+                this._editorView.updateState(nextProps.editorState);
+            }
+        }
+    }
+
+    componentWillUnmount() {
+        if (this._editorView) {
+            this._editorView.destroy();
+        }
+    }
+
+    shouldComponentUpdate() {
+        // Note that EditorView manages its DOM itself so we'd ratrher don't mess
+        // with it.
+        return false;
+    }
+
+    render() {
+        // Render just an empty div which is then used as a container for an
+        // EditorView instance.
+        return (
+            <div ref={this._createEditorView} />
+        );
+    }
+}
\ No newline at end of file
-- 
cgit v1.2.3-70-g09d2


From 6162c951e07874fbb12717d4bcd2cd649e41d0d2 Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samwilkins333@gmail.com>
Date: Mon, 13 Jan 2020 00:00:56 -0500
Subject: deleted old Session folder and fixed linter errors

---
 src/client/util/ProseMirrorEditorView.tsx          |   8 +-
 src/client/util/RichTextMenu.tsx                   |  13 +-
 src/client/views/EditableView.tsx                  |   2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |   4 +-
 src/client/views/linking/LinkMenuGroup.tsx         |   2 +-
 src/client/views/nodes/DocumentView.tsx            |   2 +-
 src/client/views/nodes/FormattedTextBox.tsx        |  23 +-
 src/pen-gestures/ndollar.ts                        |  66 +-
 src/server/MemoryDatabase.ts                       |   7 +-
 src/server/RouteManager.ts                         |   3 +-
 src/server/Session/session.ts                      | 686 ---------------------
 11 files changed, 67 insertions(+), 749 deletions(-)
 delete mode 100644 src/server/Session/session.ts

(limited to 'src/client/util/ProseMirrorEditorView.tsx')

diff --git a/src/client/util/ProseMirrorEditorView.tsx b/src/client/util/ProseMirrorEditorView.tsx
index 3e5fd0604..b42adfbb4 100644
--- a/src/client/util/ProseMirrorEditorView.tsx
+++ b/src/client/util/ProseMirrorEditorView.tsx
@@ -18,23 +18,23 @@ export class ProseMirrorEditorView extends React.Component<ProseMirrorEditorView
     private _editorView?: EditorView;
 
     _createEditorView = (element: HTMLDivElement | null) => {
-        if (element != null) {
+        if (element !== null) {
             this._editorView = new EditorView(element, {
                 state: this.props.editorState,
                 dispatchTransaction: this.dispatchTransaction,
             });
         }
-    };
+    }
 
     dispatchTransaction = (tx: any) => {
         // In case EditorView makes any modification to a state we funnel those
         // modifications up to the parent and apply to the EditorView itself.
         const editorState = this.props.editorState.apply(tx);
-        if (this._editorView != null) {
+        if (this._editorView) {
             this._editorView.updateState(editorState);
         }
         this.props.onEditorState(editorState);
-    };
+    }
 
     focus() {
         if (this._editorView) {
diff --git a/src/client/util/RichTextMenu.tsx b/src/client/util/RichTextMenu.tsx
index 639772faa..419d7caf9 100644
--- a/src/client/util/RichTextMenu.tsx
+++ b/src/client/util/RichTextMenu.tsx
@@ -175,7 +175,7 @@ export default class RichTextMenu extends AntimodeMenu {
     setMark = (mark: Mark, state: EditorState<any>, dispatch: any) => {
         if (mark) {
             const node = (state.selection as NodeSelection).node;
-            if (node ?.type === schema.nodes.ordered_list) {
+            if (node?.type === schema.nodes.ordered_list) {
                 let attrs = node.attrs;
                 if (mark.type === schema.marks.pFontFamily) attrs = { ...attrs, setFontFamily: mark.attrs.family };
                 if (mark.type === schema.marks.pFontSize) attrs = { ...attrs, setFontSize: mark.attrs.fontSize };
@@ -294,8 +294,8 @@ export default class RichTextMenu extends AntimodeMenu {
             e.preventDefault();
             e.stopPropagation();
             self.view && self.view.focus();
-            self.view && command && command(self.view!.state, self.view!.dispatch, self.view);
-            self.view && onclick && onclick(self.view!.state, self.view!.dispatch, self.view);
+            self.view && command && command(self.view.state, self.view.dispatch, self.view);
+            self.view && onclick && onclick(self.view.state, self.view.dispatch, self.view);
             self.setActiveMarkButtons(self.getActiveMarksOnSelection());
         }
 
@@ -602,7 +602,7 @@ export default class RichTextMenu extends AntimodeMenu {
 
         const link = this.currentLink ? this.currentLink : "";
 
-        const button = <FontAwesomeIcon icon="link" size="lg" />
+        const button = <FontAwesomeIcon icon="link" size="lg" />;
 
         const dropdownContent =
             <div className="dropdown link-menu">
@@ -684,8 +684,9 @@ export default class RichTextMenu extends AntimodeMenu {
                 }
             } else {
                 if (node) {
-                    const extension = this.linkExtend(this.view!.state.selection.$anchor, href);
-                    this.view!.dispatch(this.view!.state.tr.removeMark(extension.from, extension.to, this.view!.state.schema.marks.link));
+                    const { tr, schema, selection } = this.view.state;
+                    const extension = this.linkExtend(selection.$anchor, href);
+                    this.view.dispatch(tr.removeMark(extension.from, extension.to, schema.marks.link));
                 }
             }
         }
diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx
index 54def38b5..faf02b946 100644
--- a/src/client/views/EditableView.tsx
+++ b/src/client/views/EditableView.tsx
@@ -36,7 +36,7 @@ export interface EditableProps {
         resetValue: () => void;
         value: string,
         onChange: (e: React.ChangeEvent, { newValue }: { newValue: string }) => void,
-        autosuggestProps: Autosuggest.AutosuggestProps<string>
+        autosuggestProps: Autosuggest.AutosuggestProps<string, any>
 
     };
     oneLine?: boolean;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 936c4413f..e3780261d 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -988,8 +988,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         </MarqueeView>;
     }
     @computed get contentScaling() {
-        let hscale = this.nativeHeight ? this.props.PanelHeight() / this.nativeHeight : 1;
-        let wscale = this.nativeWidth ? this.props.PanelWidth() / this.nativeWidth : 1;
+        const hscale = this.nativeHeight ? this.props.PanelHeight() / this.nativeHeight : 1;
+        const wscale = this.nativeWidth ? this.props.PanelWidth() / this.nativeWidth : 1;
         return wscale < hscale ? wscale : hscale;
     }
     render() {
diff --git a/src/client/views/linking/LinkMenuGroup.tsx b/src/client/views/linking/LinkMenuGroup.tsx
index abd17ec4d..ace9a9e4c 100644
--- a/src/client/views/linking/LinkMenuGroup.tsx
+++ b/src/client/views/linking/LinkMenuGroup.tsx
@@ -47,7 +47,7 @@ export class LinkMenuGroup extends React.Component<LinkMenuGroupProps> {
             document.removeEventListener("pointerup", this.onLinkButtonUp);
 
             const targets = this.props.group.map(l => LinkManager.Instance.getOppositeAnchor(l, this.props.sourceDoc)).filter(d => d) as Doc[];
-            DragManager.StartLinkTargetsDrag(this._drag.current!, e.x, e.y, this.props.sourceDoc, targets);
+            DragManager.StartLinkTargetsDrag(this._drag.current, e.x, e.y, this.props.sourceDoc, targets);
         }
         e.stopPropagation();
     }
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 10d2e2b3e..ba35366ff 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -196,7 +196,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 ScriptBox.EditButtonScript("On Button Clicked ...", this.props.Document, "onClick", e.clientX, e.clientY);
             } else if (this.props.Document.isButton === "Selector") {  // this should be moved to an OnClick script
                 FormattedTextBoxComment.Hide();
-                this.Document.links?.[0] instanceof Doc && (Doc.UserDoc().SelectedDocs = new List([Doc.LinkOtherAnchor(this.Document.links[0]!, this.props.Document)]));
+                this.Document.links?.[0] instanceof Doc && (Doc.UserDoc().SelectedDocs = new List([Doc.LinkOtherAnchor(this.Document.links[0], this.props.Document)]));
             } else if (this.Document.isButton) {
                 SelectionManager.SelectDoc(this, e.ctrlKey); // don't think this should happen if a button action is actually triggered.
                 this.buttonClick(e.altKey, e.ctrlKey);
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 8e28cf928..8b5c24878 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -906,15 +906,16 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
         this.tryUpdateHeight();
 
         // see if we need to preserve the insertion point
-        const prosediv = this.ProseRef ?.children ?.[0] as any;
-        const keeplocation = prosediv ?.keeplocation;
+        const prosediv = this.ProseRef?.children?.[0] as any;
+        const keeplocation = prosediv?.keeplocation;
         prosediv && (prosediv.keeplocation = undefined);
-        const pos = this._editorView ?.state.selection.$from.pos || 1;
-        keeplocation && setTimeout(() => this._editorView ?.dispatch(this._editorView ?.state.tr.setSelection(TextSelection.create(this._editorView.state.doc, pos))));
+        const pos = this._editorView?.state.selection.$from.pos || 1;
+        keeplocation && setTimeout(() => this._editorView?.dispatch(this._editorView?.state.tr.setSelection(TextSelection.create(this._editorView.state.doc, pos))));
 
         // jump rich text menu to this textbox
-        if (this._ref.current) {
-            const x = Math.min(Math.max(this._ref.current!.getBoundingClientRect().left, 0), window.innerWidth - RichTextMenu.Instance.width);
+        const { current } = this._ref;
+        if (current) {
+            const x = Math.min(Math.max(current.getBoundingClientRect().left, 0), window.innerWidth - RichTextMenu.Instance.width);
             const y = this._ref.current!.getBoundingClientRect().top - RichTextMenu.Instance.height - 50;
             RichTextMenu.Instance.jumpTo(x, y);
         }
@@ -933,7 +934,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
         if ((this._editorView!.root as any).getSelection().isCollapsed) { // this is a hack to allow the cursor to be placed at the end of a document when the document ends in an inline dash comment.  Apparently Chrome on Windows has a bug/feature which breaks this when clicking after the end of the text.
             const pcords = this._editorView!.posAtCoords({ left: e.clientX, top: e.clientY });
             const node = pcords && this._editorView!.state.doc.nodeAt(pcords.pos); // get what prosemirror thinks the clicked node is (if it's null, then we didn't click on any text)
-            if (pcords && node ?.type === this._editorView!.state.schema.nodes.dashComment) {
+            if (pcords && node?.type === this._editorView!.state.schema.nodes.dashComment) {
                 this._editorView!.dispatch(this._editorView!.state.tr.setSelection(TextSelection.create(this._editorView!.state.doc, pcords.pos + 2)));
                 e.preventDefault();
             }
@@ -996,7 +997,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
                 for (let off = 1; off < 100; off++) {
                     const pos = this._editorView!.posAtCoords({ left: x + off, top: y });
                     const node = pos && this._editorView!.state.doc.nodeAt(pos.pos);
-                    if (node ?.type === schema.nodes.list_item) {
+                    if (node?.type === schema.nodes.list_item) {
                         list_node = node;
                         break;
                     }
@@ -1087,7 +1088,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
         }
         if (e.key === "Escape") {
             this._editorView!.dispatch(state.tr.setSelection(TextSelection.create(state.doc, state.selection.from, state.selection.from)));
-            (document.activeElement as any).blur ?.();
+            (document.activeElement as any).blur?.();
             SelectionManager.DeselectAll();
         }
         e.stopPropagation();
@@ -1109,7 +1110,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
 
     @action
     tryUpdateHeight(limitHeight?: number) {
-        let scrollHeight = this._ref.current ?.scrollHeight;
+        let scrollHeight = this._ref.current?.scrollHeight;
         if (!this.layoutDoc.animateToPos && this.layoutDoc.autoHeight && scrollHeight &&
             getComputedStyle(this._ref.current!.parentElement!).top === "0px") {  // if top === 0, then the text box is growing upward (as the overlay caption) which doesn't contribute to the height computation
             if (limitHeight && scrollHeight > limitHeight) {
@@ -1171,7 +1172,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
                 {this.props.Document.hideSidebar ? (null) : this.sidebarWidthPercent === "0%" ?
                     <div className="formattedTextBox-sidebar-handle" onPointerDown={this.sidebarDown} onClick={e => this.toggleSidebar()} /> :
                     <div className={"formattedTextBox-sidebar" + (InkingControl.Instance.selectedTool !== InkTool.None ? "-inking" : "")}
-                        style={{ width: `${this.sidebarWidthPercent}`, backgroundColor: `${StrCast(this.extensionDoc ?.backgroundColor, "transparent")}` }}>
+                        style={{ width: `${this.sidebarWidthPercent}`, backgroundColor: `${StrCast(this.extensionDoc?.backgroundColor, "transparent")}` }}>
                         <CollectionFreeFormView {...this.props}
                             PanelHeight={this.props.PanelHeight}
                             PanelWidth={() => this.sidebarWidth}
diff --git a/src/pen-gestures/ndollar.ts b/src/pen-gestures/ndollar.ts
index 872c524d6..ef5ca38c6 100644
--- a/src/pen-gestures/ndollar.ts
+++ b/src/pen-gestures/ndollar.ts
@@ -257,16 +257,16 @@ export class NDollarRecognizer {
         {
             if (!requireSameNoOfStrokes || strokes.length === this.Multistrokes[i].NumStrokes) // optional -- only attempt match when same # of component strokes
             {
-                for (var j = 0; j < this.Multistrokes[i].Unistrokes.length; j++) // for each unistroke within this multistroke
+                for (const unistroke of this.Multistrokes[i].Unistrokes) // for each unistroke within this multistroke
                 {
-                    if (AngleBetweenUnitVectors(candidate.StartUnitVector, this.Multistrokes[i].Unistrokes[j].StartUnitVector) <= AngleSimilarityThreshold) // strokes start in the same direction
+                    if (AngleBetweenUnitVectors(candidate.StartUnitVector, unistroke.StartUnitVector) <= AngleSimilarityThreshold) // strokes start in the same direction
                     {
                         var d;
                         if (useProtractor) {
-                            d = OptimalCosineDistance(this.Multistrokes[i].Unistrokes[j].Vector, candidate.Vector); // Protractor
+                            d = OptimalCosineDistance(unistroke.Vector, candidate.Vector); // Protractor
                         }
                         else {
-                            d = DistanceAtBestAngle(candidate.Points, this.Multistrokes[i].Unistrokes[j], -AngleRange, +AngleRange, AnglePrecision); // Golden Section Search (original $N)
+                            d = DistanceAtBestAngle(candidate.Points, unistroke, -AngleRange, +AngleRange, AnglePrecision); // Golden Section Search (original $N)
                         }
                         if (d < b) {
                             b = d; // best (least) distance
@@ -283,8 +283,8 @@ export class NDollarRecognizer {
     AddGesture = (name: string, useBoundedRotationInvariance: boolean, strokes: any[]) => {
         this.Multistrokes[this.Multistrokes.length] = new Multistroke(name, useBoundedRotationInvariance, strokes);
         var num = 0;
-        for (var i = 0; i < this.Multistrokes.length; i++) {
-            if (this.Multistrokes[i].Name === name) {
+        for (const multistroke of this.Multistrokes) {
+            if (multistroke.Name === name) {
                 num++;
             }
         }
@@ -322,20 +322,20 @@ function HeapPermute(n: number, order: any[], /*out*/ orders: any[]) {
 
 function MakeUnistrokes(strokes: any, orders: any) {
     const unistrokes = new Array(); // array of point arrays
-    for (var r = 0; r < orders.length; r++) {
-        for (var b = 0; b < Math.pow(2, orders[r].length); b++) // use b's bits for directions
+    for (const order of orders) {
+        for (var b = 0; b < Math.pow(2, order.length); b++) // use b's bits for directions
         {
             const unistroke = new Array(); // array of points
-            for (var i = 0; i < orders[r].length; i++) {
+            for (var i = 0; i < order.length; i++) {
                 var pts;
                 if (((b >> i) & 1) === 1) {// is b's bit at index i on?
-                    pts = strokes[orders[r][i]].slice().reverse(); // copy and reverse
+                    pts = strokes[order[i]].slice().reverse(); // copy and reverse
                 }
                 else {
-                    pts = strokes[orders[r][i]].slice(); // copy
+                    pts = strokes[order[i]].slice(); // copy
                 }
-                for (var p = 0; p < pts.length; p++) {
-                    unistroke[unistroke.length] = pts[p]; // append points
+                for (const point of pts) {
+                    unistroke[unistroke.length] = point; // append points
                 }
             }
             unistrokes[unistrokes.length] = unistroke; // add one unistroke to set
@@ -346,9 +346,9 @@ function MakeUnistrokes(strokes: any, orders: any) {
 
 function CombineStrokes(strokes: any) {
     const points = new Array();
-    for (var s = 0; s < strokes.length; s++) {
-        for (var p = 0; p < strokes[s].length; p++) {
-            points[points.length] = new Point(strokes[s][p].X, strokes[s][p].Y);
+    for (const stroke of strokes) {
+        for (const { X, Y } of stroke) {
+            points[points.length] = new Point(X, Y);
         }
     }
     return points;
@@ -384,9 +384,9 @@ function RotateBy(points: any, radians: any) // rotates points around centroid
     const cos = Math.cos(radians);
     const sin = Math.sin(radians);
     const newpoints = new Array();
-    for (var i = 0; i < points.length; i++) {
-        const qx = (points[i].X - c.X) * cos - (points[i].Y - c.Y) * sin + c.X;
-        const qy = (points[i].X - c.X) * sin + (points[i].Y - c.Y) * cos + c.Y;
+    for (const point of points) {
+        const qx = (point.X - c.X) * cos - (point.Y - c.Y) * sin + c.X;
+        const qy = (point.X - c.X) * sin + (point.Y - c.Y) * cos + c.Y;
         newpoints[newpoints.length] = new Point(qx, qy);
     }
     return newpoints;
@@ -396,9 +396,9 @@ function ScaleDimTo(points: any, size: any, ratio1D: any) // scales bbox uniform
     const B = BoundingBox(points);
     const uniformly = Math.min(B.Width / B.Height, B.Height / B.Width) <= ratio1D; // 1D or 2D gesture test
     const newpoints = new Array();
-    for (var i = 0; i < points.length; i++) {
-        const qx = uniformly ? points[i].X * (size / Math.max(B.Width, B.Height)) : points[i].X * (size / B.Width);
-        const qy = uniformly ? points[i].Y * (size / Math.max(B.Width, B.Height)) : points[i].Y * (size / B.Height);
+    for (const { X, Y } of points) {
+        const qx = uniformly ? X * (size / Math.max(B.Width, B.Height)) : X * (size / B.Width);
+        const qy = uniformly ? Y * (size / Math.max(B.Width, B.Height)) : Y * (size / B.Height);
         newpoints[newpoints.length] = new Point(qx, qy);
     }
     return newpoints;
@@ -407,9 +407,9 @@ function TranslateTo(points: any, pt: any) // translates points' centroid
 {
     const c = Centroid(points);
     const newpoints = new Array();
-    for (var i = 0; i < points.length; i++) {
-        const qx = points[i].X + pt.X - c.X;
-        const qy = points[i].Y + pt.Y - c.Y;
+    for (const { X, Y } of points) {
+        const qx = X + pt.X - c.X;
+        const qy = Y + pt.Y - c.Y;
         newpoints[newpoints.length] = new Point(qx, qy);
     }
     return newpoints;
@@ -478,9 +478,9 @@ function DistanceAtAngle(points: any, T: any, radians: any) {
 }
 function Centroid(points: any) {
     var x = 0.0, y = 0.0;
-    for (var i = 0; i < points.length; i++) {
-        x += points[i].X;
-        y += points[i].Y;
+    for (const point of points) {
+        x += point.X;
+        y += point.Y;
     }
     x /= points.length;
     y /= points.length;
@@ -488,11 +488,11 @@ function Centroid(points: any) {
 }
 function BoundingBox(points: any) {
     var minX = +Infinity, maxX = -Infinity, minY = +Infinity, maxY = -Infinity;
-    for (var i = 0; i < points.length; i++) {
-        minX = Math.min(minX, points[i].X);
-        minY = Math.min(minY, points[i].Y);
-        maxX = Math.max(maxX, points[i].X);
-        maxY = Math.max(maxY, points[i].Y);
+    for (const { X, Y } of points) {
+        minX = Math.min(minX, X);
+        minY = Math.min(minY, Y);
+        maxX = Math.max(maxX, X);
+        maxY = Math.max(maxY, Y);
     }
     return new Rectangle(minX, minY, maxX - minX, maxY - minY);
 }
diff --git a/src/server/MemoryDatabase.ts b/src/server/MemoryDatabase.ts
index e7396babf..543f96e7f 100644
--- a/src/server/MemoryDatabase.ts
+++ b/src/server/MemoryDatabase.ts
@@ -7,7 +7,7 @@ export class MemoryDatabase implements IDatabase {
     private db: { [collectionName: string]: { [id: string]: any } } = {};
 
     private getCollection(collectionName: string) {
-        let collection = this.db[collectionName];
+        const collection = this.db[collectionName];
         if (collection) {
             return collection;
         } else {
@@ -17,9 +17,10 @@ export class MemoryDatabase implements IDatabase {
 
     public update(id: string, value: any, callback: (err: mongodb.MongoError, res: mongodb.UpdateWriteOpResult) => void, _upsert?: boolean, collectionName = DocumentsCollection): Promise<void> {
         const collection = this.getCollection(collectionName);
-        if ("$set" in value) {
+        const set = "$set";
+        if (set in value) {
             let currentVal = collection[id] ?? (collection[id] = {});
-            const val = value["$set"];
+            const val = value[set];
             for (const key in val) {
                 const keys = key.split(".");
                 for (let i = 0; i < keys.length - 1; i++) {
diff --git a/src/server/RouteManager.ts b/src/server/RouteManager.ts
index a7ee405a7..5afd607fd 100644
--- a/src/server/RouteManager.ts
+++ b/src/server/RouteManager.ts
@@ -86,7 +86,8 @@ export default class RouteManager {
         const { method, subscription, secureHandler: onValidation, publicHandler: onUnauthenticated, errorHandler: onError } = initializer;
         const isRelease = this._isRelease;
         const supervised = async (req: express.Request, res: express.Response) => {
-            let { user, originalUrl: target } = req;
+            let { user } = req;
+            const { originalUrl: target } = req;
             if (process.env.DB === "MEM" && !user) {
                 user = { id: "guest", email: "", userDocumentId: "guestDocId" };
             }
diff --git a/src/server/Session/session.ts b/src/server/Session/session.ts
deleted file mode 100644
index ec3d46ac1..000000000
--- a/src/server/Session/session.ts
+++ /dev/null
@@ -1,686 +0,0 @@
-import { red, cyan, green, yellow, magenta, blue, white, Color, grey, gray, black } from "colors";
-import { on, fork, setupMaster, Worker, isMaster, isWorker } from "cluster";
-import { get } from "request-promise";
-import { Utils } from "../../Utils";
-import Repl, { ReplAction } from "../repl";
-import { readFileSync } from "fs";
-import { validate, ValidationError } from "jsonschema";
-import { configurationSchema } from "./session_config_schema";
-import { exec, ExecOptions } from "child_process";
-
-/**
- * This namespace relies on NodeJS's cluster module, which allows a parent (master) process to share
- * code with its children (workers). A simple `isMaster` flag indicates who is trying to access
- * the code, and thus determines the functionality that actually gets invoked (checked by the caller, not internally).
- * 
- * Think of the master thread as a factory, and the workers as the helpers that actually run the server.
- * 
- * So, when we run `npm start`, given the appropriate check, initializeMaster() is called in the parent process
- * This will spawn off its own child process (by default, mirrors the execution path of its parent),
- * in which initializeWorker() is invoked.
- */
-export namespace Session {
-
-    type ColorLabel = "yellow" | "red" | "cyan" | "green" | "blue" | "magenta" | "grey" | "gray" | "white" | "black";
-    const colorMapping: Map<ColorLabel, Color> = new Map([
-        ["yellow", yellow],
-        ["red", red],
-        ["cyan", cyan],
-        ["green", green],
-        ["blue", blue],
-        ["magenta", magenta],
-        ["grey", grey],
-        ["gray", gray],
-        ["white", white],
-        ["black", black]
-    ]);
-
-    export abstract class AppliedSessionAgent {
-
-        // the following two methods allow the developer to create a custom
-        // session and use the built in customization options for each thread
-        protected abstract async launchMonitor(): Promise<Session.Monitor>;
-        protected abstract async launchServerWorker(): Promise<Session.ServerWorker>;
-
-        private launched = false;
-
-        public killSession = (reason: string, graceful = true, errorCode = 0) => {
-            const target = isMaster ? this.sessionMonitor : this.serverWorker;
-            target.killSession(reason, graceful, errorCode);
-        }
-
-        private sessionMonitorRef: Session.Monitor | undefined;
-        public get sessionMonitor(): Session.Monitor {
-            if (!isMaster) {
-                this.serverWorker.sendMonitorAction("kill", {
-                    graceful: false,
-                    reason: "Cannot access the session monitor directly from the server worker thread.",
-                    errorCode: 1
-                });
-                throw new Error();
-            }
-            return this.sessionMonitorRef!;
-        }
-
-        private serverWorkerRef: Session.ServerWorker | undefined;
-        public get serverWorker(): Session.ServerWorker {
-            if (isMaster) {
-                throw new Error("Cannot access the server worker directly from the session monitor thread");
-            }
-            return this.serverWorkerRef!;
-        }
-
-        public async launch(): Promise<void> {
-            if (!this.launched) {
-                this.launched = true;
-                if (isMaster) {
-                    this.sessionMonitorRef = await this.launchMonitor();
-                } else {
-                    this.serverWorkerRef = await this.launchServerWorker();
-                }
-            } else {
-                throw new Error("Cannot launch a session thread more than once per process.");
-            }
-        }
-
-    }
-
-    interface Identifier {
-        text: string;
-        color: ColorLabel;
-    }
-
-    interface Identifiers {
-        master: Identifier;
-        worker: Identifier;
-        exec: Identifier;
-    }
-
-    interface Configuration {
-        showServerOutput: boolean;
-        identifiers: Identifiers;
-        ports: { [description: string]: number };
-        polling: {
-            route: string;
-            intervalSeconds: number;
-            failureTolerance: number;
-        };
-    }
-
-    const defaultConfig: Configuration = {
-        showServerOutput: false,
-        identifiers: {
-            master: {
-                text: "__monitor__",
-                color: "yellow"
-            },
-            worker: {
-                text: "__server__",
-                color: "magenta"
-            },
-            exec: {
-                text: "__exec__",
-                color: "green"
-            }
-        },
-        ports: { server: 3000 },
-        polling: {
-            route: "/",
-            intervalSeconds: 30,
-            failureTolerance: 0
-        }
-    };
-
-    export type ExitHandler = (reason: Error | boolean) => void | Promise<void>;
-
-    export namespace Monitor {
-
-        export interface NotifierHooks {
-            key?: (key: string) => (boolean | Promise<boolean>);
-            crash?: (error: Error) => (boolean | Promise<boolean>);
-        }
-
-        export interface Action {
-            message: string;
-            args: any;
-        }
-
-        export type ServerMessageHandler = (action: Action) => void | Promise<void>;
-
-    }
-
-    /**
-     * 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 class Monitor {
-
-        private static count = 0;
-        private exitHandlers: ExitHandler[] = [];
-        private readonly notifiers: Monitor.NotifierHooks | undefined;
-        private readonly config: Configuration;
-        private onMessage: { [message: string]: Monitor.ServerMessageHandler[] | undefined } = {};
-        private activeWorker: Worker | undefined;
-        private key: string | undefined;
-        private repl: Repl;
-
-        public static Create(notifiers?: Monitor.NotifierHooks) {
-            if (isWorker) {
-                process.send?.({
-                    action: {
-                        message: "kill",
-                        args: {
-                            reason: "cannot create a monitor on the worker process.",
-                            graceful: false,
-                            errorCode: 1
-                        }
-                    }
-                });
-                process.exit(1);
-            } else if (++Monitor.count > 1) {
-                console.error(red("cannot create more than one monitor."));
-                process.exit(1);
-            } else {
-                return new Monitor(notifiers);
-            }
-        }
-
-        /**
-         * Kill this session and its active child
-         * server process, either gracefully (may wait
-         * indefinitely, but at least allows active networking
-         * requests to complete) or immediately.
-         */
-        public killSession = async (reason: string, graceful = true, errorCode = 0) => {
-            this.mainLog(cyan(`exiting session ${graceful ? "clean" : "immediate"}ly`));
-            this.mainLog(`session exit reason: ${(red(reason))}`);
-            await this.executeExitHandlers(true);
-            this.killActiveWorker(graceful, true);
-            process.exit(errorCode);
-        }
-
-        /**
-         * Execute the list of functions registered to be called
-         * whenever the process exits.
-         */
-        public addExitHandler = (handler: ExitHandler) => this.exitHandlers.push(handler);
-
-        /**
-         * Extend the default repl by adding in custom commands
-         * that can invoke application logic external to this module
-         */
-        public addReplCommand = (basename: string, argPatterns: (RegExp | string)[], action: ReplAction) => {
-            this.repl.registerCommand(basename, argPatterns, action);
-        }
-
-        public exec = (command: string, options?: ExecOptions) => {
-            return new Promise<void>(resolve => {
-                exec(command, { ...options, encoding: "utf8" }, (error, stdout, stderr) => {
-                    if (error) {
-                        this.execLog(red(`unable to execute ${white(command)}`));
-                        error.message.split("\n").forEach(line => line.length && this.execLog(red(`(error) ${line}`)));
-                    } else {
-                        let outLines: string[], errorLines: string[];
-                        if ((outLines = stdout.split("\n").filter(line => line.length)).length) {
-                            outLines.forEach(line => line.length && this.execLog(cyan(`(stdout) ${line}`)));
-                        }
-                        if ((errorLines = stderr.split("\n").filter(line => line.length)).length) {
-                            errorLines.forEach(line => line.length && this.execLog(yellow(`(stderr) ${line}`)));
-                        }
-                    }
-                    resolve();
-                });
-            });
-        }
-
-        /**
-         * Add a listener at this message. When the monitor process
-         * receives a message, it will invoke all registered functions.
-         */
-        public addServerMessageListener = (message: string, handler: Monitor.ServerMessageHandler) => {
-            const handlers = this.onMessage[message];
-            if (handlers) {
-                handlers.push(handler);
-            } else {
-                this.onMessage[message] = [handler];
-            }
-        }
-
-        /**
-         * Unregister a given listener at this message.
-         */
-        public removeServerMessageListener = (message: string, handler: Monitor.ServerMessageHandler) => {
-            const handlers = this.onMessage[message];
-            if (handlers) {
-                const index = handlers.indexOf(handler);
-                if (index > -1) {
-                    handlers.splice(index, 1);
-                }
-            }
-        }
-
-        /**
-         * Unregister all listeners at this message.
-         */
-        public clearServerMessageListeners = (message: string) => this.onMessage[message] = undefined;
-
-        private constructor(notifiers?: Monitor.NotifierHooks) {
-            this.notifiers = notifiers;
-
-            console.log(this.timestamp(), cyan("initializing session..."));
-
-            this.config = this.loadAndValidateConfiguration();
-
-            this.initializeSessionKey();
-            // determines whether or not we see the compilation / initialization / runtime output of each child server process
-            const output = this.config.showServerOutput ? "inherit" : "ignore";
-            setupMaster({ stdio: ["ignore", output, output, "ipc"] });
-
-            // handle exceptions in the master thread - there shouldn't be many of these
-            // the IPC (inter process communication) channel closed exception can't seem
-            // to be caught in a try catch, and is inconsequential, so it is ignored
-            process.on("uncaughtException", ({ message, stack }): void => {
-                if (message !== "Channel closed") {
-                    this.mainLog(red(message));
-                    if (stack) {
-                        this.mainLog(`uncaught exception\n${red(stack)}`);
-                    }
-                }
-            });
-
-            // a helpful cluster event called on the master thread each time a child process exits
-            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}`}.`;
-                this.mainLog(cyan(prompt));
-                // to make this a robust, continuous session, every time a child process dies, we immediately spawn a new one
-                this.spawn();
-            });
-
-            this.repl = this.initializeRepl();
-            this.spawn();
-        }
-
-        /**
-         * Generates a blue UTC string associated with the time
-         * of invocation.
-         */
-        private timestamp = () => blue(`[${new Date().toUTCString()}]`);
-
-        /**
-         * A formatted, identified and timestamped log in color
-         */
-        public mainLog = (...optionalParams: any[]) => {
-            console.log(this.timestamp(), this.config.identifiers.master.text, ...optionalParams);
-        }
-
-        /**
-         * A formatted, identified and timestamped log in color for non-
-         */
-        private execLog = (...optionalParams: any[]) => {
-            console.log(this.timestamp(), this.config.identifiers.exec.text, ...optionalParams);
-        }
-
-        /**
-         * If the caller has indicated an interest
-         * in being notified of this feature, creates
-         * a GUID for this session that can, for example,
-         * be used as authentication for killing the server
-         * (checked externally).
-         */
-        private initializeSessionKey = async (): Promise<void> => {
-            if (this.notifiers?.key) {
-                this.key = Utils.GenerateGuid();
-                const success = await this.notifiers.key(this.key);
-                const statement = success ? green("distributed session key to recipients") : red("distribution of session key failed");
-                this.mainLog(statement);
-            }
-        }
-
-        /**
-         * At any arbitrary layer of nesting within the configuration objects, any single value that
-         * 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 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 {
-                        target[property] = sourceValue;
-                    }
-                }
-            });
-        }
-
-        /**
-         * Reads in configuration .json file only once, in the master thread
-         * and pass down any variables the pertinent to the child processes as environment variables.
-         */
-        private loadAndValidateConfiguration = (): Configuration => {
-            let config: Configuration;
-            try {
-                console.log(this.timestamp(), cyan("validating configuration..."));
-                config = JSON.parse(readFileSync('./session.config.json', 'utf8'));
-                const options = {
-                    throwError: true,
-                    allowUnknownAttributes: false
-                };
-                // ensure all necessary and no excess information is specified by the configuration file
-                validate(config, configurationSchema, options);
-                config = this.preciseAssign({}, defaultConfig, config);
-            } catch (error) {
-                if (error instanceof ValidationError) {
-                    console.log(red("\nSession configuration failed."));
-                    console.log("The given session.config.json configuration file is invalid.");
-                    console.log(`${error.instance}: ${error.stack}`);
-                    process.exit(0);
-                } 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 = this.preciseAssign({}, defaultConfig);
-                } else {
-                    console.log(red("\nSession configuration failed."));
-                    console.log("The following unknown error occurred during configuration.");
-                    console.log(error.stack);
-                    process.exit(0);
-                }
-            } finally {
-                const { identifiers } = config!;
-                Object.keys(identifiers).forEach(key => {
-                    const resolved = key as keyof Identifiers;
-                    const { text, color } = identifiers[resolved];
-                    identifiers[resolved].text = (colorMapping.get(color) || white)(`${text}:`);
-                });
-                return config!;
-            }
-        }
-
-        /**
-         * Builds the repl that allows the following commands to be typed into stdin of the master thread.
-         */
-        private initializeRepl = (): Repl => {
-            const repl = new Repl({ identifier: () => `${this.timestamp()} ${this.config.identifiers.master.text}` });
-            const boolean = /true|false/;
-            const number = /\d+/;
-            const letters = /[a-zA-Z]+/;
-            repl.registerCommand("exit", [/clean|force/], args => this.killSession("manual exit requested by repl", args[0] === "clean", 0));
-            repl.registerCommand("restart", [/clean|force/], args => this.killActiveWorker(args[0] === "clean"));
-            repl.registerCommand("set", [letters, "port", number, boolean], args => this.setPort(args[0], Number(args[2]), args[3] === "true"));
-            repl.registerCommand("set", [/polling/, number, boolean], args => {
-                const newPollingIntervalSeconds = Math.floor(Number(args[2]));
-                if (newPollingIntervalSeconds < 0) {
-                    this.mainLog(red("the polling interval must be a non-negative integer"));
-                } else {
-                    if (newPollingIntervalSeconds !== this.config.polling.intervalSeconds) {
-                        this.config.polling.intervalSeconds = newPollingIntervalSeconds;
-                        if (args[3] === "true") {
-                            this.activeWorker?.send({ newPollingIntervalSeconds });
-                        }
-                    }
-                }
-            });
-            return repl;
-        }
-
-        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, isSessionEnd = false): void => {
-            if (this.activeWorker && !this.activeWorker.isDead()) {
-                if (graceful) {
-                    this.activeWorker.send({ manualExit: { isSessionEnd } });
-                } else {
-                    this.activeWorker.process.kill();
-                }
-            }
-        }
-
-        /**
-         * Allows the caller to set the port at which the target (be it the server,
-         * the websocket, some other custom port) is listening. If an immediate restart
-         * is specified, this monitor will kill the active child and re-launch the server
-         * at the port. Otherwise, the updated port won't be used until / unless the child
-         * dies on its own and triggers a restart.
-         */
-        private setPort = (port: "server" | "socket" | string, value: number, immediateRestart: boolean): void => {
-            if (value > 1023 && value < 65536) {
-                this.config.ports[port] = value;
-                if (immediateRestart) {
-                    this.killActiveWorker();
-                }
-            } else {
-                this.mainLog(red(`${port} is an invalid port number`));
-            }
-        }
-
-        /**
-         * Kills the current active worker and proceeds to spawn a new worker,
-         * feeding in configuration information as environment variables.
-         */
-        private spawn = (): void => {
-            const {
-                polling: {
-                    route,
-                    failureTolerance,
-                    intervalSeconds
-                },
-                ports
-            } = this.config;
-            this.killActiveWorker();
-            this.activeWorker = fork({
-                pollingRoute: route,
-                pollingFailureTolerance: failureTolerance,
-                serverPort: ports.server,
-                socketPort: ports.socket,
-                pollingIntervalSeconds: intervalSeconds,
-                session_key: this.key,
-                DB: process.env.DB
-            });
-            this.mainLog(cyan(`spawned new server worker with process id ${this.activeWorker.process.pid}`));
-            // an IPC message handler that executes actions on the master thread when prompted by the active worker
-            this.activeWorker.on("message", async ({ lifecycle, action }) => {
-                if (action) {
-                    const { message, args } = action as Monitor.Action;
-                    console.log(this.timestamp(), `${this.config.identifiers.worker.text} action requested (${cyan(message)})`);
-                    switch (message) {
-                        case "kill":
-                            const { reason, graceful, errorCode } = args;
-                            this.killSession(reason, graceful, errorCode);
-                            break;
-                        case "notify_crash":
-                            if (this.notifiers?.crash) {
-                                const { error } = args;
-                                const success = await this.notifiers.crash(error);
-                                const statement = success ? green("distributed crash notification to recipients") : red("distribution of crash notification failed");
-                                this.mainLog(statement);
-                            }
-                            break;
-                        case "set_port":
-                            const { port, value, immediateRestart } = args;
-                            this.setPort(port, value, immediateRestart);
-                            break;
-                    }
-                    const handlers = this.onMessage[message];
-                    if (handlers) {
-                        handlers.forEach(handler => handler({ message, args }));
-                    }
-                }
-                if (lifecycle) {
-                    console.log(this.timestamp(), `${this.config.identifiers.worker.text} lifecycle phase (${lifecycle})`);
-                }
-            });
-        }
-
-    }
-
-    /**
-     * Effectively, each worker repairs the connection to the server by reintroducing a consistent state
-     * if its predecessor has died. It itself also polls the server heartbeat, and exits with a notification
-     * email if the server encounters an uncaught exception or if the server cannot be reached.
-     */
-    export class ServerWorker {
-
-        private static count = 0;
-        private shouldServerBeResponsive = false;
-        private exitHandlers: ExitHandler[] = [];
-        private pollingFailureCount = 0;
-        private pollingIntervalSeconds: number;
-        private pollingFailureTolerance: number;
-        private pollTarget: string;
-        private serverPort: number;
-
-        public static Create(work: Function) {
-            if (isMaster) {
-                console.error(red("cannot create a worker on the monitor process."));
-                process.exit(1);
-            } else if (++ServerWorker.count > 1) {
-                process.send?.({
-                    action: {
-                        message: "kill", args: {
-                            reason: "cannot create more than one worker on a given worker process.",
-                            graceful: false,
-                            errorCode: 1
-                        }
-                    }
-                });
-                process.exit(1);
-            } else {
-                return new ServerWorker(work);
-            }
-        }
-
-        /**
-         * Allows developers to invoke application specific logic
-         * by hooking into the exiting of the server process.
-         */
-        public addExitHandler = (handler: ExitHandler) => this.exitHandlers.push(handler);
-
-        /**
-         * Kill the session monitor (parent process) from this
-         * server worker (child process). This will also kill
-         * this process (child process).
-         */
-        public killSession = (reason: string, graceful = true, errorCode = 0) => this.sendMonitorAction("kill", { reason, graceful, errorCode });
-
-        /**
-         * A convenience wrapper to tell the session monitor (parent process)
-         * to carry out the action with the specified message and arguments.
-         */
-        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(" ")}]`)}`));
-
-            const { pollingRoute, serverPort, pollingIntervalSeconds, pollingFailureTolerance } = process.env;
-            this.serverPort = Number(serverPort);
-            this.pollingIntervalSeconds = Number(pollingIntervalSeconds);
-            this.pollingFailureTolerance = Number(pollingFailureTolerance);
-            this.pollTarget = `http://localhost:${serverPort}${pollingRoute}`;
-
-            this.configureProcess();
-            work();
-            this.pollServer();
-        }
-
-        /**
-         * Set up message and uncaught exception handlers for this
-         * server process.
-         */
-        private configureProcess = () => {
-            // updates the local values of variables to the those sent from master
-            process.on("message", async ({ newPollingIntervalSeconds, manualExit }) => {
-                if (newPollingIntervalSeconds !== undefined) {
-                    this.pollingIntervalSeconds = newPollingIntervalSeconds;
-                }
-                if (manualExit !== undefined) {
-                    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', 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 | boolean) => Promise.all(this.exitHandlers.map(handler => handler(reason)));
-
-        /**
-         * Notify master thread (which will log update in the console) of initialization via IPC.
-         */
-        public lifecycleNotification = (event: string) => process.send?.({ lifecycle: event });
-
-        /**
-         * 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: 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));
-            process.exit(1);
-        }
-
-        /**
-         * This monitors the health of the server by submitting a get request to whatever port / route specified
-         * by the configuration every n seconds, where n is also given by the configuration. 
-         */
-        private pollServer = async (): Promise<void> => {
-            await new Promise<void>(resolve => {
-                setTimeout(async () => {
-                    try {
-                        await get(this.pollTarget);
-                        if (!this.shouldServerBeResponsive) {
-                            // notify monitor thread that the server is up and running
-                            this.lifecycleNotification(green(`listening on ${this.serverPort}...`));
-                        }
-                        this.shouldServerBeResponsive = true;
-                    } 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
-                        // process will continue
-                        if (this.shouldServerBeResponsive) {
-                            if (++this.pollingFailureCount > this.pollingFailureTolerance) {
-                                this.proactiveUnplannedExit(error);
-                            } else {
-                                this.lifecycleNotification(yellow(`the server has encountered ${this.pollingFailureCount} of ${this.pollingFailureTolerance} tolerable failures`));
-                            }
-                        }
-                    } finally {
-                        resolve();
-                    }
-                }, 1000 * this.pollingIntervalSeconds);
-            });
-            // controlled, asynchronous infinite recursion achieves a persistent poll that does not submit a new request until the previous has completed
-            this.pollServer();
-        }
-
-    }
-
-}
-- 
cgit v1.2.3-70-g09d2

