(request, env, ctx)
| 269 | |
| 270 | export default { |
| 271 | async fetch(request, env, ctx): Promise<Response> { |
| 272 | try { |
| 273 | const url = new URL(request.url); |
| 274 | |
| 275 | if (request.method === 'GET' && url.pathname === '/') { |
| 276 | return new Response(infoText(retentionDays(env)), { |
| 277 | headers: { 'content-type': 'text/plain; charset=utf-8' }, |
| 278 | }); |
| 279 | } |
| 280 | // Backfill/repair for the nightly rollup. 404s unless ADMIN_TOKEN is configured. |
| 281 | if (url.pathname === '/admin/rollup') { |
| 282 | return await handleAdminRollup(request, env, url); |
| 283 | } |
| 284 | if (url.pathname !== '/v1/events') { |
| 285 | return new Response('not found\n', { status: 404 }); |
| 286 | } |
| 287 | if (request.method !== 'POST') { |
| 288 | return new Response('method not allowed\n', { status: 405, headers: { allow: 'POST' } }); |
| 289 | } |
| 290 | |
| 291 | const contentLength = Number(request.headers.get('content-length')); |
| 292 | if (!Number.isFinite(contentLength) || contentLength <= 0) { |
| 293 | return new Response('length required\n', { status: 411 }); |
| 294 | } |
| 295 | if (contentLength > MAX_BODY_BYTES) { |
| 296 | return new Response('payload too large\n', { status: 413 }); |
| 297 | } |
| 298 | |
| 299 | let body: JsonObject; |
| 300 | try { |
| 301 | const text = await request.text(); |
| 302 | if (text.length > MAX_BODY_BYTES) return new Response('payload too large\n', { status: 413 }); |
| 303 | const parsed: unknown = JSON.parse(text); |
| 304 | if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) { |
| 305 | return new Response('bad request\n', { status: 400 }); |
| 306 | } |
| 307 | body = parsed as JsonObject; |
| 308 | } catch { |
| 309 | return new Response('bad request\n', { status: 400 }); |
| 310 | } |
| 311 | |
| 312 | const machineId = body.machine_id; |
| 313 | if (typeof machineId !== 'string' || !UUID_RE.test(machineId)) { |
| 314 | return new Response('bad request\n', { status: 400 }); |
| 315 | } |
| 316 | |
| 317 | // Best-effort rate limit; fails open — losing a data point beats losing availability. |
| 318 | try { |
| 319 | const { success } = await env.MACHINE_RATE_LIMITER.limit({ key: machineId }); |
| 320 | if (!success) return new Response('rate limited\n', { status: 429 }); |
| 321 | } catch (err) { |
| 322 | console.error(JSON.stringify({ msg: 'rate limiter unavailable', err: String(err) })); |
| 323 | } |
| 324 | |
| 325 | const common: JsonObject = {}; |
| 326 | for (const [key, sanitize] of Object.entries(ENVELOPE_PROPS)) { |
| 327 | const val = sanitize(body[key]); |
| 328 | if (val !== undefined) common[key] = val; |
no test coverage detected