aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/client/DocServer.ts6
-rw-r--r--src/client/util/CurrentUserUtils.ts2
-rw-r--r--src/client/util/SerializationHelper.ts2
-rw-r--r--src/client/views/DocumentDecorations.tsx5
-rw-r--r--src/client/views/MainView.tsx85
-rw-r--r--src/client/views/collections/CollectionSubView.tsx31
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx5
-rw-r--r--src/client/views/nodes/LinkAnchorBox.tsx2
-rw-r--r--src/client/views/nodes/PresBox.tsx26
-rw-r--r--src/client/views/nodes/VideoBox.tsx16
-rw-r--r--src/client/views/nodes/WebBox.tsx62
-rw-r--r--src/client/views/presentationview/PresElementBox.tsx21
-rw-r--r--src/client/views/webcam/WebCamLogic.js6
-rw-r--r--src/debug/Viewer.tsx2
-rw-r--r--src/fields/ScriptField.ts6
-rw-r--r--src/server/ApiManagers/DeleteManager.ts12
-rw-r--r--src/server/ApiManagers/UtilManager.ts3
-rw-r--r--src/server/RouteManager.ts15
-rw-r--r--src/server/apis/google/CredentialsLoader.ts40
-rw-r--r--src/server/index.ts45
-rw-r--r--src/server/server_Initialization.ts37
-rw-r--r--src/server/websocket.ts35
22 files changed, 280 insertions, 184 deletions
diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts
index ac5b7a218..c6b3fa61f 100644
--- a/src/client/DocServer.ts
+++ b/src/client/DocServer.ts
@@ -1,4 +1,4 @@
-import * as OpenSocket from 'socket.io-client';
+import * as io from 'socket.io-client';
import { MessageStore, YoutubeQueryTypes, GestureContent, MobileInkOverlayContent, UpdateMobileInkOverlayPositionContent, MobileDocumentUploadContent } from "./../server/Message";
import { Opt, Doc } from '../fields/Doc';
import { Utils, emptyFunction } from '../Utils';
@@ -108,7 +108,9 @@ export namespace DocServer {
export function init(protocol: string, hostname: string, port: number, identifier: string) {
_cache = {};
GUID = identifier;
- _socket = OpenSocket(`${protocol}//${hostname}:${port}`);// OpenSocket(`https://7f079dda.ngrok.io`);// if using ngrok, create a special address for the websocket
+ protocol = protocol.startsWith("https") ? "wss" : "ws";
+ _socket = io.connect(`${protocol}://${hostname}:${port}`);
+ // io.connect(`https://7f079dda.ngrok.io`);// if using ngrok, create a special address for the websocket
_GetCachedRefField = _GetCachedRefFieldImpl;
_GetRefField = _GetRefFieldImpl;
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 1f25ed790..67da265b3 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -317,7 +317,7 @@ export class CurrentUserUtils {
{ _width: 250, _height: 250, title: "container" });
}
if (doc.emptyWebpage === undefined) {
- doc.emptyWebpage = Docs.Create.WebDocument("", { title: "New Webpage", _width: 600, UseCors: true });
+ doc.emptyWebpage = Docs.Create.WebDocument("", { title: "New Webpage", _nativeWidth: 850, _nativeHeight: 962, _width: 600, UseCors: true });
}
return [
{ title: "Drag a collection", label: "Col", icon: "folder", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: doc.emptyCollection as Doc },
diff --git a/src/client/util/SerializationHelper.ts b/src/client/util/SerializationHelper.ts
index ad0309fa7..19b217726 100644
--- a/src/client/util/SerializationHelper.ts
+++ b/src/client/util/SerializationHelper.ts
@@ -91,7 +91,7 @@ export function Deserializable(constructor: { new(...args: any[]): any } | strin
if (typeof constructor === "string") {
return Object.assign((ctor: { new(...args: any[]): any }) => {
addToMap(constructor, ctor);
- }, { withFields: (fields: string[]) => Deserializable.withFields(fields, name, afterDeserialize) });
+ }, { withFields: (fields: string[]) => Deserializable.withFields(fields, constructor, afterDeserialize) });
}
addToMap(constructor.name, constructor);
}
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 6639f1cce..7ee4e8d91 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -21,6 +21,7 @@ import { Id } from '../../fields/FieldSymbols';
import e = require('express');
import { CollectionDockingView } from './collections/CollectionDockingView';
import { SnappingManager } from '../util/SnappingManager';
+import { HtmlField } from '../../fields/HtmlField';
library.add(faCaretUp);
library.add(faObjectGroup);
@@ -289,7 +290,9 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
let dX = 0, dY = 0, dW = 0, dH = 0;
const unfreeze = () =>
SelectionManager.SelectedDocuments().forEach(action((element: DocumentView) =>
- (element.rootDoc.type === DocumentType.RTF && element.layoutDoc._nativeHeight) && element.toggleNativeDimensions()));
+ ((element.rootDoc.type === DocumentType.RTF ||
+ (element.rootDoc.type === DocumentType.WEB && Doc.LayoutField(element.rootDoc) instanceof HtmlField))
+ && element.layoutDoc._nativeHeight) && element.toggleNativeDimensions()));
switch (this._resizeHdlId) {
case "": break;
case "documentDecorations-topLeftResizer":
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 3e4833052..4a20c39d4 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -1,6 +1,6 @@
import { library } from '@fortawesome/fontawesome-svg-core';
import {
- faTrashAlt, faAngleRight, faBell, faTrash, faCamera, faExpand, faCaretDown, faCaretLeft, faCaretRight, faCaretSquareDown, faCaretSquareRight, faArrowsAltH, faPlus, faMinus,
+ faTasks, faTrashAlt, faAngleRight, faBell, faTrash, faCamera, faExpand, faCaretDown, faCaretLeft, faCaretRight, faCaretSquareDown, faCaretSquareRight, faArrowsAltH, faPlus, faMinus,
faTerminal, faToggleOn, faFile as fileSolid, faExternalLinkAlt, faLocationArrow, faSearch, faFileDownload, faStop, faCalculator, faWindowMaximize, faAddressCard,
faQuestionCircle, faArrowLeft, faArrowRight, faArrowDown, faArrowUp, faBolt, faBullseye, faCaretUp, faCat, faCheck, faChevronRight, faClipboard, faClone, faCloudUploadAlt,
faCommentAlt, faCompressArrowsAlt, faCut, faEllipsisV, faEraser, faExclamation, faFileAlt, faFileAudio, faFilePdf, faFilm, faFilter, faFont, faGlobeAsia, faHighlighter,
@@ -50,7 +50,6 @@ import PDFMenu from './pdf/PDFMenu';
import { PreviewCursor } from './PreviewCursor';
import { ScriptField } from '../../fields/ScriptField';
import { TimelineMenu } from './animationtimeline/TimelineMenu';
-import { DragManager } from '../util/DragManager';
import { SnappingManager } from '../util/SnappingManager';
@observer
@@ -66,7 +65,7 @@ export class MainView extends React.Component {
@observable private _panelHeight: number = 0;
@observable private _flyoutTranslate: boolean = true;
@observable public flyoutWidth: number = 250;
- private get darkScheme() { return BoolCast(Cast(this.userDoc.activeWorkspace, Doc, null)?.darkScheme); }
+ private get darkScheme() { return BoolCast(Cast(this.userDoc?.activeWorkspace, Doc, null)?.darkScheme); }
@computed private get userDoc() { return Doc.UserDoc(); }
@computed private get mainContainer() { return this.userDoc ? FieldValue(Cast(this.userDoc.activeWorkspace, Doc)) : CurrentUserUtils.GuestWorkspace; }
@@ -114,78 +113,12 @@ export class MainView extends React.Component {
}
}
- library.add(faTrashAlt);
- library.add(faAngleRight);
- library.add(faBell);
- library.add(faTrash);
- library.add(faCamera);
- library.add(faExpand);
- library.add(faCaretDown);
- library.add(faCaretUp);
- library.add(faCaretLeft);
- library.add(faCaretRight);
- library.add(faCaretSquareDown);
- library.add(faCaretSquareRight);
- library.add(faArrowsAltH);
- library.add(faPlus, faMinus);
- library.add(faTerminal);
- library.add(faToggleOn);
- library.add(faLocationArrow);
- library.add(faSearch);
- library.add(fileSolid);
- library.add(faFileDownload);
- library.add(faStop);
- library.add(faCalculator);
- library.add(faWindowMaximize);
- library.add(faFileAlt);
- library.add(faAddressCard);
- library.add(faQuestionCircle);
- library.add(faStickyNote);
- library.add(faFont);
- library.add(faExclamation);
- library.add(faPortrait);
- library.add(faCat);
- library.add(faFilePdf);
- library.add(faObjectGroup);
- library.add(faTv);
- library.add(faGlobeAsia);
- library.add(faUndoAlt);
- library.add(faRedoAlt);
- library.add(faMousePointer);
- library.add(faPen);
- library.add(faHighlighter);
- library.add(faEraser);
- library.add(faFileAudio);
- library.add(faPenNib);
- library.add(faMicrophone);
- library.add(faFilm);
- library.add(faMusic);
- library.add(faTree);
- library.add(faPlay);
- library.add(faCompressArrowsAlt);
- library.add(faPause);
- library.add(faClone);
- library.add(faCut);
- library.add(faCommentAlt);
- library.add(faThumbtack);
- library.add(faLongArrowAltRight);
- library.add(faCheck);
- library.add(faFilter);
- library.add(faBullseye);
- library.add(faArrowLeft);
- library.add(faArrowRight);
- library.add(faArrowDown);
- library.add(faArrowUp);
- library.add(faCloudUploadAlt);
- library.add(faBolt);
- library.add(faVideo);
- library.add(faChevronRight);
- library.add(faEllipsisV);
- library.add(faMusic);
- library.add(faPhone);
- library.add(faClipboard);
- library.add(faStamp);
- library.add(faExternalLinkAlt);
+ library.add(faTasks, faTrashAlt, faAngleRight, faBell, faTrash, faCamera, faExpand, faCaretDown, faCaretLeft, faCaretRight, faCaretSquareDown, faCaretSquareRight, faArrowsAltH, faPlus, faMinus,
+ faTerminal, faToggleOn, faExternalLinkAlt, faLocationArrow, faSearch, faFileDownload, faStop, faCalculator, faWindowMaximize, faAddressCard, fileSolid,
+ faQuestionCircle, faArrowLeft, faArrowRight, faArrowDown, faArrowUp, faBolt, faBullseye, faCaretUp, faCat, faCheck, faChevronRight, faClipboard, faClone, faCloudUploadAlt,
+ faCommentAlt, faCompressArrowsAlt, faCut, faEllipsisV, faEraser, faExclamation, faFileAlt, faFileAudio, faFilePdf, faFilm, faFilter, faFont, faGlobeAsia, faHighlighter,
+ faLongArrowAltRight, faMicrophone, faMousePointer, faMusic, faObjectGroup, faPause, faPen, faPenNib, faPhone, faPlay, faPortrait, faRedoAlt, faStamp, faStickyNote, faTrashAlt, faAngleRight, faBell,
+ faThumbtack, faTree, faTv, faUndoAlt, faVideo);
this.initEventListeners();
this.initAuthenticationRouters();
}
@@ -205,7 +138,7 @@ export class MainView extends React.Component {
globalPointerUp = () => this.isPointerDown = false;
initEventListeners = () => {
- window.addEventListener("drop", (e) => e.preventDefault(), false); // drop event handler
+ window.addEventListener("drop", (e) => { e.preventDefault(); }, false); // drop event handler
window.addEventListener("dragover", (e) => e.preventDefault(), false); // drag event handler
// click interactions for the context menu
document.addEventListener("pointerdown", this.globalPointerDown);
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 0827f8782..673cce14f 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -6,14 +6,14 @@ import { Id } from "../../../fields/FieldSymbols";
import { List } from "../../../fields/List";
import { listSpec } from "../../../fields/Schema";
import { ScriptField } from "../../../fields/ScriptField";
-import { Cast, ScriptCast } from "../../../fields/Types";
+import { Cast, ScriptCast, NumCast } from "../../../fields/Types";
import { GestureUtils } from "../../../pen-gestures/GestureUtils";
import { CurrentUserUtils } from "../../util/CurrentUserUtils";
import { Upload } from "../../../server/SharedMediaTypes";
import { Utils } from "../../../Utils";
import { GooglePhotos } from "../../apis/google_docs/GooglePhotosClientUtils";
import { DocServer } from "../../DocServer";
-import { Docs, DocumentOptions } from "../../documents/Documents";
+import { Docs, DocumentOptions, DocUtils } from "../../documents/Documents";
import { DocumentType } from "../../documents/DocumentTypes";
import { Networking } from "../../Network";
import { DragManager, dropActionType } from "../../util/DragManager";
@@ -25,6 +25,8 @@ import { FieldViewProps } from "../nodes/FieldView";
import { FormattedTextBox, GoogleRef } from "../nodes/formattedText/FormattedTextBox";
import { CollectionView } from "./CollectionView";
import React = require("react");
+import { SelectionManager } from "../../util/SelectionManager";
+import { WebField } from "../../../fields/URLField";
export interface CollectionViewProps extends FieldViewProps {
addDocument: (document: Doc | Doc[]) => boolean;
@@ -44,7 +46,7 @@ export interface SubCollectionViewProps extends CollectionViewProps {
CollectionView: Opt<CollectionView>;
children?: never | (() => JSX.Element[]) | React.ReactNode;
ChildLayoutTemplate?: () => Doc;
- childOpacity?:() => number;
+ childOpacity?: () => number;
ChildLayoutString?: string;
childClickScript?: ScriptField;
childDoubleClickScript?: ScriptField;
@@ -324,9 +326,28 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
}
});
} else {
- const htmlDoc = Docs.Create.HtmlDocument(html, { ...options, title: "-web page-", _width: 300, _height: 300 });
+ let srcUrl: string | undefined;
+ let srcWeb: Doc | undefined;
+ if (SelectionManager.SelectedDocuments().length) {
+ srcWeb = SelectionManager.SelectedDocuments()[0].props.Document;
+ srcUrl = (srcWeb.data as WebField).url.href.match(/http[s]?:\/\/[^/]*/)?.[0];
+ }
+ let reg = new RegExp(Utils.prepend(""), "g");
+ const modHtml = srcUrl ? html.replace(reg, srcUrl) : html;
+ const htmlDoc = Docs.Create.HtmlDocument(modHtml, { ...options, title: "-web page-", _width: 300, _height: 300 });
Doc.GetProto(htmlDoc)["data-text"] = text;
this.props.addDocument(htmlDoc);
+ if (srcWeb) {
+ const focusNode = (SelectionManager.SelectedDocuments()[0].ContentDiv?.getElementsByTagName("iframe")[0].contentDocument?.getSelection()?.focusNode as any);
+ const rect = "getBoundingClientRect" in focusNode ? focusNode.getBoundingClientRect() : focusNode?.parentElement.getBoundingClientRect();
+ const x = (rect?.x || 0);
+ const y = NumCast(srcWeb.scrollTop) + (rect?.y || 0);
+ const anchor = Docs.Create.FreeformDocument([], { _LODdisable: true, _backgroundColor: "transparent", _width: 25, _height: 25, x, y, annotationOn: srcWeb });
+ anchor.context = srcWeb;
+ const key = Doc.LayoutFieldKey(srcWeb);
+ Doc.AddDocToList(srcWeb, key + "-annotations", anchor);
+ DocUtils.MakeLink({ doc: htmlDoc }, { doc: anchor });
+ }
}
return;
}
@@ -335,7 +356,7 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
if (text) {
if (text.includes("www.youtube.com/watch")) {
- const url = text.replace("youtube.com/watch?v=", "youtube.com/embed/");
+ const url = text.replace("youtube.com/watch?v=", "youtube.com/embed/").split("&")[0];
addDocument(Docs.Create.VideoDocument(url, {
...options,
title: url,
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index e66e95d81..d8fe662ae 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -127,7 +127,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
this.addDocument(newBox);
}
addDocument = (newBox: Doc | Doc[]) => {
- if (this.Document.currentTimecode !== undefined) {
+ if (this.Document.currentTimecode !== undefined && !this.props.isAnnotationOverlay) {
CollectionFreeFormDocumentView.setupKeyframes((newBox instanceof Doc) ? [newBox] : newBox, this.Document.currentTimecode, this.props.Document);
}
@@ -152,6 +152,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
}
CollectionFreeFormDocumentView.updateKeyframe(this.childDocs, currentTimecode || 0);
this.Document.currentTimecode = Math.max(0, (currentTimecode || 0) + 1);
+ this.Document.lastTimecode = Math.max(NumCast(this.Document.currentTimecode), NumCast(this.Document.lastTimecode));
}
@undoBatch
@action
@@ -205,7 +206,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
for (let i = 0; i < droppedDocs.length; i++) {
const d = droppedDocs[i];
const layoutDoc = Doc.Layout(d);
- if (this.Document.currentTimecode !== undefined) {
+ if (this.Document.currentTimecode !== undefined && !this.props.isAnnotationOverlay) {
CollectionFreeFormDocumentView.setValues(this.Document.currentTimecode, d, x + NumCast(d.x) - dropX, y + NumCast(d.y) - dropY, Cast(d.opacity, "number", null));
} else {
d.x = x + NumCast(d.x) - dropX;
diff --git a/src/client/views/nodes/LinkAnchorBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx
index 098aa58e9..83245a89c 100644
--- a/src/client/views/nodes/LinkAnchorBox.tsx
+++ b/src/client/views/nodes/LinkAnchorBox.tsx
@@ -72,7 +72,7 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps, LinkAnch
anchorContainerDoc && this.props.bringToFront(anchorContainerDoc, false);
if (anchorContainerDoc && !this.layoutDoc.onClick && !this._isOpen) {
this._timeout = setTimeout(action(() => {
- DocumentManager.Instance.FollowLink(this.rootDoc, anchorContainerDoc, document => this.props.addDocTab(document, StrCast(this.layoutDoc.linkOpenLocation, "inTab")), false);
+ DocumentManager.Instance.FollowLink(this.rootDoc, anchorContainerDoc, document => this.props.addDocTab(document, StrCast(this.layoutDoc.linkOpenLocation, e.altKey ? "inTab" : "onRight")), false);
this._editing = false;
}), 300 - (Date.now() - this._lastTap));
}
diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx
index 6edcae417..09c03fb30 100644
--- a/src/client/views/nodes/PresBox.tsx
+++ b/src/client/views/nodes/PresBox.tsx
@@ -15,7 +15,7 @@ import { InkingControl } from "../InkingControl";
import { FieldView, FieldViewProps } from './FieldView';
import "./PresBox.scss";
import { ViewBoxBaseComponent } from "../DocComponent";
-import { makeInterface } from "../../../fields/Schema";
+import { makeInterface, listSpec } from "../../../fields/Schema";
import { List } from "../../../fields/List";
import { Docs } from "../../documents/Documents";
import { PrefetchProxy } from "../../../fields/Proxy";
@@ -59,7 +59,13 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
@action
next = () => {
this.updateCurrentPresentation();
- if (this.childDocs[this.itemIndex + 1] !== undefined) {
+ const presTargetDoc = Cast(this.childDocs[this.itemIndex].presentationTargetDoc, Doc, null);
+ const lastFrame = Cast(presTargetDoc.lastTimecode, "number", null);
+ const curFrame = NumCast(presTargetDoc.currentTimecode);
+ if (lastFrame !== undefined && curFrame < lastFrame) {
+ presTargetDoc.currentTimecode = curFrame + 1;
+ }
+ else if (this.childDocs[this.itemIndex + 1] !== undefined) {
let nextSelected = this.itemIndex + 1;
this.gotoDocument(nextSelected, this.itemIndex);
@@ -188,11 +194,15 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
//The function that is called when a document is clicked or reached through next or back.
//it'll also execute the necessary actions if presentation is playing.
- public gotoDocument = (index: number, fromDoc: number) => {
+ public gotoDocument = action((index: number, fromDoc: number) => {
this.updateCurrentPresentation();
Doc.UnBrushAllDocs();
if (index >= 0 && index < this.childDocs.length) {
this.rootDoc._itemIndex = index;
+ const presTargetDoc = Cast(this.childDocs[this.itemIndex].presentationTargetDoc, Doc, null);
+ if (presTargetDoc.lastTimecode !== undefined) {
+ presTargetDoc.currentTimecode = 0;
+ }
if (!this.layoutDoc.presStatus) {
this.layoutDoc.presStatus = true;
@@ -203,7 +213,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
this.hideIfNotPresented(index);
this.showAfterPresented(index);
}
- }
+ })
//The function that starts or resets presentaton functionally, depending on status flag.
startOrResetPres = () => {
@@ -286,7 +296,12 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
(this.layoutDoc.forceActive || this.props.isSelected(outsideReaction) || this._isChildActive || this.props.renderDepth === 0) ? true : false)
render() {
- this.rootDoc.presOrderedDocs = new List<Doc>(this.childDocs.map((child, i) => child));
+ // console.log("render = " + this.layoutDoc.title + " " + this.layoutDoc.presStatus);
+ // const presOrderedDocs = DocListCast(this.rootDoc.presOrderedDocs);
+ // if (presOrderedDocs.length != this.childDocs.length || presOrderedDocs.some((pd, i) => pd !== this.childDocs[i])) {
+ // this.rootDoc.presOrderedDocs = new List<Doc>(this.childDocs.slice());
+ // }
+ this.childDocs.slice(); // needed to insure that the childDocs are loaded for looking up fields
const mode = StrCast(this.rootDoc._viewType) as CollectionViewType;
return <div className="presBox-cont" style={{ minWidth: this.layoutDoc.inOverlay ? 240 : undefined }} >
<div className="presBox-buttons" style={{ display: this.rootDoc._chromeStatus === "disabled" ? "none" : undefined }}>
@@ -334,5 +349,6 @@ Scripting.addGlobal(function lookupPresBoxField(container: Doc, field: string, d
if (field === 'presCollapsedHeight') return container._viewType === CollectionViewType.Stacking ? 50 : 46;
if (field === 'presStatus') return container.presStatus;
if (field === '_itemIndex') return container._itemIndex;
+ if (field === 'presBox') return container;
return undefined;
});
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index ccf1f5588..bcbd65258 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -104,18 +104,20 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
canvas.height = 640 * (this.layoutDoc._nativeHeight || 0) / (this.layoutDoc._nativeWidth || 1);
const ctx = canvas.getContext('2d');//draw image to canvas. scale to target dimensions
if (ctx) {
- ctx.rect(0, 0, canvas.width, canvas.height);
- ctx.fillStyle = "blue";
- ctx.fill();
+ // ctx.rect(0, 0, canvas.width, canvas.height);
+ // ctx.fillStyle = "blue";
+ // ctx.fill();
this._videoRef && ctx.drawImage(this._videoRef, 0, 0, canvas.width, canvas.height);
}
if (!this._videoRef) { // can't find a way to take snapshots of videos
- const b = Docs.Create.ButtonDocument({
+ const b = Docs.Create.LabelDocument({
x: (this.layoutDoc.x || 0) + width, y: (this.layoutDoc.y || 1),
- _width: 150, _height: 50, title: (this.layoutDoc.currentTimecode || 0).toString()
+ _width: 150, _height: 50, title: (this.layoutDoc.currentTimecode || 0).toString(),
});
- b.onClick = ScriptField.MakeScript(`this.currentTimecode = ${(this.layoutDoc.currentTimecode || 0)}`);
+ b.isLinkButton = true;
+ this.props.addDocument?.(b);
+ DocUtils.MakeLink({ doc: b }, { doc: this.rootDoc }, "video snapshot");
} else {
//convert to desired file format
const dataUrl = canvas.toDataURL('image/png'); // can also use 'image/png'
@@ -132,7 +134,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
Doc.GetProto(imageSummary)["data-nativeWidth"] = this.layoutDoc._nativeWidth;
Doc.GetProto(imageSummary)["data-nativeHeight"] = this.layoutDoc._nativeHeight;
imageSummary.isLinkButton = true;
- this.props.addDocument && this.props.addDocument(imageSummary);
+ this.props.addDocument?.(imageSummary);
DocUtils.MakeLink({ doc: imageSummary }, { doc: this.rootDoc }, "video snapshot");
}
});
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 82f05012a..8844702d7 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -22,6 +22,9 @@ import React = require("react");
import * as WebRequest from 'web-request';
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView";
+import { ContextMenu } from "../ContextMenu";
+import { ContextMenuProps } from "../ContextMenuItem";
+import { undoBatch } from "../../util/UndoManager";
const htmlToText = require("html-to-text");
library.add(faStickyNote);
@@ -33,7 +36,7 @@ const WebDocument = makeInterface(documentSchema);
export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocument>(WebDocument) {
public static LayoutString(fieldKey: string) { return FieldView.LayoutString(WebBox, fieldKey); }
- get _collapsed() { return StrCast(this.layoutDoc._chromeStatus) === "disabled"; }
+ get _collapsed() { return StrCast(this.layoutDoc._chromeStatus) !== "enabled"; }
set _collapsed(value) { this.layoutDoc._chromeStatus = !value ? "enabled" : "disabled"; }
@observable private _url: string = "hello";
@observable private _pressX: number = 0;
@@ -48,11 +51,13 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean) => void);
iframeLoaded = action((e: any) => {
- if (this._iframeRef.current?.contentDocument) {
- this._iframeRef.current.contentDocument.addEventListener('pointerdown', this.iframedown, false);
- this._iframeRef.current.contentDocument.addEventListener('scroll', this.iframeScrolled, false);
- this.layoutDoc.scrollHeight = this._iframeRef.current.contentDocument.children?.[0].scrollHeight || 1000;
- this._iframeRef.current.contentDocument.children[0].scrollTop = NumCast(this.layoutDoc.scrollTop);
+ const iframe = this._iframeRef.current;
+ if (iframe && iframe.contentDocument) {
+ iframe.setAttribute("enable-annotation", "true");
+ iframe.contentDocument.addEventListener('pointerdown', this.iframedown, false);
+ iframe.contentDocument.addEventListener('scroll', this.iframeScrolled, false);
+ this.layoutDoc.scrollHeight = iframe.contentDocument.children?.[0].scrollHeight || 1000;
+ iframe.contentDocument.children[0].scrollTop = NumCast(this.layoutDoc.scrollTop);
}
this._reactionDisposer?.();
this._reactionDisposer = reaction(() => this.layoutDoc.scrollY,
@@ -77,8 +82,6 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
this.setURL();
- this._iframeRef.current!.setAttribute("enable-annotation", "true");
-
document.addEventListener("pointerup", this.onLongPressUp);
document.addEventListener("pointermove", this.onLongPressMove);
const field = Cast(this.rootDoc[this.props.fieldKey], WebField);
@@ -86,11 +89,13 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
const youtubeaspect = 400 / 315;
const nativeWidth = NumCast(this.layoutDoc._nativeWidth);
const nativeHeight = NumCast(this.layoutDoc._nativeHeight);
- if (!nativeWidth || !nativeHeight || Math.abs(nativeWidth / nativeHeight - youtubeaspect) > 0.05) {
- if (!nativeWidth) this.layoutDoc._nativeWidth = 600;
- this.layoutDoc._nativeHeight = NumCast(this.layoutDoc._nativeWidth) / youtubeaspect;
- this.layoutDoc._height = NumCast(this.layoutDoc._width) / youtubeaspect;
- }
+ if (field) {
+ if (!nativeWidth || !nativeHeight || Math.abs(nativeWidth / nativeHeight - youtubeaspect) > 0.05) {
+ if (!nativeWidth) this.layoutDoc._nativeWidth = 600;
+ this.layoutDoc._nativeHeight = NumCast(this.layoutDoc._nativeWidth) / youtubeaspect;
+ this.layoutDoc._height = NumCast(this.layoutDoc._width) / youtubeaspect;
+ }
+ } // else it's an HTMLfield
} else if (field?.url) {
const result = await WebRequest.get(Utils.CorsProxy(field.url.href));
this.dataDoc.text = htmlToText.fromString(result.content);
@@ -101,8 +106,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
this._reactionDisposer?.();
document.removeEventListener("pointerup", this.onLongPressUp);
document.removeEventListener("pointermove", this.onLongPressMove);
- this._iframeRef.current!.contentDocument?.removeEventListener('pointerdown', this.iframedown);
- this._iframeRef.current!.contentDocument?.removeEventListener('scroll', this.iframeScrolled);
+ this._iframeRef.current?.contentDocument?.removeEventListener('pointerdown', this.iframedown);
+ this._iframeRef.current?.contentDocument?.removeEventListener('scroll', this.iframeScrolled);
}
@action
@@ -112,6 +117,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
@action
submitURL = () => {
+ if (!this._url.startsWith("http")) this._url = "http://" + this._url;
this.dataDoc[this.props.fieldKey] = new WebField(new URL(this._url));
}
@@ -308,6 +314,20 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
}
}
+
+ @undoBatch
+ @action
+ toggleNativeDimensions = () => {
+ Doc.toggleNativeDimensions(this.layoutDoc, this.props.ContentScaling(), this.props.NativeWidth(), this.props.NativeHeight());
+ }
+ specificContextMenu = (e: React.MouseEvent): void => {
+ const cm = ContextMenu.Instance;
+ const funcs: ContextMenuProps[] = [];
+ funcs.push({ description: (!this.layoutDoc._nativeWidth || !this.layoutDoc._nativeHeight ? "Freeze" : "Unfreeze") + " Aspect", event: this.toggleNativeDimensions, icon: "snowflake" });
+ cm.addItem({ description: "Options...", subitems: funcs, icon: "asterisk" });
+
+ }
+
//const href = "https://brown365-my.sharepoint.com/personal/bcz_ad_brown_edu/_layouts/15/Doc.aspx?sourcedoc={31aa3178-4c21-4474-b367-877d0a7135e4}&action=embedview&wdStartOn=1";
@computed
@@ -356,14 +376,20 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
width: Number.isFinite(this.props.ContentScaling()) ? `${100 / this.props.ContentScaling()}%` : "100%",
height: Number.isFinite(this.props.ContentScaling()) ? `${100 / this.props.ContentScaling()}%` : "100%",
pointerEvents: this.layoutDoc.isBackground ? "none" : undefined
- }} >
+ }}
+ onContextMenu={this.specificContextMenu}>
+ <base target="_blank" />
{this.content}
<div className={"webBox-outerContent"} ref={this._outerRef}
style={{ pointerEvents: this.layoutDoc.isAnnotating && !this.layoutDoc.isBackground ? "all" : "none" }}
onWheel={e => e.stopPropagation()}
onScroll={e => {
- if (this._iframeRef.current!.contentDocument!.children[0].scrollTop !== this._outerRef.current!.scrollTop) {
- this._iframeRef.current!.contentDocument!.children[0].scrollTop = this._outerRef.current!.scrollTop;
+ const iframe = this._iframeRef?.current?.contentDocument;
+ const outerFrame = this._outerRef.current;
+ if (iframe && outerFrame) {
+ if (iframe.children[0].scrollTop !== outerFrame.scrollTop) {
+ iframe.children[0].scrollTop = outerFrame.scrollTop;
+ }
}
//this._outerRef.current!.scrollTop !== this._scrollTop && (this._outerRef.current!.scrollTop = this._scrollTop)
}}>
diff --git a/src/client/views/presentationview/PresElementBox.tsx b/src/client/views/presentationview/PresElementBox.tsx
index 26c8aa80a..a2a6882b9 100644
--- a/src/client/views/presentationview/PresElementBox.tsx
+++ b/src/client/views/presentationview/PresElementBox.tsx
@@ -4,9 +4,9 @@ import { observer } from "mobx-react";
import { Doc, DataSym, DocListCast } from "../../../fields/Doc";
import { documentSchema } from '../../../fields/documentSchemas';
import { Id } from "../../../fields/FieldSymbols";
-import { createSchema, makeInterface } from '../../../fields/Schema';
+import { createSchema, makeInterface, listSpec } from '../../../fields/Schema';
import { Cast, NumCast, BoolCast, ScriptCast } from "../../../fields/Types";
-import { emptyFunction, emptyPath, returnFalse, returnTrue, returnOne, returnZero } from "../../../Utils";
+import { emptyFunction, emptyPath, returnFalse, returnTrue, returnOne, returnZero, numberRange } from "../../../Utils";
import { Transform } from "../../util/Transform";
import { CollectionViewType } from '../collections/CollectionView';
import { ViewBoxBaseComponent } from '../DocComponent';
@@ -14,6 +14,7 @@ import { ContentFittingDocumentView } from '../nodes/ContentFittingDocumentView'
import { FieldView, FieldViewProps } from '../nodes/FieldView';
import "./PresElementBox.scss";
import React = require("react");
+import { CollectionFreeFormDocumentView } from "../nodes/CollectionFreeFormDocumentView";
export const presSchema = createSchema({
presentationTargetDoc: Doc,
@@ -43,6 +44,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps, PresDoc
@computed get collapsedHeight() { return Number(this.lookupField("presCollapsedHeight")); } // the collapsed height changes depending on the state of the presBox. We could store this on the presentation elemnt template if it's used by only one presentation - but if it's shared by multiple, then this value must be looked up
@computed get presStatus() { return BoolCast(this.lookupField("presStatus")); }
@computed get itemIndex() { return NumCast(this.lookupField("_itemIndex")); }
+ @computed get presBox() { return Cast(this.lookupField("presBox"), Doc, null); }
@computed get targetDoc() { return Cast(this.rootDoc.presentationTargetDoc, Doc, null) || this.rootDoc; }
componentDidMount() {
@@ -93,6 +95,20 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps, PresDoc
}
}
+ @action
+ progressivize = (e: React.MouseEvent) => {
+ e.stopPropagation();
+ this.rootDoc.presProgressivize = !this.rootDoc.presProgressivize;
+ const rootTarget = Cast(this.rootDoc.presentationTargetDoc, Doc, null);
+ if (this.rootDoc.presProgressivize && !rootTarget?.lastTimecode) {
+ const docs = DocListCast(rootTarget[Doc.LayoutFieldKey(rootTarget)]);
+ rootTarget.currentTimecode = 0;
+ CollectionFreeFormDocumentView.setupKeyframes(docs, docs.length, this.presBox);
+ docs.forEach((d, i) => numberRange(docs.length - i).forEach(f => Cast(d["opacity-indexed"], listSpec("number"), [])[f + i] = 1));
+ rootTarget.lastTimecode = docs.length - 1;
+ }
+ }
+
/**
* The function that is called on click to turn fading document after presented option on/off.
* It also makes sure that the option swithches from hide-after to this one, since both
@@ -210,6 +226,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps, PresDoc
<button title="Fade After" className={pbi + (this.rootDoc.presFadeButton ? "-selected" : "")} onClick={this.onFadeDocumentAfterPresentedClick}><FontAwesomeIcon icon={"file-download"} onPointerDown={e => e.stopPropagation()} /></button>
<button title="Hide After" className={pbi + (this.rootDoc.presHideAfterButton ? "-selected" : "")} onClick={this.onHideDocumentAfterPresentedClick}><FontAwesomeIcon icon={"file-download"} onPointerDown={e => e.stopPropagation()} /></button>
<button title="Group With Up" className={pbi + (this.rootDoc.presGroupButton ? "-selected" : "")} onClick={e => { e.stopPropagation(); this.rootDoc.presGroupButton = !this.rootDoc.presGroupButton; }}><FontAwesomeIcon icon={"arrow-up"} onPointerDown={e => e.stopPropagation()} /></button>
+ <button title="Progressivize" className={pbi + (this.rootDoc.pres ? "-selected" : "")} onClick={this.progressivize}><FontAwesomeIcon icon={"tasks"} onPointerDown={e => e.stopPropagation()} /></button>
<button title="Expand Inline" className={pbi + (this.rootDoc.presExpandInlineButton ? "-selected" : "")} onClick={e => { e.stopPropagation(); this.rootDoc.presExpandInlineButton = !this.rootDoc.presExpandInlineButton; }}><FontAwesomeIcon icon={"arrow-down"} onPointerDown={e => e.stopPropagation()} /></button>
</div>
{this.renderEmbeddedInline}
diff --git a/src/client/views/webcam/WebCamLogic.js b/src/client/views/webcam/WebCamLogic.js
index f542fb983..c847b8656 100644
--- a/src/client/views/webcam/WebCamLogic.js
+++ b/src/client/views/webcam/WebCamLogic.js
@@ -104,9 +104,9 @@ export function initialize(roomName, handlerUI) {
navigator.mediaDevices.getUserMedia({
- audio: true,
- video: true
- })
+ audio: true,
+ video: true
+ })
.then(gotStream)
.catch(function (e) {
alert('getUserMedia() error: ' + e.name);
diff --git a/src/debug/Viewer.tsx b/src/debug/Viewer.tsx
index 6ce39d533..ddddee3be 100644
--- a/src/debug/Viewer.tsx
+++ b/src/debug/Viewer.tsx
@@ -96,7 +96,7 @@ class DocumentViewer extends React.Component<{ field: Doc }> {
content = (
<div>
Document ({this.props.field[Id]})
- <div style={{ paddingLeft: "25px" }}>
+ <div style={{ paddingLeft: "25px" }}>
{fields}
</div>
</div>
diff --git a/src/fields/ScriptField.ts b/src/fields/ScriptField.ts
index 3a4cdbdf8..566390c0d 100644
--- a/src/fields/ScriptField.ts
+++ b/src/fields/ScriptField.ts
@@ -144,12 +144,6 @@ export class ComputedField extends ScriptField {
return new ComputedField(this.script);
}
- constructor(script: CompiledScript, setterscript?: CompiledScript) {
- super(script,
- !setterscript && script?.originalScript.includes("self.displayTimecode") ?
- ScriptField.CompileScript("self['x' + self.displayTimecode] = value", { value: "any" }, true) : setterscript);
- }
-
public static MakeScript(script: string, params: object = {}) {
const compiled = ScriptField.CompileScript(script, params, false);
return compiled.compiled ? new ComputedField(compiled) : undefined;
diff --git a/src/server/ApiManagers/DeleteManager.ts b/src/server/ApiManagers/DeleteManager.ts
index 7fbb37658..46c0d8a8a 100644
--- a/src/server/ApiManagers/DeleteManager.ts
+++ b/src/server/ApiManagers/DeleteManager.ts
@@ -14,24 +14,20 @@ export default class DeleteManager extends ApiManager {
register({
method: Method.GET,
+ requireAdminInRelease: true,
subscription: new RouteSubscriber("delete").add("target?"),
- secureHandler: async ({ req, res, isRelease }) => {
- if (isRelease) {
- return _permission_denied(res, "Cannot perform a delete operation outside of the development environment!");
- }
-
+ secureHandler: async ({ req, res }) => {
const { target } = req.params;
- const { doDelete } = WebSocket;
if (!target) {
- await doDelete();
+ await WebSocket.doDelete();
} else {
let all = false;
switch (target) {
case "all":
all = true;
case "database":
- await doDelete(false);
+ await WebSocket.doDelete(false);
if (!all) break;
case "files":
rimraf.sync(filesDirectory);
diff --git a/src/server/ApiManagers/UtilManager.ts b/src/server/ApiManagers/UtilManager.ts
index aec523cd0..e2cd88726 100644
--- a/src/server/ApiManagers/UtilManager.ts
+++ b/src/server/ApiManagers/UtilManager.ts
@@ -1,8 +1,6 @@
import ApiManager, { Registration } from "./ApiManager";
import { Method } from "../RouteManager";
import { exec } from 'child_process';
-import RouteSubscriber from "../RouteSubscriber";
-import { red } from "colors";
// import { IBM_Recommender } from "../../client/apis/IBM_Recommender";
// import { Recommender } from "../Recommender";
@@ -34,7 +32,6 @@ export default class UtilManager extends ApiManager {
// }
// });
-
register({
method: Method.GET,
subscription: "/pull",
diff --git a/src/server/RouteManager.ts b/src/server/RouteManager.ts
index b23215996..1a2340afc 100644
--- a/src/server/RouteManager.ts
+++ b/src/server/RouteManager.ts
@@ -2,6 +2,7 @@ import RouteSubscriber from "./RouteSubscriber";
import { DashUserModel } from "./authentication/DashUserModel";
import { Request, Response, Express } from 'express';
import { cyan, red, green } from 'colors';
+import { AdminPriviliges } from ".";
export enum Method {
GET,
@@ -25,6 +26,7 @@ export interface RouteInitializer {
secureHandler: SecureHandler;
publicHandler?: PublicHandler;
errorHandler?: ErrorHandler;
+ requireAdminInRelease?: true;
}
const registered = new Map<string, Set<Method>>();
@@ -84,7 +86,7 @@ export default class RouteManager {
* @param initializer
*/
addSupervisedRoute = (initializer: RouteInitializer): void => {
- const { method, subscription, secureHandler, publicHandler, errorHandler } = initializer;
+ const { method, subscription, secureHandler, publicHandler, errorHandler, requireAdminInRelease: requireAdmin } = initializer;
typeof (initializer.subscription) === "string" && RouteManager.routes.push(initializer.subscription);
initializer.subscription instanceof RouteSubscriber && RouteManager.routes.push(initializer.subscription.root);
@@ -94,7 +96,7 @@ export default class RouteManager {
});
const isRelease = this._isRelease;
const supervised = async (req: Request, res: Response) => {
- let { user } = req;
+ let user = req.user as Partial<DashUserModel> | undefined;
const { originalUrl: target } = req;
if (process.env.DB === "MEM" && !user) {
user = { id: "guest", email: "", userDocumentId: "guestDocId" };
@@ -113,6 +115,13 @@ export default class RouteManager {
}
};
if (user) {
+ if (requireAdmin && isRelease && process.env.PASSWORD) {
+ if (AdminPriviliges.get(user.id)) {
+ AdminPriviliges.delete(user.id);
+ } else {
+ return res.redirect(`/admin/${req.originalUrl.substring(1).replace("/", ":")}`);
+ }
+ }
await tryExecute(secureHandler, { ...core, user });
} else {
req.session!.target = target;
@@ -205,5 +214,5 @@ export function _permission_denied(res: Response, message?: string) {
if (message) {
res.statusMessage = message;
}
- res.status(STATUS.PERMISSION_DENIED).send("Permission Denied!");
+ res.status(STATUS.PERMISSION_DENIED).send(`Permission Denied! ${message}`);
}
diff --git a/src/server/apis/google/CredentialsLoader.ts b/src/server/apis/google/CredentialsLoader.ts
index e3f4d167b..ef1f9a91e 100644
--- a/src/server/apis/google/CredentialsLoader.ts
+++ b/src/server/apis/google/CredentialsLoader.ts
@@ -1,4 +1,7 @@
-import { readFile } from "fs";
+import { readFile, readFileSync } from "fs";
+import { pathFromRoot } from "../../ActionUtilities";
+import { SecureContextOptions } from "tls";
+import { blue, red } from "colors";
export namespace GoogleCredentialsLoader {
@@ -27,3 +30,38 @@ export namespace GoogleCredentialsLoader {
}
}
+
+export namespace SSL {
+
+ export let Credentials: SecureContextOptions = {};
+ export let Loaded = false;
+
+ const suffixes = {
+ privateKey: ".key",
+ certificate: ".crt",
+ caBundle: "-ca.crt"
+ };
+
+ export async function loadCredentials() {
+ const { serverName } = process.env;
+ const cert = (suffix: string) => readFileSync(pathFromRoot(`./${serverName}${suffix}`)).toString();
+ try {
+ Credentials.key = cert(suffixes.privateKey);
+ Credentials.cert = cert(suffixes.certificate);
+ Credentials.ca = cert(suffixes.caBundle);
+ Loaded = true;
+ } catch (e) {
+ Credentials = {};
+ Loaded = false;
+ }
+ }
+
+ export function exit() {
+ console.log(red("Running this server in release mode requires the following SSL credentials in the project root:"));
+ const serverName = process.env.serverName ? process.env.serverName : "{process.env.serverName}";
+ Object.values(suffixes).forEach(suffix => console.log(blue(`${serverName}${suffix}`)));
+ console.log(red("Please ensure these files exist and restart, or run this in development mode."));
+ process.exit(0);
+ }
+
+}
diff --git a/src/server/index.ts b/src/server/index.ts
index 7ac032ed3..ff74cfb33 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -11,9 +11,8 @@ import * as qs from 'query-string';
import UtilManager from './ApiManagers/UtilManager';
import { SearchManager } from './ApiManagers/SearchManager';
import UserManager from './ApiManagers/UserManager';
-import { WebSocket } from './websocket';
import DownloadManager from './ApiManagers/DownloadManager';
-import { GoogleCredentialsLoader } from './apis/google/CredentialsLoader';
+import { GoogleCredentialsLoader, SSL } from './apis/google/CredentialsLoader';
import DeleteManager from "./ApiManagers/DeleteManager";
import PDFManager from "./ApiManagers/PDFManager";
import UploadManager from "./ApiManagers/UploadManager";
@@ -26,6 +25,7 @@ import { DashSessionAgent } from "./DashSession/DashSessionAgent";
import SessionManager from "./ApiManagers/SessionManager";
import { AppliedSessionAgent } from "./DashSession/Session/agents/applied_session_agent";
+export const AdminPriviliges: Map<string, boolean> = new Map();
export const onWindows = process.platform === "win32";
export let sessionAgent: AppliedSessionAgent;
export const publicDirectory = path.resolve(__dirname, "public");
@@ -41,6 +41,7 @@ async function preliminaryFunctions() {
await DashUploadUtils.buildFileDirectories();
await Logger.initialize();
await GoogleCredentialsLoader.loadCredentials();
+ SSL.loadCredentials();
GoogleApiServerUtils.processProjectCredentials();
if (process.env.DB !== "MEM") {
await log_execution({
@@ -101,6 +102,42 @@ function routeSetter({ isRelease, addSupervisedRoute, logRegistrationOutcome }:
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)) {
+ return res.redirect("/home");
+ }
+ 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)) {
+ return res.redirect("/home");
+ }
+ const { password } = req.body;
+ const { previous_target } = req.params;
+ let redirect: string;
+ if (password === PASSWORD) {
+ AdminPriviliges.set(id, true);
+ redirect = `/${previous_target.replace(":", "/")}`;
+ } else {
+ redirect = `/admin/${previous_target}`;
+ }
+ res.redirect(redirect);
+ }
+ });
+
addSupervisedRoute({
method: Method.GET,
subscription: ["/home", new RouteSubscriber("doc").add("docId")],
@@ -121,10 +158,6 @@ function routeSetter({ isRelease, addSupervisedRoute, logRegistrationOutcome }:
});
logRegistrationOutcome();
-
- // initialize the web socket (bidirectional communication: if a user changes
- // a field on one client, that change must be broadcast to all other clients)
- WebSocket.initialize(isRelease);
}
diff --git a/src/server/server_Initialization.ts b/src/server/server_Initialization.ts
index f572ec906..14b8776d8 100644
--- a/src/server/server_Initialization.ts
+++ b/src/server/server_Initialization.ts
@@ -10,6 +10,7 @@ import { Database } from './database';
import { getForgot, getLogin, getLogout, getReset, getSignup, postForgot, postLogin, postReset, postSignup } from './authentication/AuthenticationManager';
const MongoStore = require('connect-mongo')(session);
import RouteManager from './RouteManager';
+import { WebSocket } from './websocket';
import * as webpack from 'webpack';
const config = require('../../webpack.config');
const compiler = webpack(config);
@@ -20,10 +21,11 @@ import * as request from 'request';
import RouteSubscriber from './RouteSubscriber';
import { publicDirectory } from '.';
import { logPort, pathFromRoot, } from './ActionUtilities';
-import { blue, yellow } from 'colors';
+import { blue, yellow, red } from 'colors';
import * as cors from "cors";
-import { createServer, Server as SecureServer } from "https";
-import { Server } from "http";
+import { createServer, Server as HttpsServer } from "https";
+import { Server as HttpServer } from "http";
+import { SSL } from './apis/google/CredentialsLoader';
/* RouteSetter is a wrapper around the server that prevents the server
from being exposed. */
@@ -47,28 +49,23 @@ export default async function InitializeServer(routeSetter: RouteSetter) {
const isRelease = determineEnvironment();
+ isRelease && !SSL.Loaded && SSL.exit();
+
routeSetter(new RouteManager(app, isRelease));
registerRelativePath(app);
+ let server: HttpServer | HttpsServer;
const { serverPort } = process.env;
const resolved = isRelease && serverPort ? Number(serverPort) : 1050;
-
- let server: Server | SecureServer;
- if (isRelease) {
- server = createServer({
- key: fs.readFileSync(pathFromRoot(`./${process.env.serverName}.key`)),
- cert: fs.readFileSync(pathFromRoot(`./${process.env.serverName}.crt`))
- }, app);
- (server as SecureServer).listen(resolved, () => {
- logPort("server", resolved);
- console.log();
- });
- } else {
- server = app.listen(resolved, () => {
- logPort("server", resolved);
- console.log();
- });
- }
+ await new Promise<void>(resolve => server = isRelease ?
+ createServer(SSL.Credentials, app).listen(resolved, resolve) :
+ app.listen(resolved, resolve)
+ );
+ logPort("server", resolved);
+
+ // initialize the web socket (bidirectional communication: if a user changes
+ // a field on one client, that change must be broadcast to all other clients)
+ WebSocket.initialize(isRelease, app);
disconnect = async () => new Promise<Error>(resolve => server.close(resolve));
return isRelease;
diff --git a/src/server/websocket.ts b/src/server/websocket.ts
index 9ab3d2611..21e772e83 100644
--- a/src/server/websocket.ts
+++ b/src/server/websocket.ts
@@ -1,18 +1,21 @@
+import * as fs from 'fs';
+import { logPort } from './ActionUtilities';
import { Utils } from "../Utils";
import { MessageStore, Transferable, Types, Diff, YoutubeQueryInput, YoutubeQueryTypes, GestureContent, MobileInkOverlayContent, UpdateMobileInkOverlayPositionContent, MobileDocumentUploadContent, RoomMessage } from "./Message";
import { Client } from "./Client";
import { Socket } from "socket.io";
import { Database } from "./database";
import { Search } from "./Search";
-import * as io from 'socket.io';
+import * as sio from 'socket.io';
import YoutubeApi from "./apis/youtube/youtubeApiSample";
-import { GoogleCredentialsLoader } from "./apis/google/CredentialsLoader";
-import { logPort } from "./ActionUtilities";
+import { GoogleCredentialsLoader, SSL } from "./apis/google/CredentialsLoader";
import { timeMap } from "./ApiManagers/UserManager";
import { green } from "colors";
import { networkInterfaces } from "os";
import executeImport from "../scraping/buxton/final/BuxtonImporter";
import { DocumentsCollection } from "./IDatabase";
+import { createServer, Server } from "https";
+import * as express from "express";
export namespace WebSocket {
@@ -20,12 +23,25 @@ export namespace WebSocket {
const clients: { [key: string]: Client } = {};
export const socketMap = new Map<SocketIO.Socket, string>();
export let disconnect: Function;
+ const defaultPort = 4321;
+
+ export async function initialize(isRelease: boolean, app: express.Express) {
+ let io: sio.Server;
+ let resolved: number;
+ if (isRelease) {
+ const { socketPort } = process.env;
+ resolved = socketPort ? Number(socketPort) : defaultPort;
+ let socketEndpoint: Server;
+ await new Promise<void>(resolve => socketEndpoint = createServer(SSL.Credentials, app).listen(resolved, resolve));
+ io = sio(socketEndpoint!, SSL.Credentials as any);
+ } else {
+ io = sio().listen(resolved = defaultPort);
+ }
+ logPort("websocket", resolved);
+ console.log();
- export function initialize(isRelease: boolean) {
- const endpoint = io();
- endpoint.on("connection", function (socket: Socket) {
+ io.on("connection", function (socket: Socket) {
_socket = socket;
-
socket.use((_packet, next) => {
const userEmail = socketMap.get(socket);
if (userEmail) {
@@ -121,11 +137,6 @@ export namespace WebSocket {
socket.disconnect(true);
};
});
-
- const { socketPort } = process.env;
- const resolved = isRelease && socketPort ? Number(socketPort) : 4321;
- endpoint.listen(resolved);
- logPort("websocket", resolved);
}
function processGesturePoints(socket: Socket, content: GestureContent) {