mission management by timestamp

This commit is contained in:
Julian Krauser 2025-02-28 14:02:21 +01:00
parent 1646d0d6ca
commit 8aaeaa7e0f
5 changed files with 136 additions and 34 deletions

21
package-lock.json generated
View file

@ -37,6 +37,7 @@
"sqlite3": "^5.1.7",
"typeorm": "^0.3.20",
"uuid": "^10.0.0",
"y-protocols": "^1.0.6",
"yjs": "^13.6.23"
},
"devDependencies": {
@ -4831,6 +4832,26 @@
"node": ">=0.4"
}
},
"node_modules/y-protocols": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/y-protocols/-/y-protocols-1.0.6.tgz",
"integrity": "sha512-vHRF2L6iT3rwj1jub/K5tYcTT/mEYDUppgNPXwp8fmLpui9f7Yeq3OEtTLVF012j39QnV+KEQpNqoN7CWU7Y9Q==",
"license": "MIT",
"dependencies": {
"lib0": "^0.2.85"
},
"engines": {
"node": ">=16.0.0",
"npm": ">=8.0.0"
},
"funding": {
"type": "GitHub Sponsors ❤",
"url": "https://github.com/sponsors/dmonad"
},
"peerDependencies": {
"yjs": "^13.0.0"
}
},
"node_modules/y18n": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz",

View file

@ -52,6 +52,7 @@
"sqlite3": "^5.1.7",
"typeorm": "^0.3.20",
"uuid": "^10.0.0",
"y-protocols": "^1.0.6",
"yjs": "^13.6.23"
},
"devDependencies": {

View file

@ -0,0 +1,29 @@
import * as Y from "yjs";
import { MissionMap } from "../storage/missionMap";
export default abstract class MissionDocHelper {
public static async populateDoc(missionId: string) {
// get Data from database
const doc = new Y.Doc();
doc.getMap("form");
doc.getText("editor");
// const ymap = ydoc.getMap('myMap');
// ymap.set('titel', 'Mein Dokument');
// ymap.set('inhalt', 'Hier ist der initiale Inhalt');
// ymap.set('erstelltAm', new Date().toISOString());
// const yarray = ydoc.getArray('meineArray');
// yarray.push(['Element 1', 'Element 2', 'Element 3']);
// const ytext = ydoc.getText('meinText');
// ytext.insert(0, 'Hier ist ein initialer Text');
MissionMap.write(missionId, { missionId, doc, timestamp: 0 }, true);
}
public static async saveDoc(missionId: string) {
// store Data to database
const mission = MissionMap.read(missionId);
}
}

View file

@ -3,6 +3,7 @@ import * as Y from "yjs";
export interface missionStoreModel {
missionId: string;
doc: Y.Doc;
timestamp: number;
}
/**
@ -15,6 +16,18 @@ export abstract class MissionMap {
if (!this.exists(identifier) || overwrite) this.store.set(identifier, data);
}
public static updateState(identifier: string, data: Uint8Array): void {
let mission = this.read(identifier);
Y.applyUpdate(mission.doc, data);
this.write(identifier, mission, true);
}
public static updateTimestamp(identifier: string, data: number): void {
let mission = this.read(identifier);
mission.timestamp = data;
this.write(identifier, mission, true);
}
public static read(identifier: string): missionStoreModel {
return this.store.get(identifier);
}

View file

@ -1,14 +1,15 @@
import { Server, Socket } from "socket.io";
import { handleEvent } from "../handleEvent";
import { MissionMap } from "../../storage/missionMap";
import { MissionMap, missionStoreModel } from "../../storage/missionMap";
import * as Y from "yjs";
import MissionDocHelper from "../../helpers/missionDocHelper";
export default (io: Server, socket: Socket) => {
socket.on(
"mission:join",
handleEvent(
{ type: "read", section: "operation", module: "mission" },
async (data: string, initialized: boolean) => {
async (missionId: string, clientLastUpdate: missionStoreModel) => {
socket.rooms.forEach((room) => {
if (room !== socket.id && room != "home") {
socket.leave(room);
@ -16,33 +17,35 @@ export default (io: Server, socket: Socket) => {
});
try {
const doc = new Y.Doc();
doc.getMap("form");
doc.getText("editor");
socket.join(missionId);
// const ymap = ydoc.getMap('myMap');
// ymap.set('titel', 'Mein Dokument');
// ymap.set('inhalt', 'Hier ist der initiale Inhalt');
// ymap.set('erstelltAm', new Date().toISOString());
await MissionDocHelper.populateDoc(missionId);
const mission = MissionMap.read(missionId);
// const yarray = ydoc.getArray('meineArray');
// yarray.push(['Element 1', 'Element 2', 'Element 3']);
// const ytext = ydoc.getText('meinText');
// ytext.insert(0, 'Hier ist ein initialer Text');
// get mission data
MissionMap.write(data, {
missionId: data,
doc,
});
const mission = MissionMap.read(data);
socket.join(data);
if (!initialized) {
socket.emit("package-mission", Y.encodeStateAsUpdate(mission.doc));
if (clientLastUpdate && clientLastUpdate.timestamp) {
// Prüfe, ob der Client aktuell ist oder nicht
if (mission.timestamp > clientLastUpdate.timestamp) {
// Server hat neuere Daten, sende vollständigen State
socket.emit("package-sync", {
update: Y.encodeStateAsUpdate(mission.doc),
timestamp: mission.timestamp,
source: "server",
});
} else {
// Client hat aktuellere oder gleich aktuelle Daten
socket.emit("sync-get-missing-updates", {
stateVector: Y.encodeStateVector(mission.doc),
timestamp: mission.timestamp,
source: "server",
});
}
} else {
// Client hat keine Zeitstempel-Info, sende vollständigen State
socket.emit("package-sync", {
update: Y.encodeStateAsUpdate(mission.doc),
timestamp: mission.timestamp,
source: "server",
});
}
return {
@ -58,23 +61,58 @@ export default (io: Server, socket: Socket) => {
);
socket.on(
"mission:sync",
"mission:sync-client-updates",
handleEvent(
{ type: "read", section: "operation", module: "mission" },
{ type: "create", section: "operation", module: "mission" },
async (data: { update: Array<number>; timestamp: number }) => {
const socketRooms = Array.from(socket.rooms).filter((room) => room !== socket.id && room !== "home");
try {
MissionMap.updateState(socketRooms[0], new Uint8Array(data.update));
return {
type: "package-sync",
answer: {
update: data.update,
timestamp: data.timestamp,
source: "client",
},
room: socketRooms[0],
};
} catch (error) {
const mission = MissionMap.read(socketRooms[0]);
socket.emit("package-sync", {
update: Y.encodeStateAsUpdate(mission.doc),
timestamp: mission.timestamp,
source: "server",
error: true,
});
return { type: "status-mission:sync-client-updates", answer: { status: "failed", msg: error.message } };
}
},
socket
)
);
socket.on(
"mission:sync-get-missing-updates",
handleEvent(
{ type: "create", section: "operation", module: "mission" },
async (data: any) => {
const socketRooms = Array.from(socket.rooms).filter((room) => room !== socket.id && room !== "home");
try {
const mission = MissionMap.read(socketRooms[0]);
Y.applyUpdate(mission.doc, new Uint8Array(data));
const missingUpdates = Y.encodeStateAsUpdate(mission.doc, data.stateVector);
socket.emit("status-mission:sync", { status: "success" });
return {
type: "package-sync",
answer: data,
room: socketRooms[0],
answer: {
update: missingUpdates,
timestamp: mission.timestamp,
source: "server",
},
};
} catch (error) {
return { type: "status-mission:sync", answer: { status: "failed", msg: error.message } };
return { type: "status-mission:sync-get-missing-updates", answer: { status: "failed", msg: error.message } };
}
},
socket