first commit

This commit is contained in:
2026-07-16 09:35:32 +02:00
commit dd0746e2e6
179 changed files with 25679 additions and 0 deletions
+97
View File
@@ -0,0 +1,97 @@
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<ClientToServerEvents, ServerToClientEvents>;
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<ClientToServerEvents, ServerToClientEvents>(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<AuthUser>(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 };