diff options
-rw-r--r-- | src/server/websocket.ts | 244 |
1 files changed, 89 insertions, 155 deletions
diff --git a/src/server/websocket.ts b/src/server/websocket.ts index 905dbcf57..f10455680 100644 --- a/src/server/websocket.ts +++ b/src/server/websocket.ts @@ -3,13 +3,14 @@ import { createServer } from 'https'; import * as _ from 'lodash'; import { networkInterfaces } from 'os'; import { Server, Socket } from 'socket.io'; +import { SecureContextOptions } from 'tls'; import { ServerUtils } from '../ServerUtils'; +import { serializedDoctype, serializedFieldsType } from '../fields/ObjectField'; import { logPort } from './ActionUtilities'; import { Client } from './Client'; import { DashStats } from './DashStats'; import { DocumentsCollection } from './IDatabase'; -import { Diff, GestureContent, MessageStore, MobileDocumentUploadContent, MobileInkOverlayContent, Transferable, Types, UpdateMobileInkOverlayPositionContent } from './Message'; -import { Search } from './Search'; +import { Diff, GestureContent, MessageStore } from './Message'; import { resolvedPorts, socketMap, timeMap, userOperations } from './SocketData'; import { initializeGuest } from './authentication/DashUserModel'; import { Database } from './database'; @@ -25,25 +26,10 @@ export namespace WebSocket { socket.broadcast.emit('receiveGesturePoints', content); } - function processOverlayTrigger(socket: Socket, content: MobileInkOverlayContent) { - socket.broadcast.emit('receiveOverlayTrigger', content); - } - - function processUpdateOverlayPosition(socket: Socket, content: UpdateMobileInkOverlayPositionContent) { - socket.broadcast.emit('receiveUpdateOverlayPosition', content); - } - - function processMobileDocumentUpload(socket: Socket, content: MobileDocumentUploadContent) { - socket.broadcast.emit('receiveMobileDocumentUpload', content); - } - export async function doDelete(onlyFields = true) { const target: string[] = []; onlyFields && target.push(DocumentsCollection); await Database.Instance.dropSchema(...target); - if (process.env.DISABLE_SEARCH !== 'true') { - await Search.clear(); - } initializeGuest(); } @@ -63,31 +49,15 @@ export namespace WebSocket { DashStats.logUserLogin(userEmail); } - function getField([id, callback]: [string, (result?: Transferable) => void]) { - Database.Instance.getDocument(id, (result?: Transferable) => callback(result)); - } - - function getFields([ids, callback]: [string[], (result: Transferable[]) => void]) { - Database.Instance.getDocuments(ids, callback); - } - - function setField(socket: Socket, newValue: Transferable) { - Database.Instance.update(newValue.id, newValue, () => socket.broadcast.emit(MessageStore.SetField.Message, newValue)); // broadcast set value to all other clients - if (newValue.type === Types.Text) { - // if the newValue has sring type, then it's suitable for searching -- pass it to SOLR - Search.updateDocument({ id: newValue.id, data: { set: newValue.data } }); - } - } - - function GetRefFieldLocal([id, callback]: [string, (result?: Transferable) => void]) { + function GetRefFieldLocal([id, callback]: [string, (result?: serializedDoctype) => void]) { return Database.Instance.getDocument(id, callback); } - function GetRefField([id, callback]: [string, (result?: Transferable) => void]) { + function GetRefField([id, callback]: [string, (result?: serializedDoctype) => void]) { process.stdout.write(`+`); GetRefFieldLocal([id, callback]); } - function GetRefFields([ids, callback]: [string[], (result?: Transferable[]) => void]) { + function GetRefFields([ids, callback]: [string[], (result?: serializedDoctype[]) => void]) { process.stdout.write(`${ids.length}…`); Database.Instance.getDocuments(ids, callback); } @@ -151,11 +121,11 @@ export namespace WebSocket { const { diff, socket } = next; if (diff.diff.$addToSet) { // eslint-disable-next-line no-use-before-define - return GetRefFieldLocal([diff.id, (result?: Transferable) => addToListField(socket, diff, result)]); // would prefer to have Mongo handle list additions direclty, but for now handle it on our own + return GetRefFieldLocal([diff.id, (result?: serializedDoctype) => addToListField(socket, diff, result)]); // would prefer to have Mongo handle list additions direclty, but for now handle it on our own } if (diff.diff.$remFromSet) { // eslint-disable-next-line no-use-before-define - return GetRefFieldLocal([diff.id, (result?: Transferable) => remFromListField(socket, diff, result)]); // would prefer to have Mongo handle list additions direclty, but for now handle it on our own + return GetRefFieldLocal([diff.id, (result?: serializedDoctype) => remFromListField(socket, diff, result)]); // would prefer to have Mongo handle list additions direclty, but for now handle it on our own } // eslint-disable-next-line no-use-before-define return SetField(socket, diff); @@ -163,37 +133,41 @@ export namespace WebSocket { return !pendingOps.get(id)!.length && pendingOps.delete(id); } - function addToListField(socket: Socket, diffIn: Diff, curListItems?: Transferable): void { + function addToListField(socket: Socket, diffIn: Diff, listDoc?: serializedDoctype): void { const diff = diffIn; diff.diff.$set = diff.diff.$addToSet; delete diff.diff.$addToSet; // convert add to set to a query of the current fields, and then a set of the composition of the new fields with the old ones - const updatefield = Array.from(Object.keys(diff.diff.$set))[0]; - const newListItems = diff.diff.$set[updatefield]?.fields; + const updatefield = Array.from(Object.keys(diff.diff.$set ?? {}))[0]; + const newListItems = diff.diff.$set?.[updatefield]?.fields; if (!newListItems) { console.log('Error: addToListField - no new list items'); return; } - const curList = (curListItems as any)?.fields?.[updatefield.replace('fields.', '')]?.fields.filter((item: any) => item !== undefined) || []; - diff.diff.$set[updatefield].fields = [...curList, ...newListItems]; // , ...newListItems.filter((newItem: any) => newItem === null || !curList.some((curItem: any) => curItem.fieldId ? curItem.fieldId === newItem.fieldId : curItem.heading ? curItem.heading === newItem.heading : curItem === newItem))]; - const sendBack = diff.diff.length !== diff.diff.$set[updatefield].fields.length; - delete diff.diff.length; - Database.Instance.update( - diff.id, - diff.diff, - () => { - if (sendBack) { - console.log('Warning: list modified during update. Composite list is being returned.'); - const { id } = socket; - (socket as any).id = ''; // bcz: HACK. this prevents the update message from going back to the client that made the change. - socket.broadcast.emit(MessageStore.UpdateField.Message, diff); - (socket as any).id = id; - } else { - socket.broadcast.emit(MessageStore.UpdateField.Message, diff); - } - dispatchNextOp(diff.id); - }, - false - ); + const listItems = listDoc?.fields?.[updatefield.replace('fields.', '')]?.fields.filter(item => item !== undefined) ?? []; + if (diff.diff.$set?.[updatefield]?.fields !== undefined) { + diff.diff.$set[updatefield]!.fields = [...listItems, ...newListItems]; // , ...newListItems.filter((newItem: any) => newItem === null || !curList.some((curItem: any) => curItem.fieldId ? curItem.fieldId === newItem.fieldId : curItem.heading ? curItem.heading === newItem.heading : curItem === newItem))]; + const sendBack = diff.diff.length !== diff.diff.$set[updatefield]!.fields?.length; + delete diff.diff.length; + Database.Instance.update( + diff.id, + diff.diff, + () => { + if (sendBack) { + console.log('Warning: list modified during update. Composite list is being returned.'); + const { id } = socket; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (socket as any).id = ''; // bcz: HACK to reference private variable. this allows the update message to go back to the client that made the change. + socket.broadcast.emit(MessageStore.UpdateField.Message, diff); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (socket as any).id = id; + } else { + socket.broadcast.emit(MessageStore.UpdateField.Message, diff); + } + dispatchNextOp(diff.id); + }, + false + ); + } } /** @@ -208,7 +182,7 @@ export namespace WebSocket { * the data * @returns the closest index with the same value or -1 if the element was not found. */ - function findClosestIndex(list: any, indexesToDelete: number[], value: any, hintIndex: number) { + function findClosestIndex(list: { fieldId: string; __type: string }[], indexesToDelete: number[], value: { fieldId: string; __type: string }, hintIndex: number) { let closestIndex = -1; for (let i = 0; i < list.length; i++) { if (list[i] === value && !indexesToDelete.includes(i)) { @@ -232,63 +206,63 @@ export namespace WebSocket { * items to delete) * @param curListItems the server's current copy of the data */ - function remFromListField(socket: Socket, diffIn: Diff, curListItems?: Transferable): void { + function remFromListField(socket: Socket, diffIn: Diff, curListItems?: serializedDoctype): void { const diff = diffIn; - diff.diff.$set = diff.diff.$remFromSet; + diff.diff.$set = diff.diff.$remFromSet as serializedFieldsType; + const hint = diff.diff.$remFromSet?.hint; delete diff.diff.$remFromSet; - const updatefield = Array.from(Object.keys(diff.diff.$set))[0]; - const remListItems = diff.diff.$set[updatefield].fields; - const curList = (curListItems as any)?.fields?.[updatefield.replace('fields.', '')]?.fields.filter((f: any) => f !== null) || []; - const { hint } = diff.diff.$set; - - if (hint) { - // indexesToRemove stores the indexes that we mark for deletion, which is later used to filter the list (delete the elements) - const indexesToRemove: number[] = []; - for (let i = 0; i < hint.deleteCount; i++) { - if (curList.length > i + hint.start && _.isEqual(curList[i + hint.start], remListItems[i])) { - indexesToRemove.push(i + hint.start); - } else { - const closestIndex = findClosestIndex(curList, indexesToRemove, remListItems[i], i + hint.start); - if (closestIndex !== -1) { - indexesToRemove.push(closestIndex); + const updatefield = Array.from(Object.keys(diff.diff.$set ?? {}))[0]; + const remListItems = diff.diff.$set[updatefield]?.fields; + if (diff.diff.$set[updatefield] !== undefined && remListItems) { + const curList = curListItems?.fields?.[updatefield.replace('fields.', '')]?.fields.filter(f => f !== null) || []; + + if (hint) { + // indexesToRemove stores the indexes that we mark for deletion, which is later used to filter the list (delete the elements) + const indexesToRemove: number[] = []; + for (let i = 0; i < hint.deleteCount; i++) { + if (curList.length > i + hint.start && _.isEqual(curList[i + hint.start], remListItems[i])) { + indexesToRemove.push(i + hint.start); } else { - console.log('Item to delete was not found - index = -1'); + const closestIndex = findClosestIndex(curList, indexesToRemove, remListItems[i], i + hint.start); + if (closestIndex !== -1) { + indexesToRemove.push(closestIndex); + } else { + console.log('Item to delete was not found - index = -1'); + } } } + diff.diff.$set[updatefield]!.fields = curList.filter((curItem, index) => !indexesToRemove.includes(index)); + } else { + // go back to the original way to delete if we didn't receive + // a hint from the client + diff.diff.$set[updatefield]!.fields = curList?.filter(curItem => !remListItems.some(remItem => (remItem.fieldId ? remItem.fieldId === curItem.fieldId : remItem.heading ? remItem.heading === curItem.heading : remItem === curItem))); } - - diff.diff.$set[updatefield].fields = curList?.filter((curItem: any, index: number) => !indexesToRemove.includes(index)); - } else { - // go back to the original way to delete if we didn't receive - // a hint from the client - diff.diff.$set[updatefield].fields = curList?.filter( - (curItem: any) => !remListItems.some((remItem: any) => (remItem.fieldId ? remItem.fieldId === curItem.fieldId : remItem.heading ? remItem.heading === curItem.heading : remItem === curItem)) + // if the client and server have different versions of the data after + // deletion, they will have different lengths and the server will + // send its version of the data to the client + const sendBack = diff.diff.length !== remListItems.length; + delete diff.diff.length; + Database.Instance.update( + diff.id, + diff.diff, + () => { + if (sendBack) { + // the two copies are different, so the server sends its copy. + console.log('SEND BACK'); + const { id } = socket; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (socket as any).id = ''; // bcz: HACK to access private variable this allows the update message to go back to the client that made the change. + socket.broadcast.emit(MessageStore.UpdateField.Message, diff); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (socket as any).id = id; + } else { + socket.broadcast.emit(MessageStore.UpdateField.Message, diff); + } + dispatchNextOp(diff.id); + }, + false ); } - - // if the client and server have different versions of the data after - // deletion, they will have different lengths and the server will - // send its version of the data to the client - const sendBack = diff.diff.length !== diff.diff.$set[updatefield].fields.length; - delete diff.diff.length; - Database.Instance.update( - diff.id, - diff.diff, - () => { - if (sendBack) { - // the two copies are different, so the server sends its copy. - console.log('SEND BACK'); - const { id } = socket; - (socket as any).id = ''; // bcz: HACK. this prevents the update message from going back to the client that made the change. - socket.broadcast.emit(MessageStore.UpdateField.Message, diff); - (socket as any).id = id; - } else { - socket.broadcast.emit(MessageStore.UpdateField.Message, diff); - } - dispatchNextOp(diff.id); - }, - false - ); } function UpdateField(socket: Socket, diff: Diff) { @@ -307,43 +281,16 @@ export namespace WebSocket { } pendingOps.set(diff.id, [{ diff, socket }]); if (diff.diff.$addToSet) { - return GetRefFieldLocal([diff.id, (result?: Transferable) => addToListField(socket, diff, result)]); // would prefer to have Mongo handle list additions direclty, but for now handle it on our own + return GetRefFieldLocal([diff.id, (result?: serializedDoctype) => addToListField(socket, diff, result)]); // would prefer to have Mongo handle list additions direclty, but for now handle it on our own } if (diff.diff.$remFromSet) { - return GetRefFieldLocal([diff.id, (result?: Transferable) => remFromListField(socket, diff, result)]); // would prefer to have Mongo handle list additions direclty, but for now handle it on our own + return GetRefFieldLocal([diff.id, (result?: serializedDoctype) => remFromListField(socket, diff, result)]); // would prefer to have Mongo handle list additions direclty, but for now handle it on our own } // eslint-disable-next-line no-use-before-define return SetField(socket, diff); } function SetField(socket: Socket, diff: Diff /* , curListItems?: Transferable */) { Database.Instance.update(diff.id, diff.diff, () => socket.broadcast.emit(MessageStore.UpdateField.Message, diff), false); - const docfield = diff.diff.$set || diff.diff.$unset; - if (docfield) { - const update: any = { id: diff.id }; - let dynfield = false; - // eslint-disable-next-line no-restricted-syntax - for (let key in docfield) { - // eslint-disable-next-line no-continue - if (!key.startsWith('fields.')) continue; - dynfield = true; - const val = docfield[key]; - key = key.substring(7); - Object.values(suffixMap).forEach(suf => { - update[key + getSuffix(suf)] = { set: null }; - }); - const term = ToSearchTerm(val); - if (term !== undefined) { - const { suffix, value } = term; - update[key + suffix] = { set: value }; - if (key.endsWith('modificationDate')) { - update['modificationDate' + suffix] = value; - } - } - } - if (dynfield) { - Search.updateDocument(update); - } - } dispatchNextOp(diff.id); } @@ -351,21 +298,15 @@ export namespace WebSocket { Database.Instance.delete({ _id: id }).then(() => { socket.broadcast.emit(MessageStore.DeleteField.Message, id); }); - - Search.deleteDocuments([id]); } function DeleteFields(socket: Socket, ids: string[]) { Database.Instance.delete({ _id: { $in: ids } }).then(() => { socket.broadcast.emit(MessageStore.DeleteFields.Message, ids); }); - Search.deleteDocuments(ids); } - function CreateField(newValue: any) { - Database.Instance.insert(newValue); - } - export async function initialize(isRelease: boolean, credentials: any) { + export async function initialize(isRelease: boolean, credentials: SecureContextOptions) { let io: Server; if (isRelease) { const { socketPort } = process.env; @@ -422,21 +363,14 @@ export namespace WebSocket { ServerUtils.Emit(socket, MessageStore.Foo, 'handshooken'); ServerUtils.AddServerHandler(socket, MessageStore.Bar, guid => barReceived(socket, guid)); - ServerUtils.AddServerHandler(socket, MessageStore.SetField, args => setField(socket, args)); - ServerUtils.AddServerHandlerCallback(socket, MessageStore.GetField, getField); - ServerUtils.AddServerHandlerCallback(socket, MessageStore.GetFields, getFields); if (isRelease) { ServerUtils.AddServerHandler(socket, MessageStore.DeleteAll, () => doDelete(false)); } - ServerUtils.AddServerHandler(socket, MessageStore.CreateField, CreateField); ServerUtils.AddServerHandler(socket, MessageStore.UpdateField, diff => UpdateField(socket, diff)); ServerUtils.AddServerHandler(socket, MessageStore.DeleteField, id => DeleteField(socket, id)); ServerUtils.AddServerHandler(socket, MessageStore.DeleteFields, ids => DeleteFields(socket, ids)); ServerUtils.AddServerHandler(socket, MessageStore.GesturePoints, content => processGesturePoints(socket, content)); - ServerUtils.AddServerHandler(socket, MessageStore.MobileInkOverlayTrigger, content => processOverlayTrigger(socket, content)); - ServerUtils.AddServerHandler(socket, MessageStore.UpdateMobileInkOverlayPosition, content => processUpdateOverlayPosition(socket, content)); - ServerUtils.AddServerHandler(socket, MessageStore.MobileDocumentUpload, content => processMobileDocumentUpload(socket, content)); ServerUtils.AddServerHandlerCallback(socket, MessageStore.GetRefField, GetRefField); ServerUtils.AddServerHandlerCallback(socket, MessageStore.GetRefFields, GetRefFields); |