98 lines
3.0 KiB
TypeScript
98 lines
3.0 KiB
TypeScript
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 };
|