import fs from 'node:fs'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const CRASH_DUMP_DIR = process.env.CRASH_DUMP_DIR || path.resolve(__dirname, '../../../logs/crash-dumps'); function telemetryErrorStatus(error) { if (error.message?.includes('required')) return 400; return 500; } /** * Registers public telemetry endpoints (no session/cookie auth required). * @param {import('express').Application} app * @param {string} [basePath=''] */ export function register(app, basePath = '') { /** * @openapi * /public/telemetry/crash: * post: * tags: [Telemetry] * summary: Receipt crash dump * description: Public endpoint to receive crash dumps. Saves the content parameter to a file named with the epoch millisecond. * security: [] * requestBody: * required: true * content: * application/json: * schema: * type: object * required: * - content * properties: * content: * description: Crash dump content to store * oneOf: * - type: string * - type: object * responses: * 200: * description: Crash dump saved * content: * application/json: * schema: * type: object * properties: * ok: * type: boolean * example: true * filename: * type: string * example: "1728395535123" * 400: * description: Missing content parameter * content: * application/json: * schema: * type: object * properties: * error: * type: string * example: content is required * 500: * description: Storage failure * content: * application/json: * schema: * type: object * properties: * error: * type: string */ const routePaths = [`${basePath}/public/telemetry/crash`]; if (basePath && !routePaths.includes('/public/telemetry/crash')) { routePaths.push('/public/telemetry/crash'); } app.post(routePaths, async (req, res) => { try { const { content } = req.body || {}; if (content === undefined || content === null) { return res.status(400).json({ error: 'content is required' }); } await fs.promises.mkdir(CRASH_DUMP_DIR, { recursive: true }); let ts = Date.now(); let filePath = path.join(CRASH_DUMP_DIR, String(ts)); while (fs.existsSync(filePath)) { ts++; filePath = path.join(CRASH_DUMP_DIR, String(ts)); } const data = typeof content === 'string' ? content : JSON.stringify(content, null, 2); await fs.promises.writeFile(filePath, data, 'utf8'); res.status(200).json({ ok: true, filename: String(ts) }); } catch (error) { const status = telemetryErrorStatus(error); if (status === 500) { console.error('Crash dump error:', error); } res.status(status).json({ error: error.message || 'Internal server error' }); } }); }