broadcast synced

This commit is contained in:
Julian Krauser 2025-02-26 16:26:56 +01:00
parent 3da02a89a7
commit 3bc56bf91e
8 changed files with 170 additions and 42 deletions

51
package-lock.json generated
View file

@ -35,7 +35,8 @@
"speakeasy": "^2.0.0",
"sqlite3": "^5.1.7",
"typeorm": "^0.3.20",
"uuid": "^10.0.0"
"uuid": "^10.0.0",
"yjs": "^13.6.23"
},
"devDependencies": {
"@types/cors": "^2.8.14",
@ -2163,6 +2164,16 @@
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="
},
"node_modules/isomorphic.js": {
"version": "0.2.5",
"resolved": "https://registry.npmjs.org/isomorphic.js/-/isomorphic.js-0.2.5.tgz",
"integrity": "sha512-PIeMbHqMt4DnUP3MA/Flc0HElYjMXArsw1qwJZcm9sqR8mq3l8NYizFMty0pWwE/tzIGH3EKK5+jes5mAr85yw==",
"license": "MIT",
"funding": {
"type": "GitHub Sponsors ❤",
"url": "https://github.com/sponsors/dmonad"
}
},
"node_modules/jackspeak": {
"version": "3.4.3",
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
@ -2220,6 +2231,27 @@
"safe-buffer": "^5.0.1"
}
},
"node_modules/lib0": {
"version": "0.2.99",
"resolved": "https://registry.npmjs.org/lib0/-/lib0-0.2.99.tgz",
"integrity": "sha512-vwztYuUf1uf/1zQxfzRfO5yzfNKhTtgOByCruuiQQxWQXnPb8Itaube5ylofcV0oM0aKal9Mv+S1s1Ky0UYP1w==",
"license": "MIT",
"dependencies": {
"isomorphic.js": "^0.2.4"
},
"bin": {
"0ecdsa-generate-keypair": "bin/0ecdsa-generate-keypair.js",
"0gentesthtml": "bin/gentesthtml.js",
"0serve": "bin/0serve.js"
},
"engines": {
"node": ">=16"
},
"funding": {
"type": "GitHub Sponsors ❤",
"url": "https://github.com/sponsors/dmonad"
}
},
"node_modules/locate-path": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
@ -4799,6 +4831,23 @@
"node": ">=6"
}
},
"node_modules/yjs": {
"version": "13.6.23",
"resolved": "https://registry.npmjs.org/yjs/-/yjs-13.6.23.tgz",
"integrity": "sha512-ExtnT5WIOVpkL56bhLeisG/N5c4fmzKn4k0ROVfJa5TY2QHbH7F0Wu2T5ZhR7ErsFWQEFafyrnSI8TPKVF9Few==",
"license": "MIT",
"dependencies": {
"lib0": "^0.2.99"
},
"engines": {
"node": ">=16.0.0",
"npm": ">=8.0.0"
},
"funding": {
"type": "GitHub Sponsors ❤",
"url": "https://github.com/sponsors/dmonad"
}
},
"node_modules/yn": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",

View file

@ -50,7 +50,8 @@
"speakeasy": "^2.0.0",
"sqlite3": "^5.1.7",
"typeorm": "^0.3.20",
"uuid": "^10.0.0"
"uuid": "^10.0.0",
"yjs": "^13.6.23"
},
"devDependencies": {
"@types/cors": "^2.8.14",

View file

@ -1,6 +1,8 @@
import * as Y from "yjs";
export interface missionStoreModel {
missionId: string;
doc: any;
doc: Y.Doc;
}
/**

View file

@ -1,6 +1,7 @@
import { Server, Socket } from "socket.io";
import { SocketMap } from "../../storage/socketMap";
import CustomRequestException from "../../exceptions/customRequestException";
import { MissionMap } from "../../storage/missionMap";
export default (io: Server, socket: Socket) => {
socket.on("ping", (callback: Function) => {
@ -28,6 +29,13 @@ export default (io: Server, socket: Socket) => {
socket.on("disconnecting", () => {
console.log("socket disconnection: ", socket.id);
const socketRooms = Array.from(socket.rooms).filter((room) => room !== socket.id && room !== "home");
socket.leave(socketRooms[0]);
const clients = io.sockets.adapter.rooms.get(socketRooms[0]);
if (!clients || clients.size == 0) {
MissionMap.delete(socketRooms[0]);
}
SocketMap.delete(socket.id);
});

View file

@ -0,0 +1,101 @@
import { Server, Socket } from "socket.io";
import { handleEvent } from "../handleEvent";
import { MissionMap } from "../../storage/missionMap";
import * as Y from "yjs";
export default (io: Server, socket: Socket) => {
socket.on(
"mission:join",
handleEvent(
{ type: "read", section: "operation", module: "mission" },
async (data: any) => {
socket.rooms.forEach((room) => {
if (room !== socket.id && room != "home") {
socket.leave(room);
}
});
try {
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');
// get mission data
MissionMap.write(data, {
missionId: data,
doc,
});
const mission = MissionMap.read(data);
socket.join(data);
socket.emit("status-mission:join", { status: "success" });
return {
type: "package-mission",
answer: Y.encodeStateAsUpdate(mission.doc),
};
} catch (error) {
return { type: "status-join:join", answer: { status: "failed", msg: error.message } };
}
},
socket
)
);
socket.on(
"mission:sync",
handleEvent(
{ type: "read", 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));
socket.emit("status-mission:sync", { status: "success" });
return {
type: "package-sync",
answer: data,
room: socketRooms[0],
};
} catch (error) {
return { type: "status-mission:sync", answer: { status: "failed", msg: error.message } };
}
},
socket
)
);
socket.on(
"mission:leave",
handleEvent(
{ type: "read", section: "operation", module: "mission" },
async (data: any) => {
const socketRooms = Array.from(socket.rooms).filter((room) => room !== socket.id && room !== "home");
socket.leave(socketRooms[0]);
const clients = io.sockets.adapter.rooms.get(socketRooms[0]);
if (!clients || clients.size == 0) {
MissionMap.delete(socketRooms[0]);
}
return {
type: "deleted",
answer: "leave instance",
};
},
socket
)
);
};

View file

@ -1,36 +0,0 @@
import { Server, Socket } from "socket.io";
import { SocketMap } from "../../storage/socketMap";
import CustomRequestException from "../../exceptions/customRequestException";
import { handleEvent } from "../handleEvent";
export default (io: Server, socket: Socket) => {
socket.on(
"join:mission",
handleEvent(
{ type: "read", section: "operation", module: "mission" },
async (data: any) => {
socket.rooms.forEach((room) => {
if (room !== socket.id && room != "home") {
socket.leave(room);
}
});
try {
socket.join(data);
socket.emit("status-mission:room", { status: "success" });
// get mission data
// create yjs sync doc
// provide yjs sync doc to client
return {
type: "package-mission",
answer: "mission-data by yjs",
};
} catch (error) {
return { type: "status-join:room", answer: { status: "failed", msg: error.message } };
}
},
socket
)
);
};

View file

@ -38,7 +38,10 @@ export let handleEvent = (
throw new ForbiddenRequestException(`missing admin permission`);
}
} else {
if (!PermissionHelper.can(socketData.permissions, permissions.type, permissions.section, permissions.module)) {
if (
!socketData.isOwner &&
!PermissionHelper.can(socketData.permissions, permissions.type, permissions.section, permissions.module)
) {
throw new ForbiddenRequestException(`missing required permission`);
}
}

View file

@ -4,7 +4,7 @@ import { Server } from "socket.io";
import authenticateSocket from "./middleware/authenticateSocket";
import base from "./endpoints/base";
import perRequestCheck from "./middleware/perRequestCheck";
import roomManagement from "./endpoints/roomManagement";
import missionManagement from "./endpoints/missionManagement";
export default abstract class SocketServer {
private static io: Server;
@ -28,7 +28,7 @@ export default abstract class SocketServer {
socket.use((packet, next) => perRequestCheck(socket, packet, next));
base(this.io, socket);
roomManagement(this.io, socket);
missionManagement(this.io, socket);
});
}