This commit is contained in:
2026-09-17 15:09:27 -04:00
parent 85c238fceb
commit bbb1639184
+48 -15
View File
@@ -1,27 +1,60 @@
import { json } from '@sveltejs/kit'; import { json } from '@sveltejs/kit';
import { getServer } from '$lib/server/fleet/servers.js'; import { getServer, listServers } from '$lib/server/fleet/servers.js';
import { saveTelemetry } from '$lib/server/fleet/telemetry.js'; import { saveTelemetry } from '$lib/server/fleet/telemetry.js';
import { emit } from '$lib/server/pipeline/bus.js'; import { emit } from '$lib/server/pipeline/bus.js';
export async function POST({ request }: { request: Request }) { export async function POST({ request }: { request: Request }) {
console.log('[telemetry] received POST /api/telemetry'); console.log('[telemetry] received POST /api/telemetry');
if (request.headers.get('content-type')?.split(';')[0].trim().toLowerCase() !== 'application/json') { // Encode payloads as JSON so script-supplied newlines cannot forge log lines.
console.log('[telemetry] rejected 415: Content-Type must be application/json'); // Bound the preview to keep malformed requests from flooding container logs.
return json({ ok: false, error: 'Content-Type must be application/json' }, { status: 415 }); const raw = await request.text();
const context = {
contentType: request.headers.get('content-type'),
payloadPreview: raw.slice(0, 4096),
payloadTruncated: raw.length > 4096
};
function reject(status: number, error: string, details: Record<string, unknown> = {}) {
console.log(`[telemetry] rejected ${status}: ${JSON.stringify({ error, ...context, ...details })}`);
return json({ ok: false, error, details }, { status });
} }
const body = await request.json().catch(() => null); if (request.headers.get('content-type')?.split(';')[0].trim().toLowerCase() !== 'application/json') {
if (!body || typeof body !== 'object' || Array.isArray(body) return reject(415, 'Content-Type must be application/json', { receivedContentType: context.contentType });
|| body.event !== 'round_change' }
|| typeof body.server !== 'string' || !body.server.length || body.server.length > 255 let body;
|| !Number.isSafeInteger(body.round) || body.round < 0 try {
|| !Number.isSafeInteger(body.players) || body.players < 0) { body = JSON.parse(raw);
console.log('[telemetry] rejected 400: invalid round_change payload or malformed JSON'); } catch (error) {
return json({ ok: false, error: 'Expected round_change, a server name, and nonnegative integer round and players' }, { status: 400 }); return reject(400, 'Malformed JSON', { parseError: (error as Error).message });
}
if (!body || typeof body !== 'object' || Array.isArray(body)) {
return reject(400, 'Payload must be a JSON object');
}
const issues: string[] = [];
if (body.event !== 'round_change') issues.push('event must equal "round_change"');
if (typeof body.server !== 'string' || !body.server.length || body.server.length > 255) {
issues.push('server must be a registered server name (1–255 characters)');
}
for (const field of ['round', 'players']) {
if (!Number.isSafeInteger(body[field]) || body[field] < 0) {
issues.push(`${field} must be a nonnegative safe integer (JSON number, not a string)`);
}
}
if (issues.length) {
return reject(400, 'Invalid telemetry payload', { issues });
} }
const server = getServer(body.server); const server = getServer(body.server);
if (!server || server.destroyed_at) { if (!server) {
console.log(`[telemetry] rejected 404: unknown or destroyed server ${JSON.stringify(body.server)}`); const registeredServers = listServers().filter((entry) => !entry.destroyed_at).map((entry) => entry.name);
return json({ ok: false, error: 'Unknown or destroyed server' }, { status: 404 }); return reject(404, 'Server is not registered', {
receivedServer: body.server,
hint: 'Send the exact Electric Cherry server name in the server field; matching is case-sensitive.',
registeredServers
});
}
if (server.destroyed_at) {
return reject(404, 'Server has been destroyed', {
receivedServer: body.server, destroyedAt: server.destroyed_at
});
} }
const telemetry = { const telemetry = {
server: body.server as string, round: body.round as number, server: body.server as string, round: body.round as number,