import { Server as IOServer } from 'socket.io'; import type { FastifyInstance } from 'fastify'; import { SOCKET_EVENTS, departmentRoom, DEPARTMENTS, type ServerToClientEvents, type ClientToServerEvents, type AuthUser, type Department, type Officer, type OfficerPositionUpdate, type OfficerDutyUpdate, type DispatchCall, type Warrant, type Coords, } from '@d4rk-tablet/shared'; import { env } from '../config/env'; import { officerSnapshot } from './officers'; type AppIO = IOServer; let io: AppIO | null = null; /** Departments, in denen der User Mitglied ist (für Room-Zuordnung). */ function userDepartments(user: AuthUser): Department[] { return DEPARTMENTS.filter((d) => user.roles.includes(d)); } export function initSocket(app: FastifyInstance): AppIO { io = new IOServer(app.server, { cors: { origin: env.CORS_ORIGINS, credentials: true }, transports: ['websocket'], }); // JWT-Auth beim Handshake io.use((socket, next) => { try { const token = (socket.handshake.auth as { token?: string })?.token; if (!token) return next(new Error('Kein Token')); const user = app.jwt.verify(token); socket.data.user = user; next(); } catch { next(new Error('Ungültiges Token')); } }); io.on('connection', (socket) => { const user = socket.data.user as AuthUser; const depts = userDepartments(user); for (const d of depts) void socket.join(departmentRoom(d)); // Initialer Snapshot der Officer der eigenen Departments const snapshot: Officer[] = depts.flatMap((d) => officerSnapshot(d)); socket.emit(SOCKET_EVENTS.OFFICER_SNAPSHOT, snapshot); // Client → Server: Waypoint an einen Officer senden (→ Bridge in M7) socket.on(SOCKET_EVENTS.OFFICER_SET_WAYPOINT, (payload) => { app.log.info({ payload }, 'setWaypoint (Bridge-Weiterleitung folgt in M7)'); }); }); app.addHook('onClose', async () => { await io?.close(); }); return io; } function getIO(): AppIO { if (!io) throw new Error('Socket.io nicht initialisiert'); return io; } // ── Broadcast-Helfer (von Services genutzt) ── export function broadcastDispatchCreated(call: DispatchCall): void { getIO().to(departmentRoom(call.department)).emit(SOCKET_EVENTS.DISPATCH_CREATED, call); } export function broadcastDispatchUpdated(call: DispatchCall): void { getIO().to(departmentRoom(call.department)).emit(SOCKET_EVENTS.DISPATCH_UPDATED, call); } export function broadcastOfficerMoved(dept: Department, update: OfficerPositionUpdate): void { getIO().to(departmentRoom(dept)).emit(SOCKET_EVENTS.OFFICER_MOVED, update); } export function broadcastOfficerDuty(dept: Department, update: OfficerDutyUpdate): void { getIO().to(departmentRoom(dept)).emit(SOCKET_EVENTS.OFFICER_DUTY, update); } export function broadcastWarrantUpdated(dept: Department, warrant: Warrant): void { getIO().to(departmentRoom(dept)).emit(SOCKET_EVENTS.WARRANT_UPDATED, warrant); } export type { Coords };