aboutsummaryrefslogtreecommitdiff
path: root/src/client/DocServer.ts
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/DocServer.ts')
-rw-r--r--src/client/DocServer.ts280
1 files changed, 130 insertions, 150 deletions
diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts
index 6217cf04b..ac865382d 100644
--- a/src/client/DocServer.ts
+++ b/src/client/DocServer.ts
@@ -1,20 +1,15 @@
import { runInAction } from 'mobx';
-import * as rp from 'request-promise';
-import { Doc, DocListCast, Opt } from '../fields/Doc';
+import { Socket, io } from 'socket.io-client';
+import { ClientUtils } from '../ClientUtils';
+import { Utils, emptyFunction } from '../Utils';
+import { Doc, Opt } from '../fields/Doc';
import { UpdatingFromServer } from '../fields/DocSymbols';
import { FieldLoader } from '../fields/FieldLoader';
import { HandleUpdate, Id, Parent } from '../fields/FieldSymbols';
-import { ObjectField } from '../fields/ObjectField';
+import { ObjectField, SetObjGetRefField, SetObjGetRefFields } from '../fields/ObjectField';
import { RefField } from '../fields/RefField';
-import { DocCast, StrCast } from '../fields/Types';
-import { Socket } from '../../node_modules/socket.io/dist/index';
-//import MobileInkOverlay from '../mobile/MobileInkOverlay';
-import { emptyFunction, Utils } from '../Utils';
-import { GestureContent, MessageStore, MobileDocumentUploadContent, MobileInkOverlayContent, UpdateMobileInkOverlayPositionContent, YoutubeQueryTypes } from './../server/Message';
-import { DocumentType } from './documents/DocumentTypes';
-import { LinkManager } from './util/LinkManager';
+import { GestureContent, Message, MessageStore, MobileDocumentUploadContent, MobileInkOverlayContent, UpdateMobileInkOverlayPositionContent, YoutubeQueryTypes } from '../server/Message';
import { SerializationHelper } from './util/SerializationHelper';
-//import { GestureOverlay } from './views/GestureOverlay';
/**
* This class encapsulates the transfer and cross-client synchronization of
@@ -30,68 +25,53 @@ import { SerializationHelper } from './util/SerializationHelper';
* or update ourselves based on the server's update message, that occurs here
*/
export namespace DocServer {
+ // eslint-disable-next-line import/no-mutable-exports
let _cache: { [id: string]: RefField | Promise<Opt<RefField>> } = {};
+ export function Cache() {
+ return _cache;
+ }
- export function FindDocByTitle(title: string) {
- const foundDocId =
- title &&
- Array.from(Object.keys(_cache))
- .filter(key => _cache[key] instanceof Doc)
- .find(key => (_cache[key] as Doc).title === title);
-
- return foundDocId ? (_cache[foundDocId] as Doc) : undefined;
- }
- let cacheDocumentIds = ''; // ; separate string of all documents ids in the user's working set (cached on the server)
- export let CacheNeedsUpdate = false;
- export function UPDATE_SERVER_CACHE() {
- const prototypes = Object.values(DocumentType)
- .filter(type => type !== DocumentType.NONE)
- .map(type => _cache[type + 'Proto'])
- .filter(doc => doc instanceof Doc)
- .map(doc => doc as Doc);
- const references = new Set<Doc>(prototypes);
- Doc.FindReferences(Doc.UserDoc(), references, undefined);
- DocListCast(DocCast(Doc.UserDoc().myLinkDatabase).data).forEach(link => {
- if (!references.has(DocCast(link.link_anchor_1)) && !references.has(DocCast(link.link_anchor_2))) {
- Doc.RemoveDocFromList(DocCast(Doc.UserDoc().myLinkDatabase), 'data', link);
- Doc.AddDocToList(Doc.MyRecentlyClosed, undefined, link);
- }
- });
- LinkManager.Instance.userLinkDBs.forEach(linkDb => Doc.FindReferences(linkDb, references, undefined));
- const filtered = Array.from(references);
-
- const newCacheUpdate = filtered.map(doc => doc[Id]).join(';');
- if (newCacheUpdate === cacheDocumentIds) return;
- cacheDocumentIds = newCacheUpdate;
-
- // print out cached docs
- console.log('Set cached docs = ');
- const is_filtered = filtered.filter(doc => !Doc.IsSystem(doc));
- const strings = is_filtered.map(doc => StrCast(doc.title) + ' ' + (Doc.IsDataProto(doc) ? '(data)' : '(embedding)'));
- strings.sort().forEach((str, i) => console.log(i.toString() + ' ' + str));
-
- rp.post(Utils.prepend('/setCacheDocumentIds'), {
- body: {
- cacheDocumentIds,
- },
- json: true,
- });
+ function errorFunc(): never {
+ throw new Error("Can't use DocServer without calling init first");
+ }
+ let _UpdateField: (id: string, diff: any) => void = errorFunc;
+ let _CreateField: (field: RefField) => void = errorFunc;
+
+ export function AddServerHandler<T>(socket: Socket, message: Message<T>, handler: (args: T) => any) {
+ socket.on(message.Message, Utils.loggingCallback('Incoming', handler, message.Name));
+ }
+ export function Emit<T>(socket: Socket, message: Message<T>, args: T) {
+ // log('Emit', message.Name, args, false);
+ socket.emit(message.Message, args);
+ }
+ export function EmitCallback<T>(socket: Socket, message: Message<T>, args: T): Promise<any>;
+ export function EmitCallback<T>(socket: Socket, message: Message<T>, args: T, fn: (args: any) => any): void;
+ export function EmitCallback<T>(socket: Socket, message: Message<T>, args: T, fn?: (args: any) => any): void | Promise<any> {
+ // log('Emit', message.Name, args, false);
+ if (fn) {
+ socket.emit(message.Message, args, Utils.loggingCallback('Receiving', fn, message.Name));
+ } else {
+ return new Promise<any>(res => {
+ socket.emit(message.Message, args, Utils.loggingCallback('Receiving', res, message.Name));
+ });
+ }
}
- export let _socket: Socket;
+
+ let _socket: Socket;
// this client's distinct GUID created at initialization
let USER_ID: string;
// indicates whether or not a document is currently being udpated, and, if so, its id
export enum WriteMode {
- Default = 0, //Anything goes
- Playground = 1, //Playground (write own/no read other updates)
- LiveReadonly = 2, //Live Readonly (no write/read others)
- LivePlayground = 3, //Live Playground (write own/read others)
+ Default = 0, // Anything goes
+ Playground = 1, // Playground (write own/no read other updates)
+ LiveReadonly = 2, // Live Readonly (no write/read others)
+ LivePlayground = 3, // Live Playground (write own/read others)
}
const fieldWriteModes: { [field: string]: WriteMode } = {};
const docsWithUpdates: { [field: string]: Set<Doc> } = {};
- export var PlaygroundFields: string[] = [];
+ export const PlaygroundFields: string[] = [];
export function setLivePlaygroundFields(livePlaygroundFields: string[]) {
DocServer.PlaygroundFields.push(...livePlaygroundFields);
livePlaygroundFields.forEach(f => DocServer.setFieldWriteMode(f, DocServer.WriteMode.LivePlayground));
@@ -116,7 +96,7 @@ export namespace DocServer {
}
export function getFieldWriteMode(field: string) {
- return Doc.CurrentUserEmail === 'guest' ? WriteMode.LivePlayground : fieldWriteModes[field] || WriteMode.Default;
+ return ClientUtils.CurrentUserEmail() === 'guest' ? WriteMode.LivePlayground : fieldWriteModes[field] || WriteMode.Default;
}
export function registerDocWithCachedUpdate(doc: Doc, field: string, oldValue: any) {
@@ -132,20 +112,20 @@ export namespace DocServer {
export namespace Mobile {
export function dispatchGesturePoints(content: GestureContent) {
- Utils.Emit(_socket, MessageStore.GesturePoints, content);
+ DocServer.Emit(_socket, MessageStore.GesturePoints, content);
}
export function dispatchOverlayTrigger(content: MobileInkOverlayContent) {
// _socket.emit("dispatchBoxTrigger");
- Utils.Emit(_socket, MessageStore.MobileInkOverlayTrigger, content);
+ DocServer.Emit(_socket, MessageStore.MobileInkOverlayTrigger, content);
}
export function dispatchOverlayPositionUpdate(content: UpdateMobileInkOverlayPositionContent) {
- Utils.Emit(_socket, MessageStore.UpdateMobileInkOverlayPosition, content);
+ DocServer.Emit(_socket, MessageStore.UpdateMobileInkOverlayPosition, content);
}
export function dispatchMobileDocumentUpload(content: MobileDocumentUploadContent) {
- Utils.Emit(_socket, MessageStore.MobileDocumentUpload, content);
+ DocServer.Emit(_socket, MessageStore.MobileDocumentUpload, content);
}
}
@@ -167,55 +147,14 @@ export namespace DocServer {
window.location.reload();
}
- export function init(protocol: string, hostname: string, port: number, identifier: string) {
- _cache = {};
- USER_ID = identifier;
- protocol = protocol.startsWith('https') ? 'wss' : 'ws';
- _socket = require('socket.io-client')(`${protocol}://${hostname}:${port}`, { transports: ['websocket'], rejectUnauthorized: false });
- // io.connect(`https://7f079dda.ngrok.io`);// if using ngrok, create a special address for the websocket
-
- _GetCachedRefField = _GetCachedRefFieldImpl;
- _GetRefField = _GetRefFieldImpl;
- _GetRefFields = _GetRefFieldsImpl;
- _CreateField = _CreateFieldImpl;
- _UpdateField = _UpdateFieldImpl;
-
- /**
- * Whenever the server sends us its handshake message on our
- * websocket, we use the above function to return the handshake.
- */
- Utils.AddServerHandler(_socket, MessageStore.Foo, onConnection);
- Utils.AddServerHandler(_socket, MessageStore.UpdateField, respondToUpdate);
- Utils.AddServerHandler(_socket, MessageStore.DeleteField, respondToDelete);
- Utils.AddServerHandler(_socket, MessageStore.DeleteFields, respondToDelete);
- Utils.AddServerHandler(_socket, MessageStore.ConnectionTerminated, alertUser);
-
- // // mobile ink overlay socket events to communicate between mobile view and desktop view
- // _socket.addEventListener('receiveGesturePoints', (content: GestureContent) => {
- // // MobileInkOverlay.Instance.drawStroke(content);
- // });
- // _socket.addEventListener('receiveOverlayTrigger', (content: MobileInkOverlayContent) => {
- // //GestureOverlay.Instance.enableMobileInkOverlay(content);
- // // MobileInkOverlay.Instance.initMobileInkOverlay(content);
- // });
- // _socket.addEventListener('receiveUpdateOverlayPosition', (content: UpdateMobileInkOverlayPositionContent) => {
- // // MobileInkOverlay.Instance.updatePosition(content);
- // });
- // _socket.addEventListener('receiveMobileDocumentUpload', (content: MobileDocumentUploadContent) => {
- // // MobileInkOverlay.Instance.uploadDocument(content);
- // });
- }
-
- function errorFunc(): never {
- throw new Error("Can't use DocServer without calling init first");
- }
-
export namespace Control {
let _isReadOnly = false;
export function makeReadOnly() {
if (!_isReadOnly) {
_isReadOnly = true;
- _CreateField = field => (_cache[field[Id]] = field);
+ _CreateField = field => {
+ _cache[field[Id]] = field;
+ };
_UpdateField = emptyFunction;
// _RespondToUpdate = emptyFunction; // bcz: option: don't clear RespondToUpdate to continue to receive updates as others change the DB
}
@@ -252,7 +191,7 @@ export namespace DocServer {
* all documents in the database.
*/
export function deleteDatabase() {
- Utils.Emit(_socket, MessageStore.DeleteAll, {});
+ DocServer.Emit(_socket, MessageStore.DeleteAll, {});
}
}
@@ -275,7 +214,7 @@ export namespace DocServer {
// synchronously, we emit a single callback to the server requesting the serialized (i.e. represented by a string)
// field for the given ids. This returns a promise, which, when resolved, indicates the the JSON serialized version of
// the field has been returned from the server
- const getSerializedField = Utils.EmitCallback(_socket, MessageStore.GetRefField, id);
+ const getSerializedField = DocServer.EmitCallback(_socket, MessageStore.GetRefField, id);
// when the serialized RefField has been received, go head and begin deserializing it into an object.
// Here, once deserialized, we also invoke .proto to 'load' the document's prototype, which ensures that all
@@ -283,7 +222,7 @@ export namespace DocServer {
const deserializeField = getSerializedField.then(async fieldJson => {
// deserialize
const field = await SerializationHelper.Deserialize(fieldJson);
- if (force && field instanceof Doc && cached instanceof Doc) {
+ if (force && field && cached instanceof Doc) {
cached[UpdatingFromServer] = true;
Array.from(Object.keys(field)).forEach(key => {
const fieldval = field[key];
@@ -294,7 +233,8 @@ export namespace DocServer {
});
cached[UpdatingFromServer] = false;
return cached;
- } else if (field !== undefined) {
+ }
+ if (field !== undefined) {
_cache[id] = field;
} else {
delete _cache[id];
@@ -308,24 +248,25 @@ export namespace DocServer {
// being retrieved and cached
!force && (_cache[id] = deserializeField);
return force ? (cached as any) : deserializeField;
- } else if (cached instanceof Promise) {
+ }
+ if (cached instanceof Promise) {
// BEING RETRIEVED AND CACHED => some other caller previously (likely recently) called GetRefField(s),
// and requested the document I'm looking for. Shouldn't fetch again, just
// return this promise which will resolve to the field itself (see 7)
return cached;
- } else {
- // CACHED => great, let's just return the cached field we have
- return Promise.resolve(cached).then(field => {
- //(field instanceof Doc) && fetchProto(field);
- return field;
- });
}
+ // CACHED => great, let's just return the cached field we have
+ return Promise.resolve(cached).then(
+ field => field
+ // (field instanceof Doc) && fetchProto(field);
+ );
};
const _GetCachedRefFieldImpl = (id: string): Opt<RefField> => {
const cached = _cache[id];
if (cached !== undefined && !(cached instanceof Promise)) {
return cached;
}
+ return undefined;
};
let _GetRefField: (id: string, force: boolean) => Promise<Opt<RefField>> = errorFunc;
@@ -339,15 +280,15 @@ export namespace DocServer {
}
export async function getYoutubeChannels() {
- return await Utils.EmitCallback(_socket, MessageStore.YoutubeApiQuery, { type: YoutubeQueryTypes.Channels });
+ return DocServer.EmitCallback(_socket, MessageStore.YoutubeApiQuery, { type: YoutubeQueryTypes.Channels });
}
export function getYoutubeVideos(videoTitle: string, callBack: (videos: any[]) => void) {
- Utils.EmitCallback(_socket, MessageStore.YoutubeApiQuery, { type: YoutubeQueryTypes.SearchVideo, userInput: videoTitle }, callBack);
+ DocServer.EmitCallback(_socket, MessageStore.YoutubeApiQuery, { type: YoutubeQueryTypes.SearchVideo, userInput: videoTitle }, callBack);
}
export function getYoutubeVideoDetails(videoIds: string, callBack: (videoDetails: any[]) => void) {
- Utils.EmitCallback(_socket, MessageStore.YoutubeApiQuery, { type: YoutubeQueryTypes.VideoDetails, videoIds: videoIds }, callBack);
+ DocServer.EmitCallback(_socket, MessageStore.YoutubeApiQuery, { type: YoutubeQueryTypes.VideoDetails, videoIds: videoIds }, callBack);
}
/**
@@ -360,21 +301,24 @@ export namespace DocServer {
const requestedIds: string[] = [];
const promises: Promise<any>[] = [];
- let defaultRes: any = undefined;
- const defaultPromise = new Promise<any>(res => (defaultRes = res));
- let defaultPromises: { p: Promise<any>; id: string }[] = [];
+ let defaultRes: any;
+ const defaultPromise = new Promise<any>(res => {
+ defaultRes = res;
+ });
+ const defaultPromises: { p: Promise<any>; id: string }[] = [];
// 1) an initial pass through the cache to determine
// i) which documents need to be fetched
// ii) which are already in the process of being fetched
// iii) which already exist in the cache
- for (const id of ids.filter(id => id)) {
+ // eslint-disable-next-line no-restricted-syntax
+ for (const id of ids.filter(filterid => filterid)) {
const cached = _cache[id];
if (cached === undefined) {
defaultPromises.push({
id,
- p: (_cache[id] = new Promise<any>(async res => {
- await defaultPromise;
- res(_cache[id]);
+ // eslint-disable-next-line no-loop-func
+ p: (_cache[id] = new Promise<any>(res => {
+ defaultPromise.then(() => res(_cache[id]));
})),
});
// NOT CACHED => we'll have to send a request to the server
@@ -396,27 +340,39 @@ export namespace DocServer {
// fields for the given ids. This returns a promise, which, when resolved, indicates that all the JSON serialized versions of
// the fields have been returned from the server
console.log('Requesting ' + requestedIds.length);
- setTimeout(() => runInAction(() => (FieldLoader.ServerLoadStatus.requested = requestedIds.length)));
- const serializedFields = await Utils.EmitCallback(_socket, MessageStore.GetRefFields, requestedIds);
+ setTimeout(() =>
+ runInAction(() => {
+ FieldLoader.ServerLoadStatus.requested = requestedIds.length;
+ })
+ );
+ const serializedFields = await DocServer.EmitCallback(_socket, MessageStore.GetRefFields, requestedIds);
// 3) when the serialized RefFields have been received, go head and begin deserializing them into objects.
// Here, once deserialized, we also invoke .proto to 'load' the documents' prototypes, which ensures that all
// future .proto calls on the Doc won't have to go farther than the cache to get their actual value.
let processed = 0;
console.log('deserializing ' + serializedFields.length + ' fields');
+ // eslint-disable-next-line no-restricted-syntax
for (const field of serializedFields) {
processed++;
if (processed % 150 === 0) {
- runInAction(() => (FieldLoader.ServerLoadStatus.retrieved = processed));
- await new Promise(res => setTimeout(res)); // force loading to yield to splash screen rendering to update progress
+ // eslint-disable-next-line no-loop-func
+ runInAction(() => {
+ FieldLoader.ServerLoadStatus.retrieved = processed;
+ });
+ // eslint-disable-next-line no-await-in-loop
+ await new Promise(res => {
+ setTimeout(res);
+ }); // force loading to yield to splash screen rendering to update progress
}
const cached = _cache[field.id];
if (!cached || (cached instanceof Promise && defaultPromises.some(dp => dp.p === cached))) {
// deserialize
// adds to a list of promises that will be awaited asynchronously
promises.push(
+ // eslint-disable-next-line no-loop-func
(_cache[field.id] = SerializationHelper.Deserialize(field).then(deserialized => {
- //overwrite or delete any promises (that we inserted as flags
+ // overwrite or delete any promises (that we inserted as flags
// to indicate that the field was in the process of being fetched). Now everything
// should be an actual value within or entirely absent from the cache.
if (deserialized !== undefined) {
@@ -436,7 +392,7 @@ export namespace DocServer {
// get the resolved field
} else if (cached instanceof Promise) {
console.log('.');
- //promises.push(cached);
+ // promises.push(cached);
} else if (field) {
// console.log('-');
}
@@ -453,7 +409,7 @@ export namespace DocServer {
// 6) with this confidence, we can now go through and update the cache at the ids of the fields that
// we explicitly had to fetch. To finish it off, we add whatever value we've come up with for a given
// id to the soon-to-be-returned field mapping.
- //ids.forEach(id => (map[id] = _cache[id] as any));
+ // ids.forEach(id => (map[id] = _cache[id] as any));
// 7) those promises we encountered in the else if of 1), which represent
// other callers having already submitted a request to the server for (a) document(s)
@@ -462,7 +418,7 @@ export namespace DocServer {
//
// fortunately, those other callers will also hit their own version of 6) and clean up
// the shared cache when these promises resolve, so all we have to do is...
- //const otherCallersFetching = await Promise.all(promises);
+ // const otherCallersFetching = await Promise.all(promises);
// ...extract the RefFields returned from the resolution of those promises and add them to our
// own map.
// waitingIds.forEach((id, index) => (map[id] = otherCallersFetching[index]));
@@ -487,6 +443,10 @@ export namespace DocServer {
}
// WRITE A NEW DOCUMENT TO THE SERVER
+ let _cacheNeedsUpdate = false;
+ export function CacheNeedsUpdate() {
+ return _cacheNeedsUpdate;
+ }
/**
* A wrapper around the function local variable _createField.
@@ -495,18 +455,16 @@ export namespace DocServer {
* @param field the [RefField] to be serialized and sent to the server to be stored in the database
*/
export function CreateField(field: RefField) {
- CacheNeedsUpdate = true;
+ _cacheNeedsUpdate = true;
_CreateField(field);
}
function _CreateFieldImpl(field: RefField) {
_cache[field[Id]] = field;
const initialState = SerializationHelper.Serialize(field);
- Doc.CurrentUserEmail !== 'guest' && Utils.Emit(_socket, MessageStore.CreateField, initialState);
+ ClientUtils.CurrentUserEmail() !== 'guest' && DocServer.Emit(_socket, MessageStore.CreateField, initialState);
}
- let _CreateField: (field: RefField) => void = errorFunc;
-
// NOTIFY THE SERVER OF AN UPDATE TO A DOC'S STATE
/**
@@ -522,13 +480,11 @@ export namespace DocServer {
}
function _UpdateFieldImpl(id: string, diff: any) {
- !DocServer.Control.isReadOnly() && Doc.CurrentUserEmail !== 'guest' && Utils.Emit(_socket, MessageStore.UpdateField, { id, diff });
+ !DocServer.Control.isReadOnly() && ClientUtils.CurrentUserEmail() !== 'guest' && DocServer.Emit(_socket, MessageStore.UpdateField, { id, diff });
}
- let _UpdateField: (id: string, diff: any) => void = errorFunc;
-
function _respondToUpdateImpl(diff: any) {
- const id = diff.id;
+ const { id } = diff;
// to be valid, the Diff object must reference
// a document's id
if (id === undefined) {
@@ -559,11 +515,11 @@ export namespace DocServer {
}
export function DeleteDocument(id: string) {
- Doc.CurrentUserEmail !== 'guest' && Utils.Emit(_socket, MessageStore.DeleteField, id);
+ ClientUtils.CurrentUserEmail() !== 'guest' && DocServer.Emit(_socket, MessageStore.DeleteField, id);
}
export function DeleteDocuments(ids: string[]) {
- Doc.CurrentUserEmail !== 'guest' && Utils.Emit(_socket, MessageStore.DeleteFields, ids);
+ ClientUtils.CurrentUserEmail() !== 'guest' && DocServer.Emit(_socket, MessageStore.DeleteFields, ids);
}
function _respondToDeleteImpl(ids: string | string[]) {
@@ -577,7 +533,7 @@ export namespace DocServer {
}
}
- let _RespondToUpdate = _respondToUpdateImpl;
+ const _RespondToUpdate = _respondToUpdateImpl;
const _respondToDelete = _respondToDeleteImpl;
function respondToUpdate(diff: any) {
@@ -587,4 +543,28 @@ export namespace DocServer {
function respondToDelete(ids: string | string[]) {
_respondToDelete(ids);
}
+
+ export function init(protocol: string, hostname: string, port: number, identifier: string) {
+ _cache = {};
+ USER_ID = identifier;
+ _socket = io(`${protocol.startsWith('https') ? 'wss' : 'ws'}://${hostname}:${port}`, { transports: ['websocket'], rejectUnauthorized: false });
+ _socket.on('connect_error', (err: any) => console.log(err));
+ // io.connect(`https://7f079dda.ngrok.io`);// if using ngrok, create a special address for the websocket
+
+ _GetCachedRefField = _GetCachedRefFieldImpl;
+ SetObjGetRefField((_GetRefField = _GetRefFieldImpl));
+ SetObjGetRefFields((_GetRefFields = _GetRefFieldsImpl));
+ _CreateField = _CreateFieldImpl;
+ _UpdateField = _UpdateFieldImpl;
+
+ /**
+ * Whenever the server sends us its handshake message on our
+ * websocket, we use the above function to return the handshake.
+ */
+ DocServer.AddServerHandler(_socket, MessageStore.Foo, onConnection);
+ DocServer.AddServerHandler(_socket, MessageStore.UpdateField, respondToUpdate);
+ DocServer.AddServerHandler(_socket, MessageStore.DeleteField, respondToDelete);
+ DocServer.AddServerHandler(_socket, MessageStore.DeleteFields, respondToDelete);
+ DocServer.AddServerHandler(_socket, MessageStore.ConnectionTerminated, alertUser);
+ }
}