import { Server, Socket } from "socket.io"; import { handleEvent } from "../handleEvent"; import { MissionMap, missionStoreModel } from "../../storage/missionMap"; import * as Y from "yjs"; import * as AwarenessProtocol from "y-protocols/dist/awareness.cjs"; import MissionDocHelper from "../../helpers/missionDocHelper"; export default (io: Server, socket: Socket) => { socket.on( "mission:join", handleEvent( { type: "read", section: "operation", module: "mission" }, async (missionId: string, clientLastUpdate: missionStoreModel) => { socket.rooms.forEach((room) => { if (room !== socket.id && room != "home") { socket.leave(room); } }); try { socket.join(missionId); if (!MissionMap.exists(missionId)) { await MissionDocHelper.populateDoc(missionId); } const mission = MissionMap.read(missionId); if (clientLastUpdate && clientLastUpdate.timestamp) { if (mission.timestamp > clientLastUpdate.timestamp) { socket.emit("package-sync", { update: Y.encodeStateAsUpdate(mission.doc), timestamp: mission.timestamp, source: "server", }); } else { socket.emit("sync-get-missing-updates", { stateVector: Y.encodeStateVector(mission.doc), timestamp: mission.timestamp, source: "server", }); } } else { socket.emit("package-sync", { update: Y.encodeStateAsUpdate(mission.doc), timestamp: mission.timestamp, source: "server", }); } socket.emit("package-sync-awareness", { update: AwarenessProtocol.encodeAwarenessUpdate( mission.awareness, Array.from(mission.awareness.getStates().keys()) ), source: "server", }); return { type: "status-mission:join", answer: { status: "success" }, }; } catch (error) { return { type: "status-join:join", answer: { status: "failed", msg: error.message } }; } }, socket ) ); socket.on( "mission:sync-client-updates", handleEvent( { type: "create", section: "operation", module: "mission" }, async (data: { update: Array; 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: socket.id, }, 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]); const missingUpdates = Y.encodeStateAsUpdate(mission.doc, data.stateVector); return { type: "package-sync", answer: { update: missingUpdates, timestamp: mission.timestamp, source: "server", }, }; } catch (error) { return { type: "status-mission:sync-get-missing-updates", answer: { status: "failed", msg: error.message } }; } }, socket ) ); socket.on( "mission:sync-client-awareness", handleEvent( { type: "create", section: "operation", module: "mission" }, async (data: { update: Array; timestamp: number }) => { const socketRooms = Array.from(socket.rooms).filter((room) => room !== socket.id && room !== "home"); try { MissionMap.updateAwareness(socketRooms[0], new Uint8Array(data.update), socket.id); return { type: "package-sync-awareness", answer: { update: data.update, source: socket.id, }, room: socketRooms[0], }; } catch (error) { const mission = MissionMap.read(socketRooms[0]); socket.emit("package-sync-awareness", { update: Y.encodeStateAsUpdate(mission.doc), source: "server", error: true, }); return { type: "status-mission:sync-client-updates", 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 ) ); };