aboutsummaryrefslogtreecommitdiff
path: root/src/server/websocket.ts
diff options
context:
space:
mode:
authorbobzel <zzzman@gmail.com>2024-08-15 11:14:04 -0400
committerbobzel <zzzman@gmail.com>2024-08-15 11:14:04 -0400
commit25ea424ab2e6c32272e828b98822eb32f1fe2cab (patch)
tree8e6790ab50664401f9c4cb9172bbec0c2e88de99 /src/server/websocket.ts
parent5960fa9635c28c2b609826005cb7595ec6b9fb75 (diff)
cleaned up server list add/rem.
Diffstat (limited to 'src/server/websocket.ts')
-rw-r--r--src/server/websocket.ts248
1 files changed, 75 insertions, 173 deletions
diff --git a/src/server/websocket.ts b/src/server/websocket.ts
index f588151a5..ccbcb1c5f 100644
--- a/src/server/websocket.ts
+++ b/src/server/websocket.ts
@@ -49,12 +49,12 @@ export namespace WebSocket {
DashStats.logUserLogin(userEmail);
}
- function GetRefFieldLocal([id, callback]: [string, (result?: serializedDoctype) => void]) {
+ function GetRefFieldLocal(id: string, callback: (result?: serializedDoctype | undefined) => void) {
return Database.Instance.getDocument(id, callback);
}
function GetRefField([id, callback]: [string, (result?: serializedDoctype) => void]) {
process.stdout.write(`+`);
- GetRefFieldLocal([id, callback]);
+ GetRefFieldLocal(id, callback);
}
function GetRefFields([ids, callback]: [string[], (result?: serializedDoctype[]) => void]) {
@@ -62,112 +62,46 @@ export namespace WebSocket {
Database.Instance.getDocuments(ids, callback);
}
- const suffixMap: { [type: string]: string | [string, string | ((json: any) => any)] } = {
- number: '_n',
- string: '_t',
- boolean: '_b',
- image: ['_t', 'url'],
- video: ['_t', 'url'],
- pdf: ['_t', 'url'],
- audio: ['_t', 'url'],
- web: ['_t', 'url'],
- map: ['_t', 'url'],
- script: ['_t', value => value.script.originalScript],
- RichTextField: ['_t', value => value.Text],
- date: ['_d', value => new Date(value.date).toISOString()],
- proxy: ['_i', 'fieldId'],
- list: [
- '_l',
- list => {
- const results: any[] = [];
- // eslint-disable-next-line no-use-before-define
- list.fields.forEach((value: any) => ToSearchTerm(value) && results.push(ToSearchTerm(value)!.value));
- return results.length ? results : null;
- },
- ],
- };
-
- function ToSearchTerm(valIn: any): { suffix: string; value: any } | undefined {
- let val = valIn;
- if (val === null || val === undefined) {
- return undefined;
- }
- const type = val.__type || typeof val;
-
- let suffix = suffixMap[type];
- if (!suffix) {
- return undefined;
- }
- if (Array.isArray(suffix)) {
- const accessor = suffix[1];
- if (typeof accessor === 'function') {
- val = accessor(val);
- } else {
- val = val[accessor];
- }
- [suffix] = suffix;
- }
- return { suffix, value: val };
- }
-
- function getSuffix(value: string | [string, any]): string {
- return typeof value === 'string' ? value : value[0];
- }
const pendingOps = new Map<string, { diff: Diff; socket: Socket }[]>();
- function dispatchNextOp(id: string) {
- const next = pendingOps.get(id)!.shift();
+ function dispatchNextOp(id: string): unknown {
+ const next = pendingOps.get(id)?.shift();
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ const nextOp = (res: boolean) => dispatchNextOp(id);
if (next) {
const { diff, socket } = next;
- if (diff.diff.$addToSet) {
- // eslint-disable-next-line no-use-before-define
- 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
+ // ideally, we'd call the Database update method for all actions, but for now we handle list insertion/removal on our own
+ switch (diff.diff.$addToSet ? 'add' : diff.diff.$remFromSet ? 'rem' : 'set') {
+ case 'add': return GetRefFieldLocal(id, (result) => addToListField(socket, diff, result, nextOp)); // prettier-ignore
+ case 'rem': return GetRefFieldLocal(id, (result) => remFromListField(socket, diff, result, nextOp)); // prettier-ignore
+ default: return Database.Instance.update(id, diff.diff,
+ () => nextOp(socket.broadcast.emit(MessageStore.UpdateField.Message, diff)),
+ false
+ ); // prettier-ignore
}
- if (diff.diff.$remFromSet) {
- // eslint-disable-next-line no-use-before-define
- 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);
}
- return !pendingOps.get(id)!.length && pendingOps.delete(id);
+ return !pendingOps.get(id)?.length && pendingOps.delete(id);
}
- 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;
- if (!newListItems) {
- console.log('Error: addToListField - no new list items');
- return;
- }
- const listItems = listDoc?.fields?.[updatefield.replace('fields.', '')]?.fields.filter(item => item !== undefined) ?? [];
- if (diff.diff.$set?.[updatefield]?.fields !== undefined) {
+ function addToListField(socket: Socket, diff: Diff, listDoc: serializedDoctype | undefined, cb: (res: boolean) => void): void {
+ const $addToSet = diff.diff.$addToSet as serializedFieldsType;
+ const updatefield = Array.from(Object.keys($addToSet ?? {}))[0];
+ const newListItems = $addToSet?.[updatefield]?.fields;
+
+ if (newListItems) {
+ const length = diff.diff.$addToSet?.length;
+ diff.diff.$set = $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
+ delete diff.diff.$addToSet; // can't pass $set to Mongo, or it will do that insetead of $addToSet
+ const listItems = listDoc?.fields?.[updatefield.replace('fields.', '')]?.fields.filter(item => item) ?? [];
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
- );
- }
+
+ // if the client's list length is not the same as what we're writing to the server,
+ // then we need to send the server's version back to the client so that they are in synch.
+ // this could happen if another client made a change before the server receives the update from the first client
+ const target = length !== diff.diff.$set[updatefield].fields.length ? socket : socket.broadcast;
+ target === socket && console.log('Warning: SEND BACK: list modified during add update. Composite list is being returned.');
+ Database.Instance.update(diff.id, diff.diff, () => cb(target.emit(MessageStore.UpdateField.Message, diff)), false);
+ } else cb(false);
}
/**
@@ -206,15 +140,17 @@ export namespace WebSocket {
* items to delete)
* @param curListItems the server's current copy of the data
*/
- function remFromListField(socket: Socket, diffIn: Diff, curListItems?: serializedDoctype): void {
- const diff = diffIn;
- 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;
- if (diff.diff.$set[updatefield] !== undefined && remListItems) {
- const curList = curListItems?.fields?.[updatefield.replace('fields.', '')]?.fields.filter(f => f !== null) || [];
+ function remFromListField(socket: Socket, diff: Diff, curListItems: serializedDoctype | undefined, cb: (res: boolean) => void): void {
+ const $remFromSet = diff.diff.$remFromSet as serializedFieldsType;
+ const updatefield = Array.from(Object.keys($remFromSet ?? {}))[0];
+ const remListItems = $remFromSet?.[updatefield]?.fields;
+
+ if (remListItems) {
+ const hint = diff.diff.$remFromSet?.hint;
+ const length = diff.diff.$remFromSet?.length;
+ diff.diff.$set = $remFromSet; // convert rem from set to a query of the current fields, and then a set of the old fields minus the removed ones
+ delete diff.diff.$remFromSet; // can't pass $set to Mongo, or it will do that insetead of $remFromSet
+ const curList = curListItems?.fields?.[updatefield.replace('fields.', '')]?.fields.filter(f => f) ?? [];
if (hint) {
// indexesToRemove stores the indexes that we mark for deletion, which is later used to filter the list (delete the elements)
@@ -227,83 +163,51 @@ export namespace WebSocket {
if (closestIndex !== -1) {
indexesToRemove.push(closestIndex);
} else {
- console.log('Item to delete was not found - index = -1');
+ console.log('Item to delete was not found');
}
}
}
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
+ // if we didn't get a hint, remove all matching items from the list
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)));
}
- // 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;
- // 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's list length is not the same as what we're writing to the server,
+ // then we need to send the server's version back to the client so that they are in synch.
+ // this could happen if another client made a change before the server receives the update from the first client
+ const target = length !== diff.diff.$set[updatefield].fields.length ? socket : socket.broadcast;
+ target === socket && console.log('Warning: SEND BACK: list modified during remove update. Composite list is being returned.');
+ Database.Instance.update(diff.id, diff.diff, () => cb(target.emit(MessageStore.UpdateField.Message, diff)), false);
+ } else cb(false);
}
function UpdateField(socket: Socket, diff: Diff) {
const curUser = socketMap.get(socket);
- if (!curUser) return false;
- const currentUsername = curUser.split(' ')[0];
- userOperations.set(currentUsername, userOperations.get(currentUsername) !== undefined ? userOperations.get(currentUsername)! + 1 : 0);
+ if (curUser) {
+ const currentUsername = curUser.split(' ')[0];
+ userOperations.set(currentUsername, userOperations.get(currentUsername) !== undefined ? userOperations.get(currentUsername)! + 1 : 0);
- if (CurUser !== socketMap.get(socket)) {
- CurUser = socketMap.get(socket);
- console.log('Switch User: ' + CurUser);
- }
- if (pendingOps.has(diff.id)) {
- pendingOps.get(diff.id)!.push({ diff, socket });
- return true;
- }
- pendingOps.set(diff.id, [{ diff, socket }]);
- if (diff.diff.$addToSet) {
- 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?: serializedDoctype) => remFromListField(socket, diff, result)]); // would prefer to have Mongo handle list additions direclty, but for now handle it on our own
+ if (CurUser !== socketMap.get(socket)) {
+ CurUser = socketMap.get(socket);
+ console.log('Switch User: ' + CurUser);
+ }
+ if (pendingOps.has(diff.id)) {
+ pendingOps.get(diff.id)!.push({ diff, socket });
+ return true;
+ }
+ pendingOps.set(diff.id, [{ diff, socket }]);
+ return dispatchNextOp(diff.id);
}
- // 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);
- dispatchNextOp(diff.id);
+ return false;
}
function DeleteField(socket: Socket, id: string) {
- Database.Instance.delete({ _id: id }).then(() => {
- socket.broadcast.emit(MessageStore.DeleteField.Message, id);
- });
+ Database.Instance.delete({ _id: id }).then(() => socket.broadcast.emit(MessageStore.DeleteField.Message, id));
}
function DeleteFields(socket: Socket, ids: string[]) {
- Database.Instance.delete({ _id: { $in: ids } }).then(() => {
- socket.broadcast.emit(MessageStore.DeleteFields.Message, ids);
- });
+ Database.Instance.delete({ _id: { $in: ids } }).then(() => socket.broadcast.emit(MessageStore.DeleteFields.Message, ids));
}
function CreateDocField(newValue: serializedDoctype) {
@@ -343,21 +247,19 @@ export namespace WebSocket {
socket.in(room).emit('message', message);
});
- socket.on('ipaddr', () => {
+ socket.on('ipaddr', () =>
networkInterfaces().keys?.forEach(dev => {
if (dev.family === 'IPv4' && dev.address !== '127.0.0.1') {
socket.emit('ipaddr', dev.address);
}
- });
- });
+ })
+ );
- socket.on('bye', () => {
- console.log('received bye');
- });
+ socket.on('bye', () => console.log('received bye'));
socket.on('disconnect', () => {
const currentUser = socketMap.get(socket);
- if (!(currentUser === undefined)) {
+ if (currentUser !== undefined) {
const currentUsername = currentUser.split(' ')[0];
DashStats.logUserLogout(currentUsername);
delete timeMap[currentUsername];