{"version":3,"file":"ingest.post.mjs","names":[],"sources":["../../../../../src/runtime/server/routes/_mxllog/ingest.post.ts"],"sourcesContent":["import { createError, defineEventHandler, getHeader, getHeaders, getRequestHost, readBody, setResponseStatus } from 'h3'\nimport { useNitroApp } from 'nitropack/runtime'\nimport type { IngestPayload, WideEvent } from '../../../../types'\nimport { getEnvironment } from '../../../../logger'\nimport { filterSafeHeaders } from '../../../../utils'\n\nconst VALID_LEVELS = ['info', 'error', 'warn', 'debug'] as const\n\nfunction validateOrigin(event: Parameters<typeof defineEventHandler>[0] extends (e: infer E) => unknown ? E : never): void {\n  const origin = getHeader(event, 'origin')\n  const referer = getHeader(event, 'referer')\n  const host = getRequestHost(event)\n\n  const requestOrigin = origin || (referer ? new URL(referer).origin : null)\n\n  if (!requestOrigin) {\n    throw createError({ statusCode: 403, message: 'Missing origin header' })\n  }\n\n  const originHost = new URL(requestOrigin).host\n\n  if (originHost !== host) {\n    throw createError({ statusCode: 403, message: 'Invalid origin' })\n  }\n}\n\n// ISO 8601 datetime pattern (e.g., 2024-01-31T14:00:00.000Z)\nconst ISO_8601_REGEX = /^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d{3})?Z$/\n\nfunction isValidISOTimestamp(value: string): boolean {\n  if (!ISO_8601_REGEX.test(value)) return false\n  const date = new Date(value)\n  return !Number.isNaN(date.getTime())\n}\n\nfunction validatePayload(body: unknown): IngestPayload {\n  if (!body || typeof body !== 'object' || Array.isArray(body)) {\n    throw createError({ statusCode: 400, message: 'Invalid request body' })\n  }\n\n  const payload = body as Record<string, unknown>\n\n  if (payload.timestamp === undefined || payload.timestamp === null) {\n    throw createError({ statusCode: 400, message: 'Missing required field: timestamp' })\n  }\n\n  const { timestamp: rawTimestamp } = payload\n  let timestamp: string\n  if (typeof rawTimestamp === 'number') {\n    const minTimestamp = new Date('2000-01-01').getTime()\n    const maxTimestamp = Date.now() + 24 * 60 * 60 * 1000 // 1 day in the future\n    if (rawTimestamp < minTimestamp || rawTimestamp > maxTimestamp) {\n      throw createError({ statusCode: 400, message: 'Invalid timestamp: value out of reasonable range' })\n    }\n    timestamp = new Date(rawTimestamp).toISOString()\n  } else if (typeof rawTimestamp === 'string') {\n    if (!isValidISOTimestamp(rawTimestamp)) {\n      throw createError({ statusCode: 400, message: 'Invalid timestamp: must be a valid ISO 8601 datetime string' })\n    }\n    timestamp = rawTimestamp\n  } else {\n    throw createError({ statusCode: 400, message: 'Invalid timestamp: must be string or number' })\n  }\n\n  if (!payload.level || typeof payload.level !== 'string') {\n    throw createError({ statusCode: 400, message: 'Missing required field: level' })\n  }\n\n  if (!VALID_LEVELS.includes(payload.level as typeof VALID_LEVELS[number])) {\n    throw createError({ statusCode: 400, message: `Invalid level: must be one of ${VALID_LEVELS.join(', ')}` })\n  }\n\n  return {\n    ...payload,\n    timestamp,\n    level: payload.level as IngestPayload['level'],\n  }\n}\n\nfunction getSafeHeaders(event: Parameters<typeof defineEventHandler>[0] extends (e: infer E) => unknown ? E : never): Record<string, string> {\n  const allHeaders = getHeaders(event as Parameters<typeof getHeaders>[0])\n  return filterSafeHeaders(allHeaders)\n}\n\nexport default defineEventHandler(async (event) => {\n  validateOrigin(event)\n\n  const body = await readBody(event)\n  const payload = validatePayload(body)\n  const nitroApp = useNitroApp()\n  const env = getEnvironment()\n\n  const { service: _clientService, ...sanitizedPayload } = payload as IngestPayload & { service?: unknown }\n\n  const wideEvent: WideEvent = {\n    ...sanitizedPayload,\n    ...env,\n    source: 'client',\n  }\n\n  const headers = getSafeHeaders(event)\n  const request = { method: 'POST' as const, path: event.path }\n\n  try {\n    await nitroApp.hooks.callHook('@safaricom-mxl/log:enrich', {\n      event: wideEvent,\n      request,\n      headers,\n      response: { status: 204 },\n    })\n  } catch (err) {\n    console.error('[mxllog] enrich failed:', err)\n  }\n\n  const drainPromise = nitroApp.hooks.callHook('@safaricom-mxl/log:drain', {\n    event: wideEvent,\n    request,\n    headers,\n  }).catch((err) => {\n    console.error('[mxllog] drain failed:', err)\n  })\n\n  // Use waitUntil if available (Cloudflare Workers, Vercel Edge)\n  // Otherwise, await the drain to prevent lost logs in serverless environments\n  const waitUntilCtx = (event as unknown as { context: Record<string, unknown> }).context\n  const cfCtx = (waitUntilCtx as { cloudflare?: { context?: { waitUntil?: (p: Promise<unknown>) => void } } }).cloudflare?.context ?? waitUntilCtx\n  if (typeof (cfCtx as { waitUntil?: unknown }).waitUntil === 'function') {\n    (cfCtx as { waitUntil: (p: Promise<unknown>) => void }).waitUntil(drainPromise)\n  } else {\n    await drainPromise\n  }\n\n  setResponseStatus(event, 204)\n  return null\n})\n"],"mappings":"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"}