diff options
Diffstat (limited to 'src/client/DocServer.ts')
-rw-r--r-- | src/client/DocServer.ts | 280 |
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); + } } |