{"version":3,"sources":["../../src/h3/plugin.ts"],"sourcesContent":["import type { H3Event, HTTPError } from \"h3\";\nimport { definePlugin, onError, onRequest, onResponse } from \"h3\";\nimport { AsyncLocalStorage } from \"node:async_hooks\";\nimport { performance } from \"node:perf_hooks\";\nimport type { ZodError } from \"zod\";\n\nimport { ApitallyClient } from \"../common/client.js\";\nimport { consumerFromStringOrObject } from \"../common/consumerRegistry.js\";\nimport { mergeHeaders, parseContentLength } from \"../common/headers.js\";\nimport type { LogRecord } from \"../common/requestLogger.js\";\nimport { convertHeaders } from \"../common/requestLogger.js\";\nimport { captureResponse } from \"../common/response.js\";\nimport type { SpanHandle } from \"../common/spanCollector.js\";\nimport { ApitallyConfig, ApitallyConsumer } from \"../common/types.js\";\nimport { patchConsole, patchWinston } from \"../loggers/index.js\";\nimport { getAppInfo } from \"./utils.js\";\n\nconst REQUEST_TIMESTAMP_SYMBOL = Symbol(\"apitally.requestTimestamp\");\nconst REQUEST_BODY_SYMBOL = Symbol(\"apitally.requestBody\");\nconst SPAN_HANDLE_SYMBOL = Symbol(\"apitally.spanHandle\");\n\ndeclare module \"h3\" {\n  interface H3EventContext {\n    apitallyConsumer?: ApitallyConsumer | string;\n\n    [REQUEST_TIMESTAMP_SYMBOL]?: number;\n    [REQUEST_BODY_SYMBOL]?: Buffer;\n    [SPAN_HANDLE_SYMBOL]?: SpanHandle;\n  }\n}\n\nconst jsonHeaders = new Headers({\n  \"content-type\": \"application/json;charset=UTF-8\",\n});\n\nexport const apitallyPlugin = definePlugin<ApitallyConfig>((app, config) => {\n  const client = new ApitallyClient(config);\n  const logsContext = new AsyncLocalStorage<LogRecord[]>();\n\n  const setStartupData = (attempt: number = 1) => {\n    const appInfo = getAppInfo(app, config.appVersion);\n    if (appInfo.paths.length > 0 || attempt >= 10) {\n      client.setStartupData(appInfo);\n      client.startSync();\n    } else {\n      setTimeout(() => setStartupData(attempt + 1), 500);\n    }\n  };\n  setTimeout(() => setStartupData(), 500);\n\n  if (client.requestLogger.config.captureLogs) {\n    patchConsole(logsContext);\n    patchWinston(logsContext);\n  }\n\n  const handleResponse = async (\n    event: H3Event,\n    response?: Response,\n    error?: HTTPError,\n  ) => {\n    if (event.req.method.toUpperCase() === \"OPTIONS\") {\n      return response;\n    }\n\n    const startTime = event.context[REQUEST_TIMESTAMP_SYMBOL];\n    const path = event.context.matchedRoute?.route;\n\n    const consumer = getConsumer(event);\n    client.consumerRegistry.addOrUpdateConsumer(consumer);\n\n    if (!response) {\n      response = new Response(null, {\n        status: error?.status || 500,\n        statusText: error?.statusText || \"Internal Server Error\",\n        headers: error?.headers\n          ? mergeHeaders(jsonHeaders, error.headers)\n          : jsonHeaders,\n      });\n    }\n\n    const [newResponse, responsePromise] = captureResponse(response, {\n      captureBody:\n        client.requestLogger.enabled &&\n        client.requestLogger.config.logResponseBody,\n      maxBodySize: client.requestLogger.maxBodySize,\n    });\n\n    responsePromise.then(async (capturedResponse) => {\n      const responseTime = startTime ? performance.now() - startTime : 0;\n\n      const spanHandle = event.context[SPAN_HANDLE_SYMBOL];\n      spanHandle?.setName(`${event.req.method} ${path}`);\n      const spans = spanHandle?.end();\n      const traceId = spanHandle?.traceId;\n\n      const responseSize = capturedResponse.completed\n        ? capturedResponse.size\n        : undefined;\n      const requestSize = parseContentLength(\n        event.req.headers.get(\"content-length\"),\n      );\n\n      if (path) {\n        client.requestCounter.addRequest({\n          consumer: consumer?.identifier,\n          method: event.req.method,\n          path,\n          statusCode: response.status,\n          responseTime,\n          requestSize,\n          responseSize,\n        });\n      }\n\n      if (client.requestLogger.enabled) {\n        const logs = logsContext.getStore();\n        client.requestLogger.logRequest(\n          {\n            timestamp: (Date.now() - responseTime) / 1000,\n            method: event.req.method,\n            path,\n            url: event.req.url,\n            headers: convertHeaders(\n              Object.fromEntries(event.req.headers.entries()),\n            ),\n            size: requestSize,\n            consumer: consumer?.identifier,\n            body: event.context[REQUEST_BODY_SYMBOL],\n          },\n          {\n            statusCode: response.status,\n            responseTime: responseTime / 1000,\n            headers: convertHeaders(\n              Object.fromEntries(response.headers.entries()),\n            ),\n            size: responseSize,\n            body: capturedResponse.body,\n          },\n          error?.cause instanceof Error ? error.cause : undefined,\n          logs,\n          spans,\n          traceId,\n        );\n      }\n    });\n\n    if (\n      path &&\n      error?.status === 400 &&\n      error.data &&\n      Array.isArray((error.data as any).issues)\n    ) {\n      const issues = (error.data as any).issues as ZodError[\"issues\"];\n      issues.forEach((issue) => {\n        client.validationErrorCounter.addValidationError({\n          consumer: consumer?.identifier,\n          method: event.req.method,\n          path,\n          loc: issue.path.join(\".\"),\n          msg: issue.message,\n          type: issue.code,\n        });\n      });\n    }\n\n    if (path && error?.status === 500 && error.cause instanceof Error) {\n      client.serverErrorCounter.addServerError({\n        consumer: consumer?.identifier,\n        method: event.req.method,\n        path,\n        type: error.cause.name,\n        msg: error.cause.message,\n        traceback: error.cause.stack || \"\",\n      });\n    }\n\n    return newResponse;\n  };\n\n  app\n    .use(\n      onRequest(async (event) => {\n        logsContext.enterWith([]);\n        event.context[REQUEST_TIMESTAMP_SYMBOL] = performance.now();\n\n        const spanHandle = client.spanCollector.startSpan();\n        event.context[SPAN_HANDLE_SYMBOL] = spanHandle;\n        spanHandle.enterContext();\n\n        const requestContentType = event.req.headers.get(\"content-type\");\n        const requestSize =\n          parseContentLength(event.req.headers.get(\"content-length\")) ?? 0;\n\n        if (\n          client.requestLogger.enabled &&\n          client.requestLogger.config.logRequestBody &&\n          client.requestLogger.isSupportedContentType(requestContentType) &&\n          requestSize <= client.requestLogger.maxBodySize\n        ) {\n          const clonedRequest = event.req.clone();\n          const requestBody = Buffer.from(await clonedRequest.arrayBuffer());\n          event.context[REQUEST_BODY_SYMBOL] = requestBody;\n        }\n      }),\n    )\n    .use(\n      onResponse((response, event) => {\n        if (client.isEnabled()) {\n          return handleResponse(event, response, undefined);\n        }\n      }),\n    )\n    .use(\n      onError((error, event) => {\n        if (client.isEnabled()) {\n          handleResponse(event, undefined, error);\n        }\n      }),\n    );\n});\n\nexport function setConsumer(\n  event: H3Event,\n  consumer: ApitallyConsumer | string | null | undefined,\n) {\n  event.context.apitallyConsumer = consumer || undefined;\n}\n\nfunction getConsumer(event: H3Event) {\n  const consumer = event.context.apitallyConsumer;\n  if (consumer) {\n    return consumerFromStringOrObject(consumer);\n  }\n  return null;\n}\n"],"mappings":"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","names":["REQUEST_TIMESTAMP_SYMBOL","Symbol","REQUEST_BODY_SYMBOL","SPAN_HANDLE_SYMBOL","jsonHeaders","Headers","apitallyPlugin","definePlugin","app","config","client","ApitallyClient","logsContext","AsyncLocalStorage","setStartupData","attempt","appInfo","getAppInfo","appVersion","paths","length","startSync","setTimeout","requestLogger","captureLogs","patchConsole","patchWinston","handleResponse","event","response","error","req","method","toUpperCase","startTime","context","path","matchedRoute","route","consumer","getConsumer","consumerRegistry","addOrUpdateConsumer","Response","status","statusText","headers","mergeHeaders","newResponse","responsePromise","captureResponse","captureBody","enabled","logResponseBody","maxBodySize","then","capturedResponse","responseTime","performance","now","spanHandle","setName","spans","end","traceId","responseSize","completed","size","undefined","requestSize","parseContentLength","get","requestCounter","addRequest","identifier","statusCode","logs","getStore","logRequest","timestamp","Date","url","convertHeaders","Object","fromEntries","entries","body","cause","Error","data","Array","isArray","issues","forEach","issue","validationErrorCounter","addValidationError","loc","join","msg","message","type","code","serverErrorCounter","addServerError","name","traceback","stack","use","onRequest","enterWith","spanCollector","startSpan","enterContext","requestContentType","logRequestBody","isSupportedContentType","clonedRequest","clone","requestBody","Buffer","from","arrayBuffer","onResponse","isEnabled","onError","setConsumer","apitallyConsumer","consumerFromStringOrObject"]}