transmit awareness

This commit is contained in:
Julian Krauser 2025-03-04 12:01:48 +01:00
parent 5fbc189884
commit 8bcc16f790
7 changed files with 143 additions and 14 deletions

15
package-lock.json generated
View file

@ -30,6 +30,7 @@
"nodemailer": "^6.10.0",
"pg": "^8.13.1",
"qrcode": "^1.5.4",
"randomcolor": "^0.6.2",
"reflect-metadata": "^0.2.2",
"rss-parser": "^3.13.0",
"socket.io": "^4.8.1",
@ -53,6 +54,7 @@
"@types/node-schedule": "^2.1.6",
"@types/nodemailer": "^6.4.17",
"@types/qrcode": "~1.5.5",
"@types/randomcolor": "^0.5.9",
"@types/speakeasy": "^2.0.10",
"@types/uuid": "^9.0.2",
"ts-node": "10.7.0",
@ -480,6 +482,13 @@
"integrity": "sha512-uXHQKES6DQKKCLh441Xv/dwxOq1TVS3JPUMlEqoEglvlhR6Mxnlew/Xq/LRVHpLyk7iK3zODe1qYHIMltO7XGg==",
"dev": true
},
"node_modules/@types/randomcolor": {
"version": "0.5.9",
"resolved": "https://registry.npmjs.org/@types/randomcolor/-/randomcolor-0.5.9.tgz",
"integrity": "sha512-k58cfpkK15AKn1m+oRd9nh5BnuiowhbyvBBdAzcddtARMr3xRzP0VlFaAKovSG6N6Knx08EicjPlOMzDejerrQ==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/range-parser": {
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz",
@ -3471,6 +3480,12 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/randomcolor": {
"version": "0.6.2",
"resolved": "https://registry.npmjs.org/randomcolor/-/randomcolor-0.6.2.tgz",
"integrity": "sha512-Mn6TbyYpFgwFuQ8KJKqf3bqqY9O1y37/0jgSK/61PUxV4QfIMv0+K2ioq8DfOjkBslcjwSzRfIDEXfzA9aCx7A==",
"license": "CC0"
},
"node_modules/range-parser": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",

View file

@ -45,6 +45,7 @@
"nodemailer": "^6.10.0",
"pg": "^8.13.1",
"qrcode": "^1.5.4",
"randomcolor": "^0.6.2",
"reflect-metadata": "^0.2.2",
"rss-parser": "^3.13.0",
"socket.io": "^4.8.1",
@ -68,6 +69,7 @@
"@types/node-schedule": "^2.1.6",
"@types/nodemailer": "^6.4.17",
"@types/qrcode": "~1.5.5",
"@types/randomcolor": "^0.5.9",
"@types/speakeasy": "^2.0.10",
"@types/uuid": "^9.0.2",
"ts-node": "10.7.0",

View file

@ -19,9 +19,13 @@ export default abstract class MissionDocHelper {
// const ytext = ydoc.getText('meinText');
// ytext.insert(0, 'Hier ist ein initialer Text');
const awareness = 0; // TODO Awareness
MissionMap.write(missionId, { missionId, doc, awareness, timestamp: 0 });
MissionMap.write(missionId, {
missionId,
doc,
editors: new Map(),
editorStates: new Map(),
timestamp: 0,
});
console.log(`created local doc ${missionId}`);
}
@ -40,6 +44,16 @@ export default abstract class MissionDocHelper {
return mission.timestamp;
}
public static getAwarenessState(missionId: string) {
const mission = MissionMap.read(missionId);
return Array.from(mission.editorStates.entries());
}
public static getAwarenessEditors(missionId: string) {
const mission = MissionMap.read(missionId);
return Array.from(mission.editors.entries());
}
public static async saveDoc(missionId: string, update: Uint8Array) {
MissionMap.updateState(missionId, update);
const mission = MissionMap.read(missionId);

View file

@ -4,10 +4,24 @@ import ms from "ms";
export interface missionStoreModel {
missionId: string;
doc: Y.Doc;
awareness: any;
editors: Map<string, Editor>;
editorStates: Map<string, EditorState>;
timestamp: number;
}
export interface Editor {
username: string;
firstname: string;
lastname: string;
color: string;
}
export interface EditorState {
field: string;
cursor: any;
range: any;
}
/**
* @description store credentials to socket to prevent auth data change while connected
*/
@ -32,12 +46,30 @@ export abstract class MissionMap {
this.write(identifier, mission, true);
}
public static updateAwareness(identifier: string, data: any): void {
public static updateAwareness(
identifier: string,
data: { action: "update" | "remove"; socketId: string } & (
| { store: "editors"; update: Editor }
| { store: "state"; update: EditorState }
)
): void {
this.monitorAccess(identifier);
let mission = this.read(identifier);
// TODO save awareness
if (data.store == "editors") {
if (data.action == "update") {
mission.editors.set(data.socketId, data.update);
} else {
mission.editors.delete(data.socketId);
}
} else {
if (data.action == "update") {
mission.editorStates.set(data.socketId, data.update);
} else {
mission.editorStates.delete(data.socketId);
}
}
this.write(identifier, mission, true);
}

View file

@ -4,6 +4,8 @@ export interface socketStoreModel {
socketId: string;
userId: string;
username: string;
firstname: string;
lastname: string;
isOwner: string;
permissions: PermissionObject;
isWebApiRequest: boolean;

View file

@ -1,8 +1,10 @@
import { Server, Socket } from "socket.io";
import { handleEvent } from "../handleEvent";
import { MissionMap, missionStoreModel } from "../../storage/missionMap";
import { EditorState, MissionMap, missionStoreModel } from "../../storage/missionMap";
import * as Y from "yjs";
import MissionDocHelper from "../../helpers/missionDocHelper";
import randomColor from "randomcolor";
import { SocketMap } from "../../storage/socketMap";
export default (io: Server, socket: Socket) => {
socket.on(
@ -45,9 +47,36 @@ export default (io: Server, socket: Socket) => {
});
}
socket.emit("package-sync-awareness", {
update: [], // do awareness self
source: "server",
console.log(MissionDocHelper.getAwarenessEditors(missionId));
socket.emit("package-init-awareness", {
update: MissionDocHelper.getAwarenessEditors(missionId),
type: "editors",
});
socket.emit("package-init-awareness", {
update: MissionDocHelper.getAwarenessState(missionId),
type: "state",
});
let newEditor = {
socketId: socket.id,
data: {
username: SocketMap.read(socket.id).username,
firstname: SocketMap.read(socket.id).firstname,
lastname: SocketMap.read(socket.id).lastname,
color: randomColor({ format: "hex" }),
},
};
MissionMap.updateAwareness(missionId, {
action: "update",
socketId: newEditor.socketId,
store: "editors",
update: newEditor.data,
});
socket.to(missionId).emit("package-sync-awareness", {
update: newEditor.data,
socketId: socket.id,
action: "update",
type: "editors",
});
return {
@ -124,16 +153,23 @@ export default (io: Server, socket: Socket) => {
"mission:sync-client-awareness",
handleEvent(
{ type: "create", section: "operation", module: "mission" },
async (data: { update: Array<number>; timestamp: number }) => {
async (data: EditorState) => {
const socketRooms = Array.from(socket.rooms).filter((room) => room !== socket.id && room !== "home");
try {
MissionMap.updateAwareness(socketRooms[0], new Uint8Array(data.update));
MissionMap.updateAwareness(socketRooms[0], {
action: "update",
store: "state",
update: data,
socketId: socket.id,
});
return {
type: "package-sync-awareness",
answer: {
update: data.update,
source: socket.id,
type: "state",
action: "update",
update: data,
socketId: socket.id,
},
room: socketRooms[0],
};
@ -163,6 +199,32 @@ export default (io: Server, socket: Socket) => {
MissionMap.delete(socketRooms[0]);
}
MissionMap.updateAwareness(socketRooms[0], {
action: "remove",
store: "editors",
update: undefined,
socketId: socket.id,
});
io.to(socketRooms[0]).emit("package-sync-awareness", {
type: "editors",
update: undefined,
action: "remove",
socketId: socket.id,
});
MissionMap.updateAwareness(socketRooms[0], {
action: "remove",
store: "state",
update: undefined,
socketId: socket.id,
});
io.to(socketRooms[0]).emit("package-sync-awareness", {
type: "state",
update: undefined,
action: "remove",
socketId: socket.id,
});
return {
type: "deleted",
answer: "leave instance",

View file

@ -39,6 +39,8 @@ export default async function authenticateSocket(socket: Socket, next: Function)
socketId: socket.id,
userId: decoded.userId,
username: decoded.username,
firstname: decoded.firstname,
lastname: decoded.lastname,
isOwner: decoded.isOwner,
permissions: decoded.permissions,
isWebApiRequest: decoded?.sub == "webapi_access_token",