This commit is contained in:
gili8420 2026-08-14 00:01:49 +09:00
commit b43955e0d9
162 changed files with 15425 additions and 0 deletions

83
mocks/plugin.ts Normal file
View file

@ -0,0 +1,83 @@
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));
});
});
}
};
}