import type { Plugin } from 'vite'; import { WebSocketServer } from 'ws'; import { handle, synthTelemetryHistory } from './handlers'; const COOKIE = 'mocksid'; const WS_PATH = /^\/api\/ws\/satellite\/([0-9a-f-]{36})\/telemetry\/$/i; function readCookie(header: string | undefined, name: string): string | null { if (!header) return null; for (const part of header.split(';')) { const [k, ...v] = part.trim().split('='); if (k === name) return decodeURIComponent(v.join('=')); } return null; } /** Dev-only middleware that answers /api/* from the in-memory mock backend. */ export function mockApi(): Plugin { return { name: 'mock-api', configureServer(server) { // Live telemetry: a real WebSocket so the tracking flow is exercised // end-to-end (the read-only satellite consumer in the real backend). const wss = new WebSocketServer({ noServer: true }); server.httpServer?.on('upgrade', (req, socket, head) => { const url = req.url ?? ''; const match = url.split('?')[0].match(WS_PATH); if (!match) return; // let Vite's own HMR socket handle it wss.handleUpgrade(req, socket as never, head, (ws) => { // Continue the satellite's synthetic track from where history ended. const history = synthTelemetryHistory(match[1]); let next = history.length; const first = history[0] as { timestamp: number; lat: number; lon: number }; const timer = setInterval(() => { ws.send( JSON.stringify({ id: `${match[1]}-live-${next}`, timestamp: first.timestamp + (next - history.length + 1) * 60, lat: first.lat + (next - history.length + 1) * 0.004, lon: first.lon + (next - history.length + 1) * 0.01, alt: 18000 + (next - history.length + 1) * 900, payload: { live: true }, raw_data: {} }) ); next += 1; }, 1000); ws.on('close', () => clearInterval(timer)); }); }); server.middlewares.use((req, res, next) => { if (!req.url?.startsWith('/api/')) return next(); let raw = ''; req.on('data', (c) => (raw += c)); req.on('end', () => { const body = raw ? JSON.parse(raw) : {}; const sessionUser = readCookie(req.headers.cookie, COOKIE); const [pathname, search = ''] = req.url!.split('?'); const result = handle( req.method ?? 'GET', pathname, body, sessionUser, new URLSearchParams(search) ); res.statusCode = result.status; res.setHeader('content-type', 'application/json'); res.setHeader('x-csrftoken', 'mock-csrf'); if (result.session !== undefined) { res.setHeader( 'set-cookie', result.session === null ? `${COOKIE}=; Path=/; Max-Age=0; SameSite=Lax` : `${COOKIE}=${encodeURIComponent(result.session)}; Path=/; SameSite=Lax` ); } res.end(JSON.stringify(result.body)); }); }); } }; }