apitally 0.25.4 → 0.25.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -189,6 +189,25 @@ const _RequestLogger = class _RequestLogger {
189
189
  }
190
190
  return typeof contentType === "string" && ALLOWED_CONTENT_TYPES.some((t) => contentType.startsWith(t));
191
191
  }
192
+ static isHttps(headers) {
193
+ return headers.some(([k, v]) => {
194
+ switch (k.toLowerCase()) {
195
+ case "x-forwarded-proto":
196
+ case "x-forwarded-protocol":
197
+ case "x-forwarded-scheme":
198
+ case "x-url-scheme":
199
+ case "x-scheme":
200
+ return v.split(",")[0].trim().toLowerCase() === "https";
201
+ case "front-end-https":
202
+ case "x-forwarded-ssl":
203
+ return v.toLowerCase() === "on";
204
+ case "forwarded":
205
+ return v.split(",")[0].trim().toLowerCase().includes("proto=https");
206
+ default:
207
+ return false;
208
+ }
209
+ });
210
+ }
192
211
  maskQueryParams(search) {
193
212
  const params = new URLSearchParams(search);
194
213
  for (const [key] of params) {
@@ -287,6 +306,10 @@ const _RequestLogger = class _RequestLogger {
287
306
  const url = new URL(request.url);
288
307
  const path = request.path ?? url.pathname;
289
308
  const userAgent = (_a = request.headers.find(([k]) => k.toLowerCase() === "user-agent")) == null ? void 0 : _a[1];
309
+ if (url.protocol === "http:" && _RequestLogger.isHttps(request.headers)) {
310
+ url.protocol = "https:";
311
+ request.url = url.toString();
312
+ }
290
313
  if (this.shouldExcludePath(path) || this.shouldExcludeUserAgent(userAgent) || (((_c = (_b = this.config).excludeCallback) == null ? void 0 : _c.call(_b, request, response)) ?? false)) {
291
314
  return;
292
315
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/common/requestLogger.ts"],"sourcesContent":["import AsyncLock from \"async-lock\";\nimport { Buffer } from \"node:buffer\";\nimport { randomUUID } from \"node:crypto\";\nimport type { IncomingHttpHeaders, OutgoingHttpHeaders } from \"node:http\";\n\nimport { getSentryEventId } from \"./sentry.js\";\nimport {\n truncateExceptionMessage,\n truncateExceptionStackTrace,\n} from \"./serverErrorCounter.js\";\nimport type { SpanData } from \"./spanCollector.js\";\nimport TempGzipFile, { checkWritableFs } from \"./tempGzipFile.js\";\n\nconst MAX_BODY_SIZE = 50_000; // 50 KB (uncompressed)\nconst MAX_FILE_SIZE = 1_000_000; // 1 MB (compressed)\nconst MAX_FILES = 50;\nconst MAX_PENDING_WRITES = 100;\nconst MAX_LOG_MSG_LENGTH = 2048;\nconst BODY_TOO_LARGE = Buffer.from(\"<body too large>\");\nconst BODY_MASKED = Buffer.from(\"<masked>\");\nconst MASKED = \"******\";\nconst ALLOWED_CONTENT_TYPES = [\n \"application/json\",\n \"application/ld+json\",\n \"application/problem+json\",\n \"application/vnd.api+json\",\n \"application/x-ndjson\",\n \"text/plain\",\n \"text/html\",\n];\nconst EXCLUDE_PATH_PATTERNS = [\n /\\/_?healthz?$/i,\n /\\/_?health[_-]?checks?$/i,\n /\\/_?heart[_-]?beats?$/i,\n /\\/ping$/i,\n /\\/ready$/i,\n /\\/live$/i,\n /\\/favicon(?:-[\\w-]+)?\\.(ico|png|svg)$/,\n /\\/apple-touch-icon(?:-[\\w-]+)?\\.png$/,\n /\\/robots\\.txt$/,\n /\\/sitemap\\.xml$/,\n /\\/manifest\\.json$/,\n /\\/site\\.webmanifest$/,\n /\\/service-worker\\.js$/,\n /\\/sw\\.js$/,\n /\\/\\.well-known\\//,\n];\nconst EXCLUDE_USER_AGENT_PATTERNS = [\n /health[-_ ]?check/i,\n /microsoft-azure-application-lb/i,\n /googlehc/i,\n /kube-probe/i,\n];\nconst MASK_QUERY_PARAM_PATTERNS = [\n /auth/i,\n /api-?key/i,\n /secret/i,\n /token/i,\n /password/i,\n /pwd/i,\n];\nconst MASK_HEADER_PATTERNS = [\n /auth/i,\n /api-?key/i,\n /secret/i,\n /token/i,\n /cookie/i,\n];\nconst MASK_BODY_FIELD_PATTERNS = [\n /password/i,\n /pwd/i,\n /token/i,\n /secret/i,\n /auth/i,\n /card[-_ ]?number/i,\n /ccv/i,\n /ssn/i,\n];\n\nexport type Request = {\n timestamp: number;\n method: string;\n path?: string;\n url: string;\n headers: [string, string][];\n size?: number;\n consumer?: string;\n body?: Buffer;\n};\n\nexport type Response = {\n statusCode: number;\n responseTime: number;\n headers: [string, string][];\n size?: number;\n body?: Buffer;\n};\n\nexport type LogRecord = {\n timestamp: number;\n logger?: string;\n level: string;\n message: string;\n};\n\nexport type RequestLoggingConfig = {\n enabled: boolean;\n logQueryParams: boolean;\n logRequestHeaders: boolean;\n logRequestBody: boolean;\n logResponseHeaders: boolean;\n logResponseBody: boolean;\n logException: boolean;\n captureLogs: boolean;\n captureTraces: boolean;\n maskQueryParams: RegExp[];\n maskHeaders: RegExp[];\n maskBodyFields: RegExp[];\n maskRequestBodyCallback?: (request: Request) => Buffer | null | undefined;\n maskResponseBodyCallback?: (\n request: Request,\n response: Response,\n ) => Buffer | null | undefined;\n excludePaths: RegExp[];\n excludeCallback?: (request: Request, response: Response) => boolean;\n};\n\nconst DEFAULT_CONFIG: RequestLoggingConfig = {\n enabled: false,\n logQueryParams: true,\n logRequestHeaders: false,\n logRequestBody: false,\n logResponseHeaders: true,\n logResponseBody: false,\n logException: true,\n captureLogs: false,\n captureTraces: false,\n maskQueryParams: [],\n maskHeaders: [],\n maskBodyFields: [],\n excludePaths: [],\n};\n\ntype RequestLogItem = {\n uuid: string;\n request: Request;\n response: Response;\n exception?: {\n type: string;\n message: string;\n stacktrace: string;\n sentryEventId?: string;\n };\n logs?: LogRecord[];\n spans?: SpanData[];\n traceId?: string;\n};\n\nexport default class RequestLogger {\n public config: RequestLoggingConfig;\n public enabled: boolean;\n public suspendUntil: number | null = null;\n private pendingWrites: RequestLogItem[] = [];\n private currentFile: TempGzipFile | null = null;\n private files: TempGzipFile[] = [];\n private maintainIntervalId?: NodeJS.Timeout;\n private lock = new AsyncLock();\n\n constructor(config?: Partial<RequestLoggingConfig>) {\n this.config = { ...DEFAULT_CONFIG, ...config };\n this.enabled = this.config.enabled && checkWritableFs();\n\n if (this.enabled) {\n this.maintainIntervalId = setInterval(() => {\n this.maintain();\n }, 1000);\n }\n }\n\n get maxBodySize() {\n return MAX_BODY_SIZE;\n }\n\n private shouldExcludePath(urlPath: string) {\n const patterns = [...this.config.excludePaths, ...EXCLUDE_PATH_PATTERNS];\n return matchPatterns(urlPath, patterns);\n }\n\n private shouldExcludeUserAgent(userAgent?: string) {\n return userAgent\n ? matchPatterns(userAgent, EXCLUDE_USER_AGENT_PATTERNS)\n : false;\n }\n\n private shouldMaskQueryParam(name: string) {\n const patterns = [\n ...this.config.maskQueryParams,\n ...MASK_QUERY_PARAM_PATTERNS,\n ];\n return matchPatterns(name, patterns);\n }\n\n private shouldMaskHeader(name: string) {\n const patterns = [...this.config.maskHeaders, ...MASK_HEADER_PATTERNS];\n return matchPatterns(name, patterns);\n }\n\n private shouldMaskBodyField(name: string) {\n const patterns = [\n ...this.config.maskBodyFields,\n ...MASK_BODY_FIELD_PATTERNS,\n ];\n return matchPatterns(name, patterns);\n }\n\n private getContentType(headers: [string, string][]) {\n return headers.find(([k]) => k.toLowerCase() === \"content-type\")?.[1];\n }\n\n private hasSupportedContentType(headers: [string, string][]) {\n const contentType = this.getContentType(headers);\n return this.isSupportedContentType(contentType);\n }\n\n public isSupportedContentType(contentType?: string | string[] | null) {\n if (Array.isArray(contentType) && contentType.length > 0) {\n contentType = contentType[0];\n }\n return (\n typeof contentType === \"string\" &&\n ALLOWED_CONTENT_TYPES.some((t) => contentType.startsWith(t))\n );\n }\n\n private maskQueryParams(search: string) {\n const params = new URLSearchParams(search);\n for (const [key] of params) {\n if (this.shouldMaskQueryParam(key)) {\n params.set(key, MASKED);\n }\n }\n return params.toString();\n }\n\n private maskHeaders(headers: [string, string][]): [string, string][] {\n return headers.map(([k, v]) => [k, this.shouldMaskHeader(k) ? MASKED : v]);\n }\n\n private maskBody(data: any): any {\n if (typeof data === \"object\" && data !== null && !Array.isArray(data)) {\n const result: any = {};\n for (const [key, value] of Object.entries(data)) {\n if (typeof value === \"string\" && this.shouldMaskBodyField(key)) {\n result[key] = MASKED;\n } else {\n result[key] = this.maskBody(value);\n }\n }\n return result;\n }\n if (Array.isArray(data)) {\n return data.map((item) => this.maskBody(item));\n }\n return data;\n }\n\n private applyMasking(item: RequestLogItem) {\n // Apply user-provided maskRequestBodyCallback function\n if (\n this.config.maskRequestBodyCallback &&\n item.request.body &&\n item.request.body !== BODY_TOO_LARGE\n ) {\n try {\n const maskedBody = this.config.maskRequestBodyCallback(item.request);\n item.request.body = maskedBody ?? BODY_MASKED;\n } catch {\n item.request.body = undefined;\n }\n }\n\n // Apply user-provided maskResponseBodyCallback function\n if (\n this.config.maskResponseBodyCallback &&\n item.response.body &&\n item.response.body !== BODY_TOO_LARGE\n ) {\n try {\n const maskedBody = this.config.maskResponseBodyCallback(\n item.request,\n item.response,\n );\n item.response.body = maskedBody ?? BODY_MASKED;\n } catch {\n item.response.body = undefined;\n }\n }\n\n // Check request and response body sizes\n if (item.request.body && item.request.body.length > MAX_BODY_SIZE) {\n item.request.body = BODY_TOO_LARGE;\n }\n if (item.response.body && item.response.body.length > MAX_BODY_SIZE) {\n item.response.body = BODY_TOO_LARGE;\n }\n\n // Mask request and response body fields\n for (const key of [\"request\", \"response\"] as const) {\n const bodyData = item[key].body;\n if (\n !bodyData ||\n bodyData === BODY_TOO_LARGE ||\n bodyData === BODY_MASKED\n ) {\n continue;\n }\n\n try {\n const contentType = this.getContentType(item[key].headers);\n if (!contentType || /\\bjson\\b/i.test(contentType)) {\n const parsedBody = JSON.parse(bodyData.toString());\n const maskedBody = this.maskBody(parsedBody);\n item[key].body = Buffer.from(JSON.stringify(maskedBody));\n } else if (/\\bndjson\\b/i.test(contentType)) {\n const lines = bodyData\n .toString()\n .split(\"\\n\")\n .filter((line) => line.trim());\n const maskedLines = lines.map((line) => {\n try {\n const parsed = JSON.parse(line);\n const masked = this.maskBody(parsed);\n return JSON.stringify(masked);\n } catch {\n return line; // Keep unparseable lines as is\n }\n });\n item[key].body = Buffer.from(maskedLines.join(\"\\n\"));\n }\n } catch {\n // If parsing fails, leave body as is\n }\n }\n\n // Mask request and response headers\n item.request.headers = this.config.logRequestHeaders\n ? this.maskHeaders(item.request.headers)\n : [];\n item.response.headers = this.config.logResponseHeaders\n ? this.maskHeaders(item.response.headers)\n : [];\n\n // Mask query params\n const url = new URL(item.request.url);\n url.search = this.config.logQueryParams\n ? this.maskQueryParams(url.search)\n : \"\";\n item.request.url = url.toString();\n\n return item;\n }\n\n logRequest(\n request: Request,\n response: Response,\n error?: Error,\n logs?: LogRecord[],\n spans?: SpanData[],\n traceId?: string,\n ) {\n if (!this.enabled || this.suspendUntil !== null) return;\n\n const url = new URL(request.url);\n const path = request.path ?? url.pathname;\n const userAgent = request.headers.find(\n ([k]) => k.toLowerCase() === \"user-agent\",\n )?.[1];\n\n if (\n this.shouldExcludePath(path) ||\n this.shouldExcludeUserAgent(userAgent) ||\n (this.config.excludeCallback?.(request, response) ?? false)\n ) {\n return;\n }\n\n if (\n !this.config.logRequestBody ||\n !this.hasSupportedContentType(request.headers)\n ) {\n request.body = undefined;\n }\n if (\n !this.config.logResponseBody ||\n !this.hasSupportedContentType(response.headers)\n ) {\n response.body = undefined;\n }\n\n if (request.size !== undefined && request.size < 0) {\n request.size = undefined;\n }\n if (response.size !== undefined && response.size < 0) {\n response.size = undefined;\n }\n\n const item: RequestLogItem = {\n uuid: randomUUID(),\n request: request,\n response: response,\n exception:\n error && this.config.logException\n ? {\n type: error.name,\n message: truncateExceptionMessage(error.message),\n stacktrace: truncateExceptionStackTrace(error.stack || \"\"),\n sentryEventId: getSentryEventId(),\n }\n : undefined,\n };\n\n if (logs && logs.length > 0) {\n item.logs = logs.map((log) => ({\n timestamp: log.timestamp,\n logger: log.logger,\n level: log.level,\n message: truncateLogMessage(log.message),\n }));\n }\n if (spans && spans.length > 0) {\n item.spans = spans;\n }\n if (traceId) {\n item.traceId = traceId;\n }\n this.pendingWrites.push(item);\n\n if (this.pendingWrites.length > MAX_PENDING_WRITES) {\n this.pendingWrites.shift();\n }\n }\n\n async writeToFile() {\n if (!this.enabled || this.pendingWrites.length === 0) {\n return;\n }\n return this.lock.acquire(\"file\", async () => {\n if (!this.currentFile) {\n this.currentFile = new TempGzipFile(\"request_logs\");\n }\n while (this.pendingWrites.length > 0) {\n let item = this.pendingWrites.shift();\n if (item) {\n item = this.applyMasking(item);\n\n const finalItem = {\n uuid: item.uuid,\n request: skipEmptyValues(item.request),\n response: skipEmptyValues(item.response),\n exception: item.exception,\n logs: item.logs,\n spans: item.spans,\n traceId: item.traceId,\n };\n\n // Set up body serialization for JSON\n [finalItem.request.body, finalItem.response.body].forEach((body) => {\n if (body) {\n // @ts-expect-error Override Buffer's default JSON serialization\n body.toJSON = function () {\n return this.toString(\"base64\");\n };\n }\n });\n\n await this.currentFile.writeLine(\n Buffer.from(JSON.stringify(finalItem)),\n );\n }\n }\n });\n }\n\n getFile() {\n return this.files.shift();\n }\n\n retryFileLater(file: TempGzipFile) {\n this.files.unshift(file);\n }\n\n async rotateFile() {\n return this.lock.acquire(\"file\", async () => {\n if (this.currentFile) {\n await this.currentFile.close();\n this.files.push(this.currentFile);\n this.currentFile = null;\n }\n });\n }\n\n async maintain() {\n await this.writeToFile();\n if (this.currentFile && this.currentFile.size > MAX_FILE_SIZE) {\n await this.rotateFile();\n }\n while (this.files.length > MAX_FILES) {\n const file = this.files.shift();\n file?.delete();\n }\n if (this.suspendUntil !== null && this.suspendUntil < Date.now()) {\n this.suspendUntil = null;\n }\n }\n\n async clear() {\n this.pendingWrites = [];\n await this.rotateFile();\n this.files.forEach((file) => {\n file.delete();\n });\n this.files = [];\n }\n\n async close() {\n this.enabled = false;\n await this.clear();\n if (this.maintainIntervalId) {\n clearInterval(this.maintainIntervalId);\n }\n }\n}\n\nexport function convertHeaders(\n headers:\n | Headers\n | IncomingHttpHeaders\n | OutgoingHttpHeaders\n | Record<string, string | string[] | number | undefined>\n | undefined,\n) {\n if (!headers) {\n return [];\n }\n if (headers instanceof Headers) {\n return Array.from(headers.entries());\n }\n return Object.entries(headers).flatMap(([key, value]) => {\n if (value === undefined) {\n return [];\n }\n if (Array.isArray(value)) {\n return value.map((v) => [key, v]);\n }\n return [[key, value.toString()]];\n }) as [string, string][];\n}\n\nexport function convertBody(body: any, contentType?: string | null) {\n if (!body || !contentType) {\n return;\n }\n try {\n if (contentType.startsWith(\"application/json\")) {\n if (isValidJsonString(body)) {\n return Buffer.from(body);\n } else {\n return Buffer.from(JSON.stringify(body));\n }\n }\n if (contentType.startsWith(\"text/\") && typeof body === \"string\") {\n return Buffer.from(body);\n }\n } catch (error) {\n return;\n }\n}\n\nfunction isValidJsonString(body: any) {\n if (typeof body !== \"string\") {\n return false;\n }\n try {\n JSON.parse(body);\n return true;\n } catch {\n return false;\n }\n}\n\nfunction matchPatterns(value: string, patterns: RegExp[]) {\n return patterns.some((pattern) => {\n return pattern.test(value);\n });\n}\n\nfunction skipEmptyValues<T extends Record<string, any>>(data: T) {\n return Object.fromEntries(\n Object.entries(data).filter(([_, v]) => {\n if (v == null || Number.isNaN(v)) return false;\n if (Array.isArray(v) || Buffer.isBuffer(v) || typeof v === \"string\") {\n return v.length > 0;\n }\n return true;\n }),\n ) as Partial<T>;\n}\n\nfunction truncateLogMessage(msg: string) {\n if (msg.length > MAX_LOG_MSG_LENGTH) {\n const suffix = \"... (truncated)\";\n return msg.slice(0, MAX_LOG_MSG_LENGTH - suffix.length) + suffix;\n }\n return msg;\n}\n"],"mappings":"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","names":["MAX_BODY_SIZE","MAX_FILE_SIZE","MAX_FILES","MAX_PENDING_WRITES","MAX_LOG_MSG_LENGTH","BODY_TOO_LARGE","Buffer","from","BODY_MASKED","MASKED","ALLOWED_CONTENT_TYPES","EXCLUDE_PATH_PATTERNS","EXCLUDE_USER_AGENT_PATTERNS","MASK_QUERY_PARAM_PATTERNS","MASK_HEADER_PATTERNS","MASK_BODY_FIELD_PATTERNS","DEFAULT_CONFIG","enabled","logQueryParams","logRequestHeaders","logRequestBody","logResponseHeaders","logResponseBody","logException","captureLogs","captureTraces","maskQueryParams","maskHeaders","maskBodyFields","excludePaths","RequestLogger","config","suspendUntil","pendingWrites","currentFile","files","maintainIntervalId","lock","AsyncLock","checkWritableFs","setInterval","maintain","maxBodySize","shouldExcludePath","urlPath","patterns","matchPatterns","shouldExcludeUserAgent","userAgent","shouldMaskQueryParam","name","shouldMaskHeader","shouldMaskBodyField","getContentType","headers","find","k","toLowerCase","hasSupportedContentType","contentType","isSupportedContentType","Array","isArray","length","some","t","startsWith","search","params","URLSearchParams","key","set","toString","map","v","maskBody","data","result","value","Object","entries","item","applyMasking","maskRequestBodyCallback","request","body","maskedBody","undefined","maskResponseBodyCallback","response","bodyData","test","parsedBody","JSON","parse","stringify","lines","split","filter","line","trim","maskedLines","parsed","masked","join","url","URL","logRequest","error","logs","spans","traceId","path","pathname","excludeCallback","size","uuid","randomUUID","exception","type","message","truncateExceptionMessage","stacktrace","truncateExceptionStackTrace","stack","sentryEventId","getSentryEventId","log","timestamp","logger","level","truncateLogMessage","push","shift","writeToFile","acquire","TempGzipFile","finalItem","skipEmptyValues","forEach","toJSON","writeLine","getFile","retryFileLater","file","unshift","rotateFile","close","delete","Date","now","clear","clearInterval","convertHeaders","Headers","flatMap","convertBody","isValidJsonString","pattern","fromEntries","_","Number","isNaN","isBuffer","msg","suffix","slice"]}
1
+ {"version":3,"sources":["../../src/common/requestLogger.ts"],"sourcesContent":["import AsyncLock from \"async-lock\";\nimport { Buffer } from \"node:buffer\";\nimport { randomUUID } from \"node:crypto\";\nimport type { IncomingHttpHeaders, OutgoingHttpHeaders } from \"node:http\";\n\nimport { getSentryEventId } from \"./sentry.js\";\nimport {\n truncateExceptionMessage,\n truncateExceptionStackTrace,\n} from \"./serverErrorCounter.js\";\nimport type { SpanData } from \"./spanCollector.js\";\nimport TempGzipFile, { checkWritableFs } from \"./tempGzipFile.js\";\n\nconst MAX_BODY_SIZE = 50_000; // 50 KB (uncompressed)\nconst MAX_FILE_SIZE = 1_000_000; // 1 MB (compressed)\nconst MAX_FILES = 50;\nconst MAX_PENDING_WRITES = 100;\nconst MAX_LOG_MSG_LENGTH = 2048;\nconst BODY_TOO_LARGE = Buffer.from(\"<body too large>\");\nconst BODY_MASKED = Buffer.from(\"<masked>\");\nconst MASKED = \"******\";\nconst ALLOWED_CONTENT_TYPES = [\n \"application/json\",\n \"application/ld+json\",\n \"application/problem+json\",\n \"application/vnd.api+json\",\n \"application/x-ndjson\",\n \"text/plain\",\n \"text/html\",\n];\nconst EXCLUDE_PATH_PATTERNS = [\n /\\/_?healthz?$/i,\n /\\/_?health[_-]?checks?$/i,\n /\\/_?heart[_-]?beats?$/i,\n /\\/ping$/i,\n /\\/ready$/i,\n /\\/live$/i,\n /\\/favicon(?:-[\\w-]+)?\\.(ico|png|svg)$/,\n /\\/apple-touch-icon(?:-[\\w-]+)?\\.png$/,\n /\\/robots\\.txt$/,\n /\\/sitemap\\.xml$/,\n /\\/manifest\\.json$/,\n /\\/site\\.webmanifest$/,\n /\\/service-worker\\.js$/,\n /\\/sw\\.js$/,\n /\\/\\.well-known\\//,\n];\nconst EXCLUDE_USER_AGENT_PATTERNS = [\n /health[-_ ]?check/i,\n /microsoft-azure-application-lb/i,\n /googlehc/i,\n /kube-probe/i,\n];\nconst MASK_QUERY_PARAM_PATTERNS = [\n /auth/i,\n /api-?key/i,\n /secret/i,\n /token/i,\n /password/i,\n /pwd/i,\n];\nconst MASK_HEADER_PATTERNS = [\n /auth/i,\n /api-?key/i,\n /secret/i,\n /token/i,\n /cookie/i,\n];\nconst MASK_BODY_FIELD_PATTERNS = [\n /password/i,\n /pwd/i,\n /token/i,\n /secret/i,\n /auth/i,\n /card[-_ ]?number/i,\n /ccv/i,\n /ssn/i,\n];\n\nexport type Request = {\n timestamp: number;\n method: string;\n path?: string;\n url: string;\n headers: [string, string][];\n size?: number;\n consumer?: string;\n body?: Buffer;\n};\n\nexport type Response = {\n statusCode: number;\n responseTime: number;\n headers: [string, string][];\n size?: number;\n body?: Buffer;\n};\n\nexport type LogRecord = {\n timestamp: number;\n logger?: string;\n level: string;\n message: string;\n};\n\nexport type RequestLoggingConfig = {\n enabled: boolean;\n logQueryParams: boolean;\n logRequestHeaders: boolean;\n logRequestBody: boolean;\n logResponseHeaders: boolean;\n logResponseBody: boolean;\n logException: boolean;\n captureLogs: boolean;\n captureTraces: boolean;\n maskQueryParams: RegExp[];\n maskHeaders: RegExp[];\n maskBodyFields: RegExp[];\n maskRequestBodyCallback?: (request: Request) => Buffer | null | undefined;\n maskResponseBodyCallback?: (\n request: Request,\n response: Response,\n ) => Buffer | null | undefined;\n excludePaths: RegExp[];\n excludeCallback?: (request: Request, response: Response) => boolean;\n};\n\nconst DEFAULT_CONFIG: RequestLoggingConfig = {\n enabled: false,\n logQueryParams: true,\n logRequestHeaders: false,\n logRequestBody: false,\n logResponseHeaders: true,\n logResponseBody: false,\n logException: true,\n captureLogs: false,\n captureTraces: false,\n maskQueryParams: [],\n maskHeaders: [],\n maskBodyFields: [],\n excludePaths: [],\n};\n\ntype RequestLogItem = {\n uuid: string;\n request: Request;\n response: Response;\n exception?: {\n type: string;\n message: string;\n stacktrace: string;\n sentryEventId?: string;\n };\n logs?: LogRecord[];\n spans?: SpanData[];\n traceId?: string;\n};\n\nexport default class RequestLogger {\n public config: RequestLoggingConfig;\n public enabled: boolean;\n public suspendUntil: number | null = null;\n private pendingWrites: RequestLogItem[] = [];\n private currentFile: TempGzipFile | null = null;\n private files: TempGzipFile[] = [];\n private maintainIntervalId?: NodeJS.Timeout;\n private lock = new AsyncLock();\n\n constructor(config?: Partial<RequestLoggingConfig>) {\n this.config = { ...DEFAULT_CONFIG, ...config };\n this.enabled = this.config.enabled && checkWritableFs();\n\n if (this.enabled) {\n this.maintainIntervalId = setInterval(() => {\n this.maintain();\n }, 1000);\n }\n }\n\n get maxBodySize() {\n return MAX_BODY_SIZE;\n }\n\n private shouldExcludePath(urlPath: string) {\n const patterns = [...this.config.excludePaths, ...EXCLUDE_PATH_PATTERNS];\n return matchPatterns(urlPath, patterns);\n }\n\n private shouldExcludeUserAgent(userAgent?: string) {\n return userAgent\n ? matchPatterns(userAgent, EXCLUDE_USER_AGENT_PATTERNS)\n : false;\n }\n\n private shouldMaskQueryParam(name: string) {\n const patterns = [\n ...this.config.maskQueryParams,\n ...MASK_QUERY_PARAM_PATTERNS,\n ];\n return matchPatterns(name, patterns);\n }\n\n private shouldMaskHeader(name: string) {\n const patterns = [...this.config.maskHeaders, ...MASK_HEADER_PATTERNS];\n return matchPatterns(name, patterns);\n }\n\n private shouldMaskBodyField(name: string) {\n const patterns = [\n ...this.config.maskBodyFields,\n ...MASK_BODY_FIELD_PATTERNS,\n ];\n return matchPatterns(name, patterns);\n }\n\n private getContentType(headers: [string, string][]) {\n return headers.find(([k]) => k.toLowerCase() === \"content-type\")?.[1];\n }\n\n private hasSupportedContentType(headers: [string, string][]) {\n const contentType = this.getContentType(headers);\n return this.isSupportedContentType(contentType);\n }\n\n public isSupportedContentType(contentType?: string | string[] | null) {\n if (Array.isArray(contentType) && contentType.length > 0) {\n contentType = contentType[0];\n }\n return (\n typeof contentType === \"string\" &&\n ALLOWED_CONTENT_TYPES.some((t) => contentType.startsWith(t))\n );\n }\n\n private static isHttps(headers: [string, string][]): boolean {\n return headers.some(([k, v]) => {\n switch (k.toLowerCase()) {\n case \"x-forwarded-proto\":\n case \"x-forwarded-protocol\":\n case \"x-forwarded-scheme\":\n case \"x-url-scheme\":\n case \"x-scheme\":\n return v.split(\",\")[0].trim().toLowerCase() === \"https\";\n case \"front-end-https\":\n case \"x-forwarded-ssl\":\n return v.toLowerCase() === \"on\";\n case \"forwarded\":\n return v.split(\",\")[0].trim().toLowerCase().includes(\"proto=https\");\n default:\n return false;\n }\n });\n }\n\n private maskQueryParams(search: string) {\n const params = new URLSearchParams(search);\n for (const [key] of params) {\n if (this.shouldMaskQueryParam(key)) {\n params.set(key, MASKED);\n }\n }\n return params.toString();\n }\n\n private maskHeaders(headers: [string, string][]): [string, string][] {\n return headers.map(([k, v]) => [k, this.shouldMaskHeader(k) ? MASKED : v]);\n }\n\n private maskBody(data: any): any {\n if (typeof data === \"object\" && data !== null && !Array.isArray(data)) {\n const result: any = {};\n for (const [key, value] of Object.entries(data)) {\n if (typeof value === \"string\" && this.shouldMaskBodyField(key)) {\n result[key] = MASKED;\n } else {\n result[key] = this.maskBody(value);\n }\n }\n return result;\n }\n if (Array.isArray(data)) {\n return data.map((item) => this.maskBody(item));\n }\n return data;\n }\n\n private applyMasking(item: RequestLogItem) {\n // Apply user-provided maskRequestBodyCallback function\n if (\n this.config.maskRequestBodyCallback &&\n item.request.body &&\n item.request.body !== BODY_TOO_LARGE\n ) {\n try {\n const maskedBody = this.config.maskRequestBodyCallback(item.request);\n item.request.body = maskedBody ?? BODY_MASKED;\n } catch {\n item.request.body = undefined;\n }\n }\n\n // Apply user-provided maskResponseBodyCallback function\n if (\n this.config.maskResponseBodyCallback &&\n item.response.body &&\n item.response.body !== BODY_TOO_LARGE\n ) {\n try {\n const maskedBody = this.config.maskResponseBodyCallback(\n item.request,\n item.response,\n );\n item.response.body = maskedBody ?? BODY_MASKED;\n } catch {\n item.response.body = undefined;\n }\n }\n\n // Check request and response body sizes\n if (item.request.body && item.request.body.length > MAX_BODY_SIZE) {\n item.request.body = BODY_TOO_LARGE;\n }\n if (item.response.body && item.response.body.length > MAX_BODY_SIZE) {\n item.response.body = BODY_TOO_LARGE;\n }\n\n // Mask request and response body fields\n for (const key of [\"request\", \"response\"] as const) {\n const bodyData = item[key].body;\n if (\n !bodyData ||\n bodyData === BODY_TOO_LARGE ||\n bodyData === BODY_MASKED\n ) {\n continue;\n }\n\n try {\n const contentType = this.getContentType(item[key].headers);\n if (!contentType || /\\bjson\\b/i.test(contentType)) {\n const parsedBody = JSON.parse(bodyData.toString());\n const maskedBody = this.maskBody(parsedBody);\n item[key].body = Buffer.from(JSON.stringify(maskedBody));\n } else if (/\\bndjson\\b/i.test(contentType)) {\n const lines = bodyData\n .toString()\n .split(\"\\n\")\n .filter((line) => line.trim());\n const maskedLines = lines.map((line) => {\n try {\n const parsed = JSON.parse(line);\n const masked = this.maskBody(parsed);\n return JSON.stringify(masked);\n } catch {\n return line; // Keep unparseable lines as is\n }\n });\n item[key].body = Buffer.from(maskedLines.join(\"\\n\"));\n }\n } catch {\n // If parsing fails, leave body as is\n }\n }\n\n // Mask request and response headers\n item.request.headers = this.config.logRequestHeaders\n ? this.maskHeaders(item.request.headers)\n : [];\n item.response.headers = this.config.logResponseHeaders\n ? this.maskHeaders(item.response.headers)\n : [];\n\n // Mask query params\n const url = new URL(item.request.url);\n url.search = this.config.logQueryParams\n ? this.maskQueryParams(url.search)\n : \"\";\n item.request.url = url.toString();\n\n return item;\n }\n\n logRequest(\n request: Request,\n response: Response,\n error?: Error,\n logs?: LogRecord[],\n spans?: SpanData[],\n traceId?: string,\n ) {\n if (!this.enabled || this.suspendUntil !== null) return;\n\n const url = new URL(request.url);\n const path = request.path ?? url.pathname;\n const userAgent = request.headers.find(\n ([k]) => k.toLowerCase() === \"user-agent\",\n )?.[1];\n\n if (url.protocol === \"http:\" && RequestLogger.isHttps(request.headers)) {\n url.protocol = \"https:\";\n request.url = url.toString();\n }\n\n if (\n this.shouldExcludePath(path) ||\n this.shouldExcludeUserAgent(userAgent) ||\n (this.config.excludeCallback?.(request, response) ?? false)\n ) {\n return;\n }\n\n if (\n !this.config.logRequestBody ||\n !this.hasSupportedContentType(request.headers)\n ) {\n request.body = undefined;\n }\n if (\n !this.config.logResponseBody ||\n !this.hasSupportedContentType(response.headers)\n ) {\n response.body = undefined;\n }\n\n if (request.size !== undefined && request.size < 0) {\n request.size = undefined;\n }\n if (response.size !== undefined && response.size < 0) {\n response.size = undefined;\n }\n\n const item: RequestLogItem = {\n uuid: randomUUID(),\n request: request,\n response: response,\n exception:\n error && this.config.logException\n ? {\n type: error.name,\n message: truncateExceptionMessage(error.message),\n stacktrace: truncateExceptionStackTrace(error.stack || \"\"),\n sentryEventId: getSentryEventId(),\n }\n : undefined,\n };\n\n if (logs && logs.length > 0) {\n item.logs = logs.map((log) => ({\n timestamp: log.timestamp,\n logger: log.logger,\n level: log.level,\n message: truncateLogMessage(log.message),\n }));\n }\n if (spans && spans.length > 0) {\n item.spans = spans;\n }\n if (traceId) {\n item.traceId = traceId;\n }\n this.pendingWrites.push(item);\n\n if (this.pendingWrites.length > MAX_PENDING_WRITES) {\n this.pendingWrites.shift();\n }\n }\n\n async writeToFile() {\n if (!this.enabled || this.pendingWrites.length === 0) {\n return;\n }\n return this.lock.acquire(\"file\", async () => {\n if (!this.currentFile) {\n this.currentFile = new TempGzipFile(\"request_logs\");\n }\n while (this.pendingWrites.length > 0) {\n let item = this.pendingWrites.shift();\n if (item) {\n item = this.applyMasking(item);\n\n const finalItem = {\n uuid: item.uuid,\n request: skipEmptyValues(item.request),\n response: skipEmptyValues(item.response),\n exception: item.exception,\n logs: item.logs,\n spans: item.spans,\n traceId: item.traceId,\n };\n\n // Set up body serialization for JSON\n [finalItem.request.body, finalItem.response.body].forEach((body) => {\n if (body) {\n // @ts-expect-error Override Buffer's default JSON serialization\n body.toJSON = function () {\n return this.toString(\"base64\");\n };\n }\n });\n\n await this.currentFile.writeLine(\n Buffer.from(JSON.stringify(finalItem)),\n );\n }\n }\n });\n }\n\n getFile() {\n return this.files.shift();\n }\n\n retryFileLater(file: TempGzipFile) {\n this.files.unshift(file);\n }\n\n async rotateFile() {\n return this.lock.acquire(\"file\", async () => {\n if (this.currentFile) {\n await this.currentFile.close();\n this.files.push(this.currentFile);\n this.currentFile = null;\n }\n });\n }\n\n async maintain() {\n await this.writeToFile();\n if (this.currentFile && this.currentFile.size > MAX_FILE_SIZE) {\n await this.rotateFile();\n }\n while (this.files.length > MAX_FILES) {\n const file = this.files.shift();\n file?.delete();\n }\n if (this.suspendUntil !== null && this.suspendUntil < Date.now()) {\n this.suspendUntil = null;\n }\n }\n\n async clear() {\n this.pendingWrites = [];\n await this.rotateFile();\n this.files.forEach((file) => {\n file.delete();\n });\n this.files = [];\n }\n\n async close() {\n this.enabled = false;\n await this.clear();\n if (this.maintainIntervalId) {\n clearInterval(this.maintainIntervalId);\n }\n }\n}\n\nexport function convertHeaders(\n headers:\n | Headers\n | IncomingHttpHeaders\n | OutgoingHttpHeaders\n | Record<string, string | string[] | number | undefined>\n | undefined,\n) {\n if (!headers) {\n return [];\n }\n if (headers instanceof Headers) {\n return Array.from(headers.entries());\n }\n return Object.entries(headers).flatMap(([key, value]) => {\n if (value === undefined) {\n return [];\n }\n if (Array.isArray(value)) {\n return value.map((v) => [key, v]);\n }\n return [[key, value.toString()]];\n }) as [string, string][];\n}\n\nexport function convertBody(body: any, contentType?: string | null) {\n if (!body || !contentType) {\n return;\n }\n try {\n if (contentType.startsWith(\"application/json\")) {\n if (isValidJsonString(body)) {\n return Buffer.from(body);\n } else {\n return Buffer.from(JSON.stringify(body));\n }\n }\n if (contentType.startsWith(\"text/\") && typeof body === \"string\") {\n return Buffer.from(body);\n }\n } catch (error) {\n return;\n }\n}\n\nfunction isValidJsonString(body: any) {\n if (typeof body !== \"string\") {\n return false;\n }\n try {\n JSON.parse(body);\n return true;\n } catch {\n return false;\n }\n}\n\nfunction matchPatterns(value: string, patterns: RegExp[]) {\n return patterns.some((pattern) => {\n return pattern.test(value);\n });\n}\n\nfunction skipEmptyValues<T extends Record<string, any>>(data: T) {\n return Object.fromEntries(\n Object.entries(data).filter(([_, v]) => {\n if (v == null || Number.isNaN(v)) return false;\n if (Array.isArray(v) || Buffer.isBuffer(v) || typeof v === \"string\") {\n return v.length > 0;\n }\n return true;\n }),\n ) as Partial<T>;\n}\n\nfunction truncateLogMessage(msg: string) {\n if (msg.length > MAX_LOG_MSG_LENGTH) {\n const suffix = \"... (truncated)\";\n return msg.slice(0, MAX_LOG_MSG_LENGTH - suffix.length) + suffix;\n }\n return msg;\n}\n"],"mappings":"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","names":["MAX_BODY_SIZE","MAX_FILE_SIZE","MAX_FILES","MAX_PENDING_WRITES","MAX_LOG_MSG_LENGTH","BODY_TOO_LARGE","Buffer","from","BODY_MASKED","MASKED","ALLOWED_CONTENT_TYPES","EXCLUDE_PATH_PATTERNS","EXCLUDE_USER_AGENT_PATTERNS","MASK_QUERY_PARAM_PATTERNS","MASK_HEADER_PATTERNS","MASK_BODY_FIELD_PATTERNS","DEFAULT_CONFIG","enabled","logQueryParams","logRequestHeaders","logRequestBody","logResponseHeaders","logResponseBody","logException","captureLogs","captureTraces","maskQueryParams","maskHeaders","maskBodyFields","excludePaths","RequestLogger","config","suspendUntil","pendingWrites","currentFile","files","maintainIntervalId","lock","AsyncLock","checkWritableFs","setInterval","maintain","maxBodySize","shouldExcludePath","urlPath","patterns","matchPatterns","shouldExcludeUserAgent","userAgent","shouldMaskQueryParam","name","shouldMaskHeader","shouldMaskBodyField","getContentType","headers","find","k","toLowerCase","hasSupportedContentType","contentType","isSupportedContentType","Array","isArray","length","some","t","startsWith","isHttps","v","split","trim","includes","search","params","URLSearchParams","key","set","toString","map","maskBody","data","result","value","Object","entries","item","applyMasking","maskRequestBodyCallback","request","body","maskedBody","undefined","maskResponseBodyCallback","response","bodyData","test","parsedBody","JSON","parse","stringify","lines","filter","line","maskedLines","parsed","masked","join","url","URL","logRequest","error","logs","spans","traceId","path","pathname","protocol","excludeCallback","size","uuid","randomUUID","exception","type","message","truncateExceptionMessage","stacktrace","truncateExceptionStackTrace","stack","sentryEventId","getSentryEventId","log","timestamp","logger","level","truncateLogMessage","push","shift","writeToFile","acquire","TempGzipFile","finalItem","skipEmptyValues","forEach","toJSON","writeLine","getFile","retryFileLater","file","unshift","rotateFile","close","delete","Date","now","clear","clearInterval","convertHeaders","Headers","flatMap","convertBody","isValidJsonString","pattern","fromEntries","_","Number","isNaN","isBuffer","msg","suffix","slice"]}
@@ -64,6 +64,7 @@ declare class RequestLogger {
64
64
  private getContentType;
65
65
  private hasSupportedContentType;
66
66
  isSupportedContentType(contentType?: string | string[] | null): boolean;
67
+ private static isHttps;
67
68
  private maskQueryParams;
68
69
  private maskHeaders;
69
70
  private maskBody;
@@ -64,6 +64,7 @@ declare class RequestLogger {
64
64
  private getContentType;
65
65
  private hasSupportedContentType;
66
66
  isSupportedContentType(contentType?: string | string[] | null): boolean;
67
+ private static isHttps;
67
68
  private maskQueryParams;
68
69
  private maskHeaders;
69
70
  private maskBody;
@@ -155,6 +155,25 @@ const _RequestLogger = class _RequestLogger {
155
155
  }
156
156
  return typeof contentType === "string" && ALLOWED_CONTENT_TYPES.some((t) => contentType.startsWith(t));
157
157
  }
158
+ static isHttps(headers) {
159
+ return headers.some(([k, v]) => {
160
+ switch (k.toLowerCase()) {
161
+ case "x-forwarded-proto":
162
+ case "x-forwarded-protocol":
163
+ case "x-forwarded-scheme":
164
+ case "x-url-scheme":
165
+ case "x-scheme":
166
+ return v.split(",")[0].trim().toLowerCase() === "https";
167
+ case "front-end-https":
168
+ case "x-forwarded-ssl":
169
+ return v.toLowerCase() === "on";
170
+ case "forwarded":
171
+ return v.split(",")[0].trim().toLowerCase().includes("proto=https");
172
+ default:
173
+ return false;
174
+ }
175
+ });
176
+ }
158
177
  maskQueryParams(search) {
159
178
  const params = new URLSearchParams(search);
160
179
  for (const [key] of params) {
@@ -253,6 +272,10 @@ const _RequestLogger = class _RequestLogger {
253
272
  const url = new URL(request.url);
254
273
  const path = request.path ?? url.pathname;
255
274
  const userAgent = (_a = request.headers.find(([k]) => k.toLowerCase() === "user-agent")) == null ? void 0 : _a[1];
275
+ if (url.protocol === "http:" && _RequestLogger.isHttps(request.headers)) {
276
+ url.protocol = "https:";
277
+ request.url = url.toString();
278
+ }
256
279
  if (this.shouldExcludePath(path) || this.shouldExcludeUserAgent(userAgent) || (((_c = (_b = this.config).excludeCallback) == null ? void 0 : _c.call(_b, request, response)) ?? false)) {
257
280
  return;
258
281
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/common/requestLogger.ts"],"sourcesContent":["import AsyncLock from \"async-lock\";\nimport { Buffer } from \"node:buffer\";\nimport { randomUUID } from \"node:crypto\";\nimport type { IncomingHttpHeaders, OutgoingHttpHeaders } from \"node:http\";\n\nimport { getSentryEventId } from \"./sentry.js\";\nimport {\n truncateExceptionMessage,\n truncateExceptionStackTrace,\n} from \"./serverErrorCounter.js\";\nimport type { SpanData } from \"./spanCollector.js\";\nimport TempGzipFile, { checkWritableFs } from \"./tempGzipFile.js\";\n\nconst MAX_BODY_SIZE = 50_000; // 50 KB (uncompressed)\nconst MAX_FILE_SIZE = 1_000_000; // 1 MB (compressed)\nconst MAX_FILES = 50;\nconst MAX_PENDING_WRITES = 100;\nconst MAX_LOG_MSG_LENGTH = 2048;\nconst BODY_TOO_LARGE = Buffer.from(\"<body too large>\");\nconst BODY_MASKED = Buffer.from(\"<masked>\");\nconst MASKED = \"******\";\nconst ALLOWED_CONTENT_TYPES = [\n \"application/json\",\n \"application/ld+json\",\n \"application/problem+json\",\n \"application/vnd.api+json\",\n \"application/x-ndjson\",\n \"text/plain\",\n \"text/html\",\n];\nconst EXCLUDE_PATH_PATTERNS = [\n /\\/_?healthz?$/i,\n /\\/_?health[_-]?checks?$/i,\n /\\/_?heart[_-]?beats?$/i,\n /\\/ping$/i,\n /\\/ready$/i,\n /\\/live$/i,\n /\\/favicon(?:-[\\w-]+)?\\.(ico|png|svg)$/,\n /\\/apple-touch-icon(?:-[\\w-]+)?\\.png$/,\n /\\/robots\\.txt$/,\n /\\/sitemap\\.xml$/,\n /\\/manifest\\.json$/,\n /\\/site\\.webmanifest$/,\n /\\/service-worker\\.js$/,\n /\\/sw\\.js$/,\n /\\/\\.well-known\\//,\n];\nconst EXCLUDE_USER_AGENT_PATTERNS = [\n /health[-_ ]?check/i,\n /microsoft-azure-application-lb/i,\n /googlehc/i,\n /kube-probe/i,\n];\nconst MASK_QUERY_PARAM_PATTERNS = [\n /auth/i,\n /api-?key/i,\n /secret/i,\n /token/i,\n /password/i,\n /pwd/i,\n];\nconst MASK_HEADER_PATTERNS = [\n /auth/i,\n /api-?key/i,\n /secret/i,\n /token/i,\n /cookie/i,\n];\nconst MASK_BODY_FIELD_PATTERNS = [\n /password/i,\n /pwd/i,\n /token/i,\n /secret/i,\n /auth/i,\n /card[-_ ]?number/i,\n /ccv/i,\n /ssn/i,\n];\n\nexport type Request = {\n timestamp: number;\n method: string;\n path?: string;\n url: string;\n headers: [string, string][];\n size?: number;\n consumer?: string;\n body?: Buffer;\n};\n\nexport type Response = {\n statusCode: number;\n responseTime: number;\n headers: [string, string][];\n size?: number;\n body?: Buffer;\n};\n\nexport type LogRecord = {\n timestamp: number;\n logger?: string;\n level: string;\n message: string;\n};\n\nexport type RequestLoggingConfig = {\n enabled: boolean;\n logQueryParams: boolean;\n logRequestHeaders: boolean;\n logRequestBody: boolean;\n logResponseHeaders: boolean;\n logResponseBody: boolean;\n logException: boolean;\n captureLogs: boolean;\n captureTraces: boolean;\n maskQueryParams: RegExp[];\n maskHeaders: RegExp[];\n maskBodyFields: RegExp[];\n maskRequestBodyCallback?: (request: Request) => Buffer | null | undefined;\n maskResponseBodyCallback?: (\n request: Request,\n response: Response,\n ) => Buffer | null | undefined;\n excludePaths: RegExp[];\n excludeCallback?: (request: Request, response: Response) => boolean;\n};\n\nconst DEFAULT_CONFIG: RequestLoggingConfig = {\n enabled: false,\n logQueryParams: true,\n logRequestHeaders: false,\n logRequestBody: false,\n logResponseHeaders: true,\n logResponseBody: false,\n logException: true,\n captureLogs: false,\n captureTraces: false,\n maskQueryParams: [],\n maskHeaders: [],\n maskBodyFields: [],\n excludePaths: [],\n};\n\ntype RequestLogItem = {\n uuid: string;\n request: Request;\n response: Response;\n exception?: {\n type: string;\n message: string;\n stacktrace: string;\n sentryEventId?: string;\n };\n logs?: LogRecord[];\n spans?: SpanData[];\n traceId?: string;\n};\n\nexport default class RequestLogger {\n public config: RequestLoggingConfig;\n public enabled: boolean;\n public suspendUntil: number | null = null;\n private pendingWrites: RequestLogItem[] = [];\n private currentFile: TempGzipFile | null = null;\n private files: TempGzipFile[] = [];\n private maintainIntervalId?: NodeJS.Timeout;\n private lock = new AsyncLock();\n\n constructor(config?: Partial<RequestLoggingConfig>) {\n this.config = { ...DEFAULT_CONFIG, ...config };\n this.enabled = this.config.enabled && checkWritableFs();\n\n if (this.enabled) {\n this.maintainIntervalId = setInterval(() => {\n this.maintain();\n }, 1000);\n }\n }\n\n get maxBodySize() {\n return MAX_BODY_SIZE;\n }\n\n private shouldExcludePath(urlPath: string) {\n const patterns = [...this.config.excludePaths, ...EXCLUDE_PATH_PATTERNS];\n return matchPatterns(urlPath, patterns);\n }\n\n private shouldExcludeUserAgent(userAgent?: string) {\n return userAgent\n ? matchPatterns(userAgent, EXCLUDE_USER_AGENT_PATTERNS)\n : false;\n }\n\n private shouldMaskQueryParam(name: string) {\n const patterns = [\n ...this.config.maskQueryParams,\n ...MASK_QUERY_PARAM_PATTERNS,\n ];\n return matchPatterns(name, patterns);\n }\n\n private shouldMaskHeader(name: string) {\n const patterns = [...this.config.maskHeaders, ...MASK_HEADER_PATTERNS];\n return matchPatterns(name, patterns);\n }\n\n private shouldMaskBodyField(name: string) {\n const patterns = [\n ...this.config.maskBodyFields,\n ...MASK_BODY_FIELD_PATTERNS,\n ];\n return matchPatterns(name, patterns);\n }\n\n private getContentType(headers: [string, string][]) {\n return headers.find(([k]) => k.toLowerCase() === \"content-type\")?.[1];\n }\n\n private hasSupportedContentType(headers: [string, string][]) {\n const contentType = this.getContentType(headers);\n return this.isSupportedContentType(contentType);\n }\n\n public isSupportedContentType(contentType?: string | string[] | null) {\n if (Array.isArray(contentType) && contentType.length > 0) {\n contentType = contentType[0];\n }\n return (\n typeof contentType === \"string\" &&\n ALLOWED_CONTENT_TYPES.some((t) => contentType.startsWith(t))\n );\n }\n\n private maskQueryParams(search: string) {\n const params = new URLSearchParams(search);\n for (const [key] of params) {\n if (this.shouldMaskQueryParam(key)) {\n params.set(key, MASKED);\n }\n }\n return params.toString();\n }\n\n private maskHeaders(headers: [string, string][]): [string, string][] {\n return headers.map(([k, v]) => [k, this.shouldMaskHeader(k) ? MASKED : v]);\n }\n\n private maskBody(data: any): any {\n if (typeof data === \"object\" && data !== null && !Array.isArray(data)) {\n const result: any = {};\n for (const [key, value] of Object.entries(data)) {\n if (typeof value === \"string\" && this.shouldMaskBodyField(key)) {\n result[key] = MASKED;\n } else {\n result[key] = this.maskBody(value);\n }\n }\n return result;\n }\n if (Array.isArray(data)) {\n return data.map((item) => this.maskBody(item));\n }\n return data;\n }\n\n private applyMasking(item: RequestLogItem) {\n // Apply user-provided maskRequestBodyCallback function\n if (\n this.config.maskRequestBodyCallback &&\n item.request.body &&\n item.request.body !== BODY_TOO_LARGE\n ) {\n try {\n const maskedBody = this.config.maskRequestBodyCallback(item.request);\n item.request.body = maskedBody ?? BODY_MASKED;\n } catch {\n item.request.body = undefined;\n }\n }\n\n // Apply user-provided maskResponseBodyCallback function\n if (\n this.config.maskResponseBodyCallback &&\n item.response.body &&\n item.response.body !== BODY_TOO_LARGE\n ) {\n try {\n const maskedBody = this.config.maskResponseBodyCallback(\n item.request,\n item.response,\n );\n item.response.body = maskedBody ?? BODY_MASKED;\n } catch {\n item.response.body = undefined;\n }\n }\n\n // Check request and response body sizes\n if (item.request.body && item.request.body.length > MAX_BODY_SIZE) {\n item.request.body = BODY_TOO_LARGE;\n }\n if (item.response.body && item.response.body.length > MAX_BODY_SIZE) {\n item.response.body = BODY_TOO_LARGE;\n }\n\n // Mask request and response body fields\n for (const key of [\"request\", \"response\"] as const) {\n const bodyData = item[key].body;\n if (\n !bodyData ||\n bodyData === BODY_TOO_LARGE ||\n bodyData === BODY_MASKED\n ) {\n continue;\n }\n\n try {\n const contentType = this.getContentType(item[key].headers);\n if (!contentType || /\\bjson\\b/i.test(contentType)) {\n const parsedBody = JSON.parse(bodyData.toString());\n const maskedBody = this.maskBody(parsedBody);\n item[key].body = Buffer.from(JSON.stringify(maskedBody));\n } else if (/\\bndjson\\b/i.test(contentType)) {\n const lines = bodyData\n .toString()\n .split(\"\\n\")\n .filter((line) => line.trim());\n const maskedLines = lines.map((line) => {\n try {\n const parsed = JSON.parse(line);\n const masked = this.maskBody(parsed);\n return JSON.stringify(masked);\n } catch {\n return line; // Keep unparseable lines as is\n }\n });\n item[key].body = Buffer.from(maskedLines.join(\"\\n\"));\n }\n } catch {\n // If parsing fails, leave body as is\n }\n }\n\n // Mask request and response headers\n item.request.headers = this.config.logRequestHeaders\n ? this.maskHeaders(item.request.headers)\n : [];\n item.response.headers = this.config.logResponseHeaders\n ? this.maskHeaders(item.response.headers)\n : [];\n\n // Mask query params\n const url = new URL(item.request.url);\n url.search = this.config.logQueryParams\n ? this.maskQueryParams(url.search)\n : \"\";\n item.request.url = url.toString();\n\n return item;\n }\n\n logRequest(\n request: Request,\n response: Response,\n error?: Error,\n logs?: LogRecord[],\n spans?: SpanData[],\n traceId?: string,\n ) {\n if (!this.enabled || this.suspendUntil !== null) return;\n\n const url = new URL(request.url);\n const path = request.path ?? url.pathname;\n const userAgent = request.headers.find(\n ([k]) => k.toLowerCase() === \"user-agent\",\n )?.[1];\n\n if (\n this.shouldExcludePath(path) ||\n this.shouldExcludeUserAgent(userAgent) ||\n (this.config.excludeCallback?.(request, response) ?? false)\n ) {\n return;\n }\n\n if (\n !this.config.logRequestBody ||\n !this.hasSupportedContentType(request.headers)\n ) {\n request.body = undefined;\n }\n if (\n !this.config.logResponseBody ||\n !this.hasSupportedContentType(response.headers)\n ) {\n response.body = undefined;\n }\n\n if (request.size !== undefined && request.size < 0) {\n request.size = undefined;\n }\n if (response.size !== undefined && response.size < 0) {\n response.size = undefined;\n }\n\n const item: RequestLogItem = {\n uuid: randomUUID(),\n request: request,\n response: response,\n exception:\n error && this.config.logException\n ? {\n type: error.name,\n message: truncateExceptionMessage(error.message),\n stacktrace: truncateExceptionStackTrace(error.stack || \"\"),\n sentryEventId: getSentryEventId(),\n }\n : undefined,\n };\n\n if (logs && logs.length > 0) {\n item.logs = logs.map((log) => ({\n timestamp: log.timestamp,\n logger: log.logger,\n level: log.level,\n message: truncateLogMessage(log.message),\n }));\n }\n if (spans && spans.length > 0) {\n item.spans = spans;\n }\n if (traceId) {\n item.traceId = traceId;\n }\n this.pendingWrites.push(item);\n\n if (this.pendingWrites.length > MAX_PENDING_WRITES) {\n this.pendingWrites.shift();\n }\n }\n\n async writeToFile() {\n if (!this.enabled || this.pendingWrites.length === 0) {\n return;\n }\n return this.lock.acquire(\"file\", async () => {\n if (!this.currentFile) {\n this.currentFile = new TempGzipFile(\"request_logs\");\n }\n while (this.pendingWrites.length > 0) {\n let item = this.pendingWrites.shift();\n if (item) {\n item = this.applyMasking(item);\n\n const finalItem = {\n uuid: item.uuid,\n request: skipEmptyValues(item.request),\n response: skipEmptyValues(item.response),\n exception: item.exception,\n logs: item.logs,\n spans: item.spans,\n traceId: item.traceId,\n };\n\n // Set up body serialization for JSON\n [finalItem.request.body, finalItem.response.body].forEach((body) => {\n if (body) {\n // @ts-expect-error Override Buffer's default JSON serialization\n body.toJSON = function () {\n return this.toString(\"base64\");\n };\n }\n });\n\n await this.currentFile.writeLine(\n Buffer.from(JSON.stringify(finalItem)),\n );\n }\n }\n });\n }\n\n getFile() {\n return this.files.shift();\n }\n\n retryFileLater(file: TempGzipFile) {\n this.files.unshift(file);\n }\n\n async rotateFile() {\n return this.lock.acquire(\"file\", async () => {\n if (this.currentFile) {\n await this.currentFile.close();\n this.files.push(this.currentFile);\n this.currentFile = null;\n }\n });\n }\n\n async maintain() {\n await this.writeToFile();\n if (this.currentFile && this.currentFile.size > MAX_FILE_SIZE) {\n await this.rotateFile();\n }\n while (this.files.length > MAX_FILES) {\n const file = this.files.shift();\n file?.delete();\n }\n if (this.suspendUntil !== null && this.suspendUntil < Date.now()) {\n this.suspendUntil = null;\n }\n }\n\n async clear() {\n this.pendingWrites = [];\n await this.rotateFile();\n this.files.forEach((file) => {\n file.delete();\n });\n this.files = [];\n }\n\n async close() {\n this.enabled = false;\n await this.clear();\n if (this.maintainIntervalId) {\n clearInterval(this.maintainIntervalId);\n }\n }\n}\n\nexport function convertHeaders(\n headers:\n | Headers\n | IncomingHttpHeaders\n | OutgoingHttpHeaders\n | Record<string, string | string[] | number | undefined>\n | undefined,\n) {\n if (!headers) {\n return [];\n }\n if (headers instanceof Headers) {\n return Array.from(headers.entries());\n }\n return Object.entries(headers).flatMap(([key, value]) => {\n if (value === undefined) {\n return [];\n }\n if (Array.isArray(value)) {\n return value.map((v) => [key, v]);\n }\n return [[key, value.toString()]];\n }) as [string, string][];\n}\n\nexport function convertBody(body: any, contentType?: string | null) {\n if (!body || !contentType) {\n return;\n }\n try {\n if (contentType.startsWith(\"application/json\")) {\n if (isValidJsonString(body)) {\n return Buffer.from(body);\n } else {\n return Buffer.from(JSON.stringify(body));\n }\n }\n if (contentType.startsWith(\"text/\") && typeof body === \"string\") {\n return Buffer.from(body);\n }\n } catch (error) {\n return;\n }\n}\n\nfunction isValidJsonString(body: any) {\n if (typeof body !== \"string\") {\n return false;\n }\n try {\n JSON.parse(body);\n return true;\n } catch {\n return false;\n }\n}\n\nfunction matchPatterns(value: string, patterns: RegExp[]) {\n return patterns.some((pattern) => {\n return pattern.test(value);\n });\n}\n\nfunction skipEmptyValues<T extends Record<string, any>>(data: T) {\n return Object.fromEntries(\n Object.entries(data).filter(([_, v]) => {\n if (v == null || Number.isNaN(v)) return false;\n if (Array.isArray(v) || Buffer.isBuffer(v) || typeof v === \"string\") {\n return v.length > 0;\n }\n return true;\n }),\n ) as Partial<T>;\n}\n\nfunction truncateLogMessage(msg: string) {\n if (msg.length > MAX_LOG_MSG_LENGTH) {\n const suffix = \"... (truncated)\";\n return msg.slice(0, MAX_LOG_MSG_LENGTH - suffix.length) + suffix;\n }\n return msg;\n}\n"],"mappings":"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","names":["AsyncLock","Buffer","randomUUID","getSentryEventId","truncateExceptionMessage","truncateExceptionStackTrace","TempGzipFile","checkWritableFs","MAX_BODY_SIZE","MAX_FILE_SIZE","MAX_FILES","MAX_PENDING_WRITES","MAX_LOG_MSG_LENGTH","BODY_TOO_LARGE","from","BODY_MASKED","MASKED","ALLOWED_CONTENT_TYPES","EXCLUDE_PATH_PATTERNS","EXCLUDE_USER_AGENT_PATTERNS","MASK_QUERY_PARAM_PATTERNS","MASK_HEADER_PATTERNS","MASK_BODY_FIELD_PATTERNS","DEFAULT_CONFIG","enabled","logQueryParams","logRequestHeaders","logRequestBody","logResponseHeaders","logResponseBody","logException","captureLogs","captureTraces","maskQueryParams","maskHeaders","maskBodyFields","excludePaths","RequestLogger","config","suspendUntil","pendingWrites","currentFile","files","maintainIntervalId","lock","setInterval","maintain","maxBodySize","shouldExcludePath","urlPath","patterns","matchPatterns","shouldExcludeUserAgent","userAgent","shouldMaskQueryParam","name","shouldMaskHeader","shouldMaskBodyField","getContentType","headers","find","k","toLowerCase","hasSupportedContentType","contentType","isSupportedContentType","Array","isArray","length","some","t","startsWith","search","params","URLSearchParams","key","set","toString","map","v","maskBody","data","result","value","Object","entries","item","applyMasking","maskRequestBodyCallback","request","body","maskedBody","undefined","maskResponseBodyCallback","response","bodyData","test","parsedBody","JSON","parse","stringify","lines","split","filter","line","trim","maskedLines","parsed","masked","join","url","URL","logRequest","error","logs","spans","traceId","path","pathname","excludeCallback","size","uuid","exception","type","message","stacktrace","stack","sentryEventId","log","timestamp","logger","level","truncateLogMessage","push","shift","writeToFile","acquire","finalItem","skipEmptyValues","forEach","toJSON","writeLine","getFile","retryFileLater","file","unshift","rotateFile","close","delete","Date","now","clear","clearInterval","convertHeaders","Headers","flatMap","convertBody","isValidJsonString","pattern","fromEntries","_","Number","isNaN","isBuffer","msg","suffix","slice"]}
1
+ {"version":3,"sources":["../../src/common/requestLogger.ts"],"sourcesContent":["import AsyncLock from \"async-lock\";\nimport { Buffer } from \"node:buffer\";\nimport { randomUUID } from \"node:crypto\";\nimport type { IncomingHttpHeaders, OutgoingHttpHeaders } from \"node:http\";\n\nimport { getSentryEventId } from \"./sentry.js\";\nimport {\n truncateExceptionMessage,\n truncateExceptionStackTrace,\n} from \"./serverErrorCounter.js\";\nimport type { SpanData } from \"./spanCollector.js\";\nimport TempGzipFile, { checkWritableFs } from \"./tempGzipFile.js\";\n\nconst MAX_BODY_SIZE = 50_000; // 50 KB (uncompressed)\nconst MAX_FILE_SIZE = 1_000_000; // 1 MB (compressed)\nconst MAX_FILES = 50;\nconst MAX_PENDING_WRITES = 100;\nconst MAX_LOG_MSG_LENGTH = 2048;\nconst BODY_TOO_LARGE = Buffer.from(\"<body too large>\");\nconst BODY_MASKED = Buffer.from(\"<masked>\");\nconst MASKED = \"******\";\nconst ALLOWED_CONTENT_TYPES = [\n \"application/json\",\n \"application/ld+json\",\n \"application/problem+json\",\n \"application/vnd.api+json\",\n \"application/x-ndjson\",\n \"text/plain\",\n \"text/html\",\n];\nconst EXCLUDE_PATH_PATTERNS = [\n /\\/_?healthz?$/i,\n /\\/_?health[_-]?checks?$/i,\n /\\/_?heart[_-]?beats?$/i,\n /\\/ping$/i,\n /\\/ready$/i,\n /\\/live$/i,\n /\\/favicon(?:-[\\w-]+)?\\.(ico|png|svg)$/,\n /\\/apple-touch-icon(?:-[\\w-]+)?\\.png$/,\n /\\/robots\\.txt$/,\n /\\/sitemap\\.xml$/,\n /\\/manifest\\.json$/,\n /\\/site\\.webmanifest$/,\n /\\/service-worker\\.js$/,\n /\\/sw\\.js$/,\n /\\/\\.well-known\\//,\n];\nconst EXCLUDE_USER_AGENT_PATTERNS = [\n /health[-_ ]?check/i,\n /microsoft-azure-application-lb/i,\n /googlehc/i,\n /kube-probe/i,\n];\nconst MASK_QUERY_PARAM_PATTERNS = [\n /auth/i,\n /api-?key/i,\n /secret/i,\n /token/i,\n /password/i,\n /pwd/i,\n];\nconst MASK_HEADER_PATTERNS = [\n /auth/i,\n /api-?key/i,\n /secret/i,\n /token/i,\n /cookie/i,\n];\nconst MASK_BODY_FIELD_PATTERNS = [\n /password/i,\n /pwd/i,\n /token/i,\n /secret/i,\n /auth/i,\n /card[-_ ]?number/i,\n /ccv/i,\n /ssn/i,\n];\n\nexport type Request = {\n timestamp: number;\n method: string;\n path?: string;\n url: string;\n headers: [string, string][];\n size?: number;\n consumer?: string;\n body?: Buffer;\n};\n\nexport type Response = {\n statusCode: number;\n responseTime: number;\n headers: [string, string][];\n size?: number;\n body?: Buffer;\n};\n\nexport type LogRecord = {\n timestamp: number;\n logger?: string;\n level: string;\n message: string;\n};\n\nexport type RequestLoggingConfig = {\n enabled: boolean;\n logQueryParams: boolean;\n logRequestHeaders: boolean;\n logRequestBody: boolean;\n logResponseHeaders: boolean;\n logResponseBody: boolean;\n logException: boolean;\n captureLogs: boolean;\n captureTraces: boolean;\n maskQueryParams: RegExp[];\n maskHeaders: RegExp[];\n maskBodyFields: RegExp[];\n maskRequestBodyCallback?: (request: Request) => Buffer | null | undefined;\n maskResponseBodyCallback?: (\n request: Request,\n response: Response,\n ) => Buffer | null | undefined;\n excludePaths: RegExp[];\n excludeCallback?: (request: Request, response: Response) => boolean;\n};\n\nconst DEFAULT_CONFIG: RequestLoggingConfig = {\n enabled: false,\n logQueryParams: true,\n logRequestHeaders: false,\n logRequestBody: false,\n logResponseHeaders: true,\n logResponseBody: false,\n logException: true,\n captureLogs: false,\n captureTraces: false,\n maskQueryParams: [],\n maskHeaders: [],\n maskBodyFields: [],\n excludePaths: [],\n};\n\ntype RequestLogItem = {\n uuid: string;\n request: Request;\n response: Response;\n exception?: {\n type: string;\n message: string;\n stacktrace: string;\n sentryEventId?: string;\n };\n logs?: LogRecord[];\n spans?: SpanData[];\n traceId?: string;\n};\n\nexport default class RequestLogger {\n public config: RequestLoggingConfig;\n public enabled: boolean;\n public suspendUntil: number | null = null;\n private pendingWrites: RequestLogItem[] = [];\n private currentFile: TempGzipFile | null = null;\n private files: TempGzipFile[] = [];\n private maintainIntervalId?: NodeJS.Timeout;\n private lock = new AsyncLock();\n\n constructor(config?: Partial<RequestLoggingConfig>) {\n this.config = { ...DEFAULT_CONFIG, ...config };\n this.enabled = this.config.enabled && checkWritableFs();\n\n if (this.enabled) {\n this.maintainIntervalId = setInterval(() => {\n this.maintain();\n }, 1000);\n }\n }\n\n get maxBodySize() {\n return MAX_BODY_SIZE;\n }\n\n private shouldExcludePath(urlPath: string) {\n const patterns = [...this.config.excludePaths, ...EXCLUDE_PATH_PATTERNS];\n return matchPatterns(urlPath, patterns);\n }\n\n private shouldExcludeUserAgent(userAgent?: string) {\n return userAgent\n ? matchPatterns(userAgent, EXCLUDE_USER_AGENT_PATTERNS)\n : false;\n }\n\n private shouldMaskQueryParam(name: string) {\n const patterns = [\n ...this.config.maskQueryParams,\n ...MASK_QUERY_PARAM_PATTERNS,\n ];\n return matchPatterns(name, patterns);\n }\n\n private shouldMaskHeader(name: string) {\n const patterns = [...this.config.maskHeaders, ...MASK_HEADER_PATTERNS];\n return matchPatterns(name, patterns);\n }\n\n private shouldMaskBodyField(name: string) {\n const patterns = [\n ...this.config.maskBodyFields,\n ...MASK_BODY_FIELD_PATTERNS,\n ];\n return matchPatterns(name, patterns);\n }\n\n private getContentType(headers: [string, string][]) {\n return headers.find(([k]) => k.toLowerCase() === \"content-type\")?.[1];\n }\n\n private hasSupportedContentType(headers: [string, string][]) {\n const contentType = this.getContentType(headers);\n return this.isSupportedContentType(contentType);\n }\n\n public isSupportedContentType(contentType?: string | string[] | null) {\n if (Array.isArray(contentType) && contentType.length > 0) {\n contentType = contentType[0];\n }\n return (\n typeof contentType === \"string\" &&\n ALLOWED_CONTENT_TYPES.some((t) => contentType.startsWith(t))\n );\n }\n\n private static isHttps(headers: [string, string][]): boolean {\n return headers.some(([k, v]) => {\n switch (k.toLowerCase()) {\n case \"x-forwarded-proto\":\n case \"x-forwarded-protocol\":\n case \"x-forwarded-scheme\":\n case \"x-url-scheme\":\n case \"x-scheme\":\n return v.split(\",\")[0].trim().toLowerCase() === \"https\";\n case \"front-end-https\":\n case \"x-forwarded-ssl\":\n return v.toLowerCase() === \"on\";\n case \"forwarded\":\n return v.split(\",\")[0].trim().toLowerCase().includes(\"proto=https\");\n default:\n return false;\n }\n });\n }\n\n private maskQueryParams(search: string) {\n const params = new URLSearchParams(search);\n for (const [key] of params) {\n if (this.shouldMaskQueryParam(key)) {\n params.set(key, MASKED);\n }\n }\n return params.toString();\n }\n\n private maskHeaders(headers: [string, string][]): [string, string][] {\n return headers.map(([k, v]) => [k, this.shouldMaskHeader(k) ? MASKED : v]);\n }\n\n private maskBody(data: any): any {\n if (typeof data === \"object\" && data !== null && !Array.isArray(data)) {\n const result: any = {};\n for (const [key, value] of Object.entries(data)) {\n if (typeof value === \"string\" && this.shouldMaskBodyField(key)) {\n result[key] = MASKED;\n } else {\n result[key] = this.maskBody(value);\n }\n }\n return result;\n }\n if (Array.isArray(data)) {\n return data.map((item) => this.maskBody(item));\n }\n return data;\n }\n\n private applyMasking(item: RequestLogItem) {\n // Apply user-provided maskRequestBodyCallback function\n if (\n this.config.maskRequestBodyCallback &&\n item.request.body &&\n item.request.body !== BODY_TOO_LARGE\n ) {\n try {\n const maskedBody = this.config.maskRequestBodyCallback(item.request);\n item.request.body = maskedBody ?? BODY_MASKED;\n } catch {\n item.request.body = undefined;\n }\n }\n\n // Apply user-provided maskResponseBodyCallback function\n if (\n this.config.maskResponseBodyCallback &&\n item.response.body &&\n item.response.body !== BODY_TOO_LARGE\n ) {\n try {\n const maskedBody = this.config.maskResponseBodyCallback(\n item.request,\n item.response,\n );\n item.response.body = maskedBody ?? BODY_MASKED;\n } catch {\n item.response.body = undefined;\n }\n }\n\n // Check request and response body sizes\n if (item.request.body && item.request.body.length > MAX_BODY_SIZE) {\n item.request.body = BODY_TOO_LARGE;\n }\n if (item.response.body && item.response.body.length > MAX_BODY_SIZE) {\n item.response.body = BODY_TOO_LARGE;\n }\n\n // Mask request and response body fields\n for (const key of [\"request\", \"response\"] as const) {\n const bodyData = item[key].body;\n if (\n !bodyData ||\n bodyData === BODY_TOO_LARGE ||\n bodyData === BODY_MASKED\n ) {\n continue;\n }\n\n try {\n const contentType = this.getContentType(item[key].headers);\n if (!contentType || /\\bjson\\b/i.test(contentType)) {\n const parsedBody = JSON.parse(bodyData.toString());\n const maskedBody = this.maskBody(parsedBody);\n item[key].body = Buffer.from(JSON.stringify(maskedBody));\n } else if (/\\bndjson\\b/i.test(contentType)) {\n const lines = bodyData\n .toString()\n .split(\"\\n\")\n .filter((line) => line.trim());\n const maskedLines = lines.map((line) => {\n try {\n const parsed = JSON.parse(line);\n const masked = this.maskBody(parsed);\n return JSON.stringify(masked);\n } catch {\n return line; // Keep unparseable lines as is\n }\n });\n item[key].body = Buffer.from(maskedLines.join(\"\\n\"));\n }\n } catch {\n // If parsing fails, leave body as is\n }\n }\n\n // Mask request and response headers\n item.request.headers = this.config.logRequestHeaders\n ? this.maskHeaders(item.request.headers)\n : [];\n item.response.headers = this.config.logResponseHeaders\n ? this.maskHeaders(item.response.headers)\n : [];\n\n // Mask query params\n const url = new URL(item.request.url);\n url.search = this.config.logQueryParams\n ? this.maskQueryParams(url.search)\n : \"\";\n item.request.url = url.toString();\n\n return item;\n }\n\n logRequest(\n request: Request,\n response: Response,\n error?: Error,\n logs?: LogRecord[],\n spans?: SpanData[],\n traceId?: string,\n ) {\n if (!this.enabled || this.suspendUntil !== null) return;\n\n const url = new URL(request.url);\n const path = request.path ?? url.pathname;\n const userAgent = request.headers.find(\n ([k]) => k.toLowerCase() === \"user-agent\",\n )?.[1];\n\n if (url.protocol === \"http:\" && RequestLogger.isHttps(request.headers)) {\n url.protocol = \"https:\";\n request.url = url.toString();\n }\n\n if (\n this.shouldExcludePath(path) ||\n this.shouldExcludeUserAgent(userAgent) ||\n (this.config.excludeCallback?.(request, response) ?? false)\n ) {\n return;\n }\n\n if (\n !this.config.logRequestBody ||\n !this.hasSupportedContentType(request.headers)\n ) {\n request.body = undefined;\n }\n if (\n !this.config.logResponseBody ||\n !this.hasSupportedContentType(response.headers)\n ) {\n response.body = undefined;\n }\n\n if (request.size !== undefined && request.size < 0) {\n request.size = undefined;\n }\n if (response.size !== undefined && response.size < 0) {\n response.size = undefined;\n }\n\n const item: RequestLogItem = {\n uuid: randomUUID(),\n request: request,\n response: response,\n exception:\n error && this.config.logException\n ? {\n type: error.name,\n message: truncateExceptionMessage(error.message),\n stacktrace: truncateExceptionStackTrace(error.stack || \"\"),\n sentryEventId: getSentryEventId(),\n }\n : undefined,\n };\n\n if (logs && logs.length > 0) {\n item.logs = logs.map((log) => ({\n timestamp: log.timestamp,\n logger: log.logger,\n level: log.level,\n message: truncateLogMessage(log.message),\n }));\n }\n if (spans && spans.length > 0) {\n item.spans = spans;\n }\n if (traceId) {\n item.traceId = traceId;\n }\n this.pendingWrites.push(item);\n\n if (this.pendingWrites.length > MAX_PENDING_WRITES) {\n this.pendingWrites.shift();\n }\n }\n\n async writeToFile() {\n if (!this.enabled || this.pendingWrites.length === 0) {\n return;\n }\n return this.lock.acquire(\"file\", async () => {\n if (!this.currentFile) {\n this.currentFile = new TempGzipFile(\"request_logs\");\n }\n while (this.pendingWrites.length > 0) {\n let item = this.pendingWrites.shift();\n if (item) {\n item = this.applyMasking(item);\n\n const finalItem = {\n uuid: item.uuid,\n request: skipEmptyValues(item.request),\n response: skipEmptyValues(item.response),\n exception: item.exception,\n logs: item.logs,\n spans: item.spans,\n traceId: item.traceId,\n };\n\n // Set up body serialization for JSON\n [finalItem.request.body, finalItem.response.body].forEach((body) => {\n if (body) {\n // @ts-expect-error Override Buffer's default JSON serialization\n body.toJSON = function () {\n return this.toString(\"base64\");\n };\n }\n });\n\n await this.currentFile.writeLine(\n Buffer.from(JSON.stringify(finalItem)),\n );\n }\n }\n });\n }\n\n getFile() {\n return this.files.shift();\n }\n\n retryFileLater(file: TempGzipFile) {\n this.files.unshift(file);\n }\n\n async rotateFile() {\n return this.lock.acquire(\"file\", async () => {\n if (this.currentFile) {\n await this.currentFile.close();\n this.files.push(this.currentFile);\n this.currentFile = null;\n }\n });\n }\n\n async maintain() {\n await this.writeToFile();\n if (this.currentFile && this.currentFile.size > MAX_FILE_SIZE) {\n await this.rotateFile();\n }\n while (this.files.length > MAX_FILES) {\n const file = this.files.shift();\n file?.delete();\n }\n if (this.suspendUntil !== null && this.suspendUntil < Date.now()) {\n this.suspendUntil = null;\n }\n }\n\n async clear() {\n this.pendingWrites = [];\n await this.rotateFile();\n this.files.forEach((file) => {\n file.delete();\n });\n this.files = [];\n }\n\n async close() {\n this.enabled = false;\n await this.clear();\n if (this.maintainIntervalId) {\n clearInterval(this.maintainIntervalId);\n }\n }\n}\n\nexport function convertHeaders(\n headers:\n | Headers\n | IncomingHttpHeaders\n | OutgoingHttpHeaders\n | Record<string, string | string[] | number | undefined>\n | undefined,\n) {\n if (!headers) {\n return [];\n }\n if (headers instanceof Headers) {\n return Array.from(headers.entries());\n }\n return Object.entries(headers).flatMap(([key, value]) => {\n if (value === undefined) {\n return [];\n }\n if (Array.isArray(value)) {\n return value.map((v) => [key, v]);\n }\n return [[key, value.toString()]];\n }) as [string, string][];\n}\n\nexport function convertBody(body: any, contentType?: string | null) {\n if (!body || !contentType) {\n return;\n }\n try {\n if (contentType.startsWith(\"application/json\")) {\n if (isValidJsonString(body)) {\n return Buffer.from(body);\n } else {\n return Buffer.from(JSON.stringify(body));\n }\n }\n if (contentType.startsWith(\"text/\") && typeof body === \"string\") {\n return Buffer.from(body);\n }\n } catch (error) {\n return;\n }\n}\n\nfunction isValidJsonString(body: any) {\n if (typeof body !== \"string\") {\n return false;\n }\n try {\n JSON.parse(body);\n return true;\n } catch {\n return false;\n }\n}\n\nfunction matchPatterns(value: string, patterns: RegExp[]) {\n return patterns.some((pattern) => {\n return pattern.test(value);\n });\n}\n\nfunction skipEmptyValues<T extends Record<string, any>>(data: T) {\n return Object.fromEntries(\n Object.entries(data).filter(([_, v]) => {\n if (v == null || Number.isNaN(v)) return false;\n if (Array.isArray(v) || Buffer.isBuffer(v) || typeof v === \"string\") {\n return v.length > 0;\n }\n return true;\n }),\n ) as Partial<T>;\n}\n\nfunction truncateLogMessage(msg: string) {\n if (msg.length > MAX_LOG_MSG_LENGTH) {\n const suffix = \"... (truncated)\";\n return msg.slice(0, MAX_LOG_MSG_LENGTH - suffix.length) + suffix;\n }\n return msg;\n}\n"],"mappings":"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","names":["AsyncLock","Buffer","randomUUID","getSentryEventId","truncateExceptionMessage","truncateExceptionStackTrace","TempGzipFile","checkWritableFs","MAX_BODY_SIZE","MAX_FILE_SIZE","MAX_FILES","MAX_PENDING_WRITES","MAX_LOG_MSG_LENGTH","BODY_TOO_LARGE","from","BODY_MASKED","MASKED","ALLOWED_CONTENT_TYPES","EXCLUDE_PATH_PATTERNS","EXCLUDE_USER_AGENT_PATTERNS","MASK_QUERY_PARAM_PATTERNS","MASK_HEADER_PATTERNS","MASK_BODY_FIELD_PATTERNS","DEFAULT_CONFIG","enabled","logQueryParams","logRequestHeaders","logRequestBody","logResponseHeaders","logResponseBody","logException","captureLogs","captureTraces","maskQueryParams","maskHeaders","maskBodyFields","excludePaths","RequestLogger","config","suspendUntil","pendingWrites","currentFile","files","maintainIntervalId","lock","setInterval","maintain","maxBodySize","shouldExcludePath","urlPath","patterns","matchPatterns","shouldExcludeUserAgent","userAgent","shouldMaskQueryParam","name","shouldMaskHeader","shouldMaskBodyField","getContentType","headers","find","k","toLowerCase","hasSupportedContentType","contentType","isSupportedContentType","Array","isArray","length","some","t","startsWith","isHttps","v","split","trim","includes","search","params","URLSearchParams","key","set","toString","map","maskBody","data","result","value","Object","entries","item","applyMasking","maskRequestBodyCallback","request","body","maskedBody","undefined","maskResponseBodyCallback","response","bodyData","test","parsedBody","JSON","parse","stringify","lines","filter","line","maskedLines","parsed","masked","join","url","URL","logRequest","error","logs","spans","traceId","path","pathname","protocol","excludeCallback","size","uuid","exception","type","message","stacktrace","stack","sentryEventId","log","timestamp","logger","level","truncateLogMessage","push","shift","writeToFile","acquire","finalItem","skipEmptyValues","forEach","toJSON","writeLine","getFile","retryFileLater","file","unshift","rotateFile","close","delete","Date","now","clear","clearInterval","convertHeaders","Headers","flatMap","convertBody","isValidJsonString","pattern","fromEntries","_","Number","isNaN","isBuffer","msg","suffix","slice"]}
@@ -191,14 +191,26 @@ function getRoutePath(req) {
191
191
  if (!req.route) {
192
192
  return;
193
193
  }
194
+ let routePath = req.route.path;
195
+ if (Array.isArray(routePath)) {
196
+ routePath = routePath.find((p) => typeof p === "string") ?? routePath[0];
197
+ }
198
+ if (typeof routePath === "string") {
199
+ routePath = (0, import_utils.stripExpressPathParamRegex)(routePath);
200
+ } else if (routePath instanceof RegExp) {
201
+ routePath = (0, import_utils.formatRegExpRoutePath)(routePath);
202
+ }
203
+ if (typeof routePath !== "string") {
204
+ return;
205
+ }
194
206
  if (req.baseUrl) {
195
207
  const routerInfo = (0, import_utils.getRouterInfo)(req.app);
196
208
  if (routerInfo.stack) {
197
209
  const routerPath = getRouterPath(routerInfo.stack, req.baseUrl);
198
- return req.route.path === "/" ? routerPath : routerPath + req.route.path;
210
+ return routePath === "/" ? routerPath : routerPath + routePath;
199
211
  }
200
212
  }
201
- return req.route.path;
213
+ return routePath;
202
214
  }
203
215
  __name(getRoutePath, "getRoutePath");
204
216
  function getRouterPath(stack, baseUrl) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/express/middleware.ts"],"sourcesContent":["import type { Express, NextFunction, Request, Response, Router } from \"express\";\nimport { AsyncLocalStorage } from \"node:async_hooks\";\nimport { performance } from \"node:perf_hooks\";\n\nimport { ApitallyClient } from \"../common/client.js\";\nimport { consumerFromStringOrObject } from \"../common/consumerRegistry.js\";\nimport { parseContentLength } from \"../common/headers.js\";\nimport { getPackageVersion } from \"../common/packageVersions.js\";\nimport type { LogRecord } from \"../common/requestLogger.js\";\nimport { convertBody, convertHeaders } from \"../common/requestLogger.js\";\nimport {\n ApitallyConfig,\n ApitallyConsumer,\n StartupData,\n ValidationError,\n} from \"../common/types.js\";\nimport {\n patchConsole,\n patchNestLogger,\n patchPinoLogger,\n patchWinston,\n} from \"../loggers/index.js\";\nimport {\n getEndpoints,\n getRouterInfo,\n parseExpressPath,\n parseExpressPathRegExp,\n} from \"./utils.js\";\n\ndeclare module \"express\" {\n interface Request {\n apitallyConsumer?: ApitallyConsumer | string | null;\n consumerIdentifier?: ApitallyConsumer | string | null; // For backwards compatibility\n }\n}\n\nexport function useApitally(\n app: Express | Router,\n config: ApitallyConfig & { basePath?: string },\n) {\n const client = new ApitallyClient(config);\n const middleware = getMiddleware(app, client);\n app.use(middleware);\n\n const setStartupData = (attempt: number = 1) => {\n const appInfo = getAppInfo(app, config.basePath, 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\nfunction getMiddleware(app: Express | Router, client: ApitallyClient) {\n let errorHandlerConfigured = false;\n const logsContext = new AsyncLocalStorage<LogRecord[]>();\n\n if (client.requestLogger.config.captureLogs) {\n patchConsole(logsContext);\n patchWinston(logsContext);\n patchNestLogger(logsContext);\n }\n\n return async (req: Request, res: Response, next: NextFunction) => {\n if (!client.isEnabled() || req.method.toUpperCase() === \"OPTIONS\") {\n next();\n return;\n }\n\n if (!errorHandlerConfigured) {\n // Add error handling middleware to the bottom of the stack when handling the first request\n app.use((err: Error, req: Request, res: Response, next: NextFunction) => {\n res.locals.serverError = err;\n next(err);\n });\n errorHandlerConfigured = true;\n }\n\n if (client.requestLogger.config.captureLogs && \"log\" in req) {\n await patchPinoLogger((req as any).log, logsContext);\n }\n\n logsContext.run([], () => {\n try {\n const startTime = performance.now();\n\n const originalSend = res.send;\n res.send = (body) => {\n const contentType = res.get(\"content-type\");\n if (client.requestLogger.isSupportedContentType(contentType)) {\n res.locals.body = body;\n }\n return originalSend.call(res, body);\n };\n\n const spanHandle = client.spanCollector.startSpan();\n spanHandle.runInContext(() => {\n res.once(\"finish\", () => {\n try {\n const responseTime = performance.now() - startTime;\n const path = getRoutePath(req);\n\n spanHandle.setName(`${req.method} ${path}`);\n const spans = spanHandle.end();\n const traceId = spanHandle.traceId;\n\n const consumer = getConsumer(req);\n client.consumerRegistry.addOrUpdateConsumer(consumer);\n\n const requestSize = parseContentLength(req.get(\"content-length\"));\n const responseSize = parseContentLength(\n res.get(\"content-length\"),\n );\n\n if (path) {\n client.requestCounter.addRequest({\n consumer: consumer?.identifier,\n method: req.method,\n path,\n statusCode: res.statusCode,\n responseTime: responseTime,\n requestSize,\n responseSize,\n });\n\n if (\n (res.statusCode === 400 || res.statusCode === 422) &&\n res.locals.body\n ) {\n let jsonBody: any;\n try {\n jsonBody = JSON.parse(res.locals.body);\n } catch {\n // Ignore\n }\n if (jsonBody) {\n const validationErrors: ValidationError[] = [];\n if (validationErrors.length === 0) {\n validationErrors.push(\n ...extractExpressValidatorErrors(jsonBody),\n );\n }\n if (validationErrors.length === 0) {\n validationErrors.push(\n ...extractCelebrateErrors(jsonBody),\n );\n }\n if (validationErrors.length === 0) {\n validationErrors.push(\n ...extractNestValidationErrors(jsonBody),\n );\n }\n validationErrors.forEach((error) => {\n client.validationErrorCounter.addValidationError({\n consumer: consumer?.identifier,\n method: req.method,\n path,\n ...error,\n });\n });\n }\n }\n\n if (res.statusCode === 500 && res.locals.serverError) {\n const serverError = res.locals.serverError as Error;\n client.serverErrorCounter.addServerError({\n consumer: consumer?.identifier,\n method: req.method,\n path,\n type: serverError.name,\n msg: serverError.message,\n traceback: serverError.stack || \"\",\n });\n }\n }\n\n if (client.requestLogger.enabled) {\n const logs = logsContext.getStore();\n client.requestLogger.logRequest(\n {\n timestamp: Date.now() / 1000,\n method: req.method,\n path,\n url: `${req.protocol}://${req.host}${req.originalUrl}`,\n headers: convertHeaders(req.headers),\n size: requestSize,\n consumer: consumer?.identifier,\n body: convertBody(req.body, req.get(\"content-type\")),\n },\n {\n statusCode: res.statusCode,\n responseTime: responseTime / 1000,\n headers: convertHeaders(res.getHeaders()),\n size: responseSize,\n body: convertBody(res.locals.body, res.get(\"content-type\")),\n },\n res.locals.serverError,\n logs,\n spans,\n traceId,\n );\n }\n } catch (error) {\n client.logger.error(\n \"Error while logging request in Apitally middleware\",\n { request: req, response: res, error },\n );\n }\n });\n\n res.once(\"close\", () => {\n spanHandle.end();\n });\n\n next();\n });\n } catch (error) {\n client.logger.error(\"Error in Apitally middleware\", {\n request: req,\n response: res,\n error,\n });\n }\n });\n };\n}\n\nfunction getRoutePath(req: Request) {\n if (!req.route) {\n return;\n }\n if (req.baseUrl) {\n const routerInfo = getRouterInfo(req.app);\n if (routerInfo.stack) {\n const routerPath = getRouterPath(routerInfo.stack, req.baseUrl);\n return req.route.path === \"/\" ? routerPath : routerPath + req.route.path;\n }\n }\n return req.route.path;\n}\n\nfunction getRouterPath(stack: any[], baseUrl: string) {\n const routerPaths: string[] = [];\n while (stack && stack.length > 0) {\n const routerLayer = stack.find(\n (layer) =>\n layer.name === \"router\" &&\n layer.path &&\n (baseUrl.startsWith(layer.path) || layer.regexp?.test(baseUrl)),\n );\n if (routerLayer) {\n if (\n routerLayer.regexp &&\n routerLayer.keys &&\n routerLayer.keys.length > 0\n ) {\n const parsedPath = parseExpressPathRegExp(\n routerLayer.regexp,\n routerLayer.keys,\n );\n routerPaths.push(\"/\" + parsedPath);\n } else if (\n routerLayer.params &&\n Object.keys(routerLayer.params).length > 0\n ) {\n const parsedPath = parseExpressPath(\n routerLayer.path,\n routerLayer.params,\n );\n routerPaths.push(parsedPath);\n } else {\n routerPaths.push(routerLayer.path);\n }\n stack = routerLayer.handle?.stack;\n baseUrl = baseUrl.slice(routerLayer.path.length);\n } else {\n break;\n }\n }\n return routerPaths.filter((path) => path !== \"/\").join(\"\");\n}\n\nexport function setConsumer(\n req: Request,\n consumer: ApitallyConsumer | string | null | undefined,\n) {\n req.apitallyConsumer = consumer || undefined;\n}\n\nfunction getConsumer(req: Request) {\n if (req.apitallyConsumer) {\n return consumerFromStringOrObject(req.apitallyConsumer);\n } else if (req.consumerIdentifier) {\n // For backwards compatibility\n process.emitWarning(\n \"The consumerIdentifier property on the request object is deprecated. Use apitallyConsumer instead.\",\n \"DeprecationWarning\",\n );\n return consumerFromStringOrObject(req.consumerIdentifier);\n }\n return null;\n}\n\nfunction extractExpressValidatorErrors(responseBody: any) {\n try {\n const errors: ValidationError[] = [];\n if (\n responseBody &&\n responseBody.errors &&\n Array.isArray(responseBody.errors)\n ) {\n responseBody.errors.forEach((error: any) => {\n if (error.location && error.path && error.msg && error.type) {\n errors.push({\n loc: `${error.location}.${error.path}`,\n msg: error.msg,\n type: error.type,\n });\n }\n });\n }\n return errors;\n } catch (error) {\n return [];\n }\n}\n\nfunction extractCelebrateErrors(responseBody: any) {\n try {\n const errors: ValidationError[] = [];\n if (responseBody && responseBody.validation) {\n Object.values(responseBody.validation).forEach((error: any) => {\n if (\n error.source &&\n error.keys &&\n Array.isArray(error.keys) &&\n error.message\n ) {\n error.keys.forEach((key: string) => {\n errors.push({\n loc: `${error.source}.${key}`,\n msg: subsetJoiMessage(error.message, key),\n type: \"\",\n });\n });\n }\n });\n }\n return errors;\n } catch (error) {\n return [];\n }\n}\n\nfunction extractNestValidationErrors(responseBody: any) {\n try {\n const errors: ValidationError[] = [];\n if (responseBody && Array.isArray(responseBody.message)) {\n responseBody.message.forEach((message: any) => {\n errors.push({\n loc: \"\",\n msg: message,\n type: \"\",\n });\n });\n }\n return errors;\n } catch (error) {\n return [];\n }\n}\n\nfunction subsetJoiMessage(message: string, key: string) {\n const messageWithKey = message\n .split(\". \")\n .find((message) => message.includes(`\"${key}\"`));\n return messageWithKey ? messageWithKey : message;\n}\n\nfunction getAppInfo(\n app: Express | Router,\n basePath?: string,\n appVersion?: string,\n): StartupData {\n const versions: Array<[string, string]> = [\n [\"nodejs\", process.version.replace(/^v/, \"\")],\n ];\n const expressVersion = getPackageVersion(\"express\");\n const nestjsVersion = getPackageVersion(\"@nestjs/core\");\n const apitallyVersion = getPackageVersion(\"../..\");\n if (expressVersion) {\n versions.push([\"express\", expressVersion]);\n }\n if (nestjsVersion) {\n versions.push([\"nestjs\", nestjsVersion]);\n }\n if (apitallyVersion) {\n versions.push([\"apitally\", apitallyVersion]);\n }\n if (appVersion) {\n versions.push([\"app\", appVersion]);\n }\n return {\n paths: getEndpoints(app, basePath || \"\"),\n versions: Object.fromEntries(versions),\n client: \"js:express\",\n };\n}\n"],"mappings":"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","names":["useApitally","app","config","client","ApitallyClient","middleware","getMiddleware","use","setStartupData","attempt","appInfo","getAppInfo","basePath","appVersion","paths","length","startSync","setTimeout","errorHandlerConfigured","logsContext","AsyncLocalStorage","requestLogger","captureLogs","patchConsole","patchWinston","patchNestLogger","req","res","next","isEnabled","method","toUpperCase","err","locals","serverError","patchPinoLogger","log","run","startTime","performance","now","originalSend","send","body","contentType","get","isSupportedContentType","call","spanHandle","spanCollector","startSpan","runInContext","once","responseTime","path","getRoutePath","setName","spans","end","traceId","consumer","getConsumer","consumerRegistry","addOrUpdateConsumer","requestSize","parseContentLength","responseSize","requestCounter","addRequest","identifier","statusCode","jsonBody","JSON","parse","validationErrors","push","extractExpressValidatorErrors","extractCelebrateErrors","extractNestValidationErrors","forEach","error","validationErrorCounter","addValidationError","serverErrorCounter","addServerError","type","name","msg","message","traceback","stack","enabled","logs","getStore","logRequest","timestamp","Date","url","protocol","host","originalUrl","headers","convertHeaders","size","convertBody","getHeaders","logger","request","response","route","baseUrl","routerInfo","getRouterInfo","routerPath","getRouterPath","routerPaths","routerLayer","find","layer","_a","startsWith","regexp","test","keys","parsedPath","parseExpressPathRegExp","params","Object","parseExpressPath","handle","slice","filter","join","setConsumer","apitallyConsumer","undefined","consumerFromStringOrObject","consumerIdentifier","process","emitWarning","responseBody","errors","Array","isArray","location","loc","validation","values","source","key","subsetJoiMessage","messageWithKey","split","includes","versions","version","replace","expressVersion","getPackageVersion","nestjsVersion","apitallyVersion","getEndpoints","fromEntries"]}
1
+ {"version":3,"sources":["../../src/express/middleware.ts"],"sourcesContent":["import type { Express, NextFunction, Request, Response, Router } from \"express\";\nimport { AsyncLocalStorage } from \"node:async_hooks\";\nimport { performance } from \"node:perf_hooks\";\n\nimport { ApitallyClient } from \"../common/client.js\";\nimport { consumerFromStringOrObject } from \"../common/consumerRegistry.js\";\nimport { parseContentLength } from \"../common/headers.js\";\nimport { getPackageVersion } from \"../common/packageVersions.js\";\nimport type { LogRecord } from \"../common/requestLogger.js\";\nimport { convertBody, convertHeaders } from \"../common/requestLogger.js\";\nimport {\n ApitallyConfig,\n ApitallyConsumer,\n StartupData,\n ValidationError,\n} from \"../common/types.js\";\nimport {\n patchConsole,\n patchNestLogger,\n patchPinoLogger,\n patchWinston,\n} from \"../loggers/index.js\";\nimport {\n formatRegExpRoutePath,\n getEndpoints,\n getRouterInfo,\n parseExpressPath,\n parseExpressPathRegExp,\n stripExpressPathParamRegex,\n} from \"./utils.js\";\n\ndeclare module \"express\" {\n interface Request {\n apitallyConsumer?: ApitallyConsumer | string | null;\n consumerIdentifier?: ApitallyConsumer | string | null; // For backwards compatibility\n }\n}\n\nexport function useApitally(\n app: Express | Router,\n config: ApitallyConfig & { basePath?: string },\n) {\n const client = new ApitallyClient(config);\n const middleware = getMiddleware(app, client);\n app.use(middleware);\n\n const setStartupData = (attempt: number = 1) => {\n const appInfo = getAppInfo(app, config.basePath, 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\nfunction getMiddleware(app: Express | Router, client: ApitallyClient) {\n let errorHandlerConfigured = false;\n const logsContext = new AsyncLocalStorage<LogRecord[]>();\n\n if (client.requestLogger.config.captureLogs) {\n patchConsole(logsContext);\n patchWinston(logsContext);\n patchNestLogger(logsContext);\n }\n\n return async (req: Request, res: Response, next: NextFunction) => {\n if (!client.isEnabled() || req.method.toUpperCase() === \"OPTIONS\") {\n next();\n return;\n }\n\n if (!errorHandlerConfigured) {\n // Add error handling middleware to the bottom of the stack when handling the first request\n app.use((err: Error, req: Request, res: Response, next: NextFunction) => {\n res.locals.serverError = err;\n next(err);\n });\n errorHandlerConfigured = true;\n }\n\n if (client.requestLogger.config.captureLogs && \"log\" in req) {\n await patchPinoLogger((req as any).log, logsContext);\n }\n\n logsContext.run([], () => {\n try {\n const startTime = performance.now();\n\n const originalSend = res.send;\n res.send = (body) => {\n const contentType = res.get(\"content-type\");\n if (client.requestLogger.isSupportedContentType(contentType)) {\n res.locals.body = body;\n }\n return originalSend.call(res, body);\n };\n\n const spanHandle = client.spanCollector.startSpan();\n spanHandle.runInContext(() => {\n res.once(\"finish\", () => {\n try {\n const responseTime = performance.now() - startTime;\n const path = getRoutePath(req);\n\n spanHandle.setName(`${req.method} ${path}`);\n const spans = spanHandle.end();\n const traceId = spanHandle.traceId;\n\n const consumer = getConsumer(req);\n client.consumerRegistry.addOrUpdateConsumer(consumer);\n\n const requestSize = parseContentLength(req.get(\"content-length\"));\n const responseSize = parseContentLength(\n res.get(\"content-length\"),\n );\n\n if (path) {\n client.requestCounter.addRequest({\n consumer: consumer?.identifier,\n method: req.method,\n path,\n statusCode: res.statusCode,\n responseTime: responseTime,\n requestSize,\n responseSize,\n });\n\n if (\n (res.statusCode === 400 || res.statusCode === 422) &&\n res.locals.body\n ) {\n let jsonBody: any;\n try {\n jsonBody = JSON.parse(res.locals.body);\n } catch {\n // Ignore\n }\n if (jsonBody) {\n const validationErrors: ValidationError[] = [];\n if (validationErrors.length === 0) {\n validationErrors.push(\n ...extractExpressValidatorErrors(jsonBody),\n );\n }\n if (validationErrors.length === 0) {\n validationErrors.push(\n ...extractCelebrateErrors(jsonBody),\n );\n }\n if (validationErrors.length === 0) {\n validationErrors.push(\n ...extractNestValidationErrors(jsonBody),\n );\n }\n validationErrors.forEach((error) => {\n client.validationErrorCounter.addValidationError({\n consumer: consumer?.identifier,\n method: req.method,\n path,\n ...error,\n });\n });\n }\n }\n\n if (res.statusCode === 500 && res.locals.serverError) {\n const serverError = res.locals.serverError as Error;\n client.serverErrorCounter.addServerError({\n consumer: consumer?.identifier,\n method: req.method,\n path,\n type: serverError.name,\n msg: serverError.message,\n traceback: serverError.stack || \"\",\n });\n }\n }\n\n if (client.requestLogger.enabled) {\n const logs = logsContext.getStore();\n client.requestLogger.logRequest(\n {\n timestamp: Date.now() / 1000,\n method: req.method,\n path,\n url: `${req.protocol}://${req.host}${req.originalUrl}`,\n headers: convertHeaders(req.headers),\n size: requestSize,\n consumer: consumer?.identifier,\n body: convertBody(req.body, req.get(\"content-type\")),\n },\n {\n statusCode: res.statusCode,\n responseTime: responseTime / 1000,\n headers: convertHeaders(res.getHeaders()),\n size: responseSize,\n body: convertBody(res.locals.body, res.get(\"content-type\")),\n },\n res.locals.serverError,\n logs,\n spans,\n traceId,\n );\n }\n } catch (error) {\n client.logger.error(\n \"Error while logging request in Apitally middleware\",\n { request: req, response: res, error },\n );\n }\n });\n\n res.once(\"close\", () => {\n spanHandle.end();\n });\n\n next();\n });\n } catch (error) {\n client.logger.error(\"Error in Apitally middleware\", {\n request: req,\n response: res,\n error,\n });\n }\n });\n };\n}\n\nfunction getRoutePath(req: Request) {\n if (!req.route) {\n return;\n }\n // req.route.path can be a string, a RegExp, or an array of either\n let routePath: unknown = req.route.path;\n if (Array.isArray(routePath)) {\n routePath = routePath.find((p) => typeof p === \"string\") ?? routePath[0];\n }\n if (typeof routePath === \"string\") {\n routePath = stripExpressPathParamRegex(routePath);\n } else if (routePath instanceof RegExp) {\n routePath = formatRegExpRoutePath(routePath);\n }\n if (typeof routePath !== \"string\") {\n return;\n }\n if (req.baseUrl) {\n const routerInfo = getRouterInfo(req.app);\n if (routerInfo.stack) {\n const routerPath = getRouterPath(routerInfo.stack, req.baseUrl);\n return routePath === \"/\" ? routerPath : routerPath + routePath;\n }\n }\n return routePath;\n}\n\nfunction getRouterPath(stack: any[], baseUrl: string) {\n const routerPaths: string[] = [];\n while (stack && stack.length > 0) {\n const routerLayer = stack.find(\n (layer) =>\n layer.name === \"router\" &&\n layer.path &&\n (baseUrl.startsWith(layer.path) || layer.regexp?.test(baseUrl)),\n );\n if (routerLayer) {\n if (\n routerLayer.regexp &&\n routerLayer.keys &&\n routerLayer.keys.length > 0\n ) {\n const parsedPath = parseExpressPathRegExp(\n routerLayer.regexp,\n routerLayer.keys,\n );\n routerPaths.push(\"/\" + parsedPath);\n } else if (\n routerLayer.params &&\n Object.keys(routerLayer.params).length > 0\n ) {\n const parsedPath = parseExpressPath(\n routerLayer.path,\n routerLayer.params,\n );\n routerPaths.push(parsedPath);\n } else {\n routerPaths.push(routerLayer.path);\n }\n stack = routerLayer.handle?.stack;\n baseUrl = baseUrl.slice(routerLayer.path.length);\n } else {\n break;\n }\n }\n return routerPaths.filter((path) => path !== \"/\").join(\"\");\n}\n\nexport function setConsumer(\n req: Request,\n consumer: ApitallyConsumer | string | null | undefined,\n) {\n req.apitallyConsumer = consumer || undefined;\n}\n\nfunction getConsumer(req: Request) {\n if (req.apitallyConsumer) {\n return consumerFromStringOrObject(req.apitallyConsumer);\n } else if (req.consumerIdentifier) {\n // For backwards compatibility\n process.emitWarning(\n \"The consumerIdentifier property on the request object is deprecated. Use apitallyConsumer instead.\",\n \"DeprecationWarning\",\n );\n return consumerFromStringOrObject(req.consumerIdentifier);\n }\n return null;\n}\n\nfunction extractExpressValidatorErrors(responseBody: any) {\n try {\n const errors: ValidationError[] = [];\n if (\n responseBody &&\n responseBody.errors &&\n Array.isArray(responseBody.errors)\n ) {\n responseBody.errors.forEach((error: any) => {\n if (error.location && error.path && error.msg && error.type) {\n errors.push({\n loc: `${error.location}.${error.path}`,\n msg: error.msg,\n type: error.type,\n });\n }\n });\n }\n return errors;\n } catch (error) {\n return [];\n }\n}\n\nfunction extractCelebrateErrors(responseBody: any) {\n try {\n const errors: ValidationError[] = [];\n if (responseBody && responseBody.validation) {\n Object.values(responseBody.validation).forEach((error: any) => {\n if (\n error.source &&\n error.keys &&\n Array.isArray(error.keys) &&\n error.message\n ) {\n error.keys.forEach((key: string) => {\n errors.push({\n loc: `${error.source}.${key}`,\n msg: subsetJoiMessage(error.message, key),\n type: \"\",\n });\n });\n }\n });\n }\n return errors;\n } catch (error) {\n return [];\n }\n}\n\nfunction extractNestValidationErrors(responseBody: any) {\n try {\n const errors: ValidationError[] = [];\n if (responseBody && Array.isArray(responseBody.message)) {\n responseBody.message.forEach((message: any) => {\n errors.push({\n loc: \"\",\n msg: message,\n type: \"\",\n });\n });\n }\n return errors;\n } catch (error) {\n return [];\n }\n}\n\nfunction subsetJoiMessage(message: string, key: string) {\n const messageWithKey = message\n .split(\". \")\n .find((message) => message.includes(`\"${key}\"`));\n return messageWithKey ? messageWithKey : message;\n}\n\nfunction getAppInfo(\n app: Express | Router,\n basePath?: string,\n appVersion?: string,\n): StartupData {\n const versions: Array<[string, string]> = [\n [\"nodejs\", process.version.replace(/^v/, \"\")],\n ];\n const expressVersion = getPackageVersion(\"express\");\n const nestjsVersion = getPackageVersion(\"@nestjs/core\");\n const apitallyVersion = getPackageVersion(\"../..\");\n if (expressVersion) {\n versions.push([\"express\", expressVersion]);\n }\n if (nestjsVersion) {\n versions.push([\"nestjs\", nestjsVersion]);\n }\n if (apitallyVersion) {\n versions.push([\"apitally\", apitallyVersion]);\n }\n if (appVersion) {\n versions.push([\"app\", appVersion]);\n }\n return {\n paths: getEndpoints(app, basePath || \"\"),\n versions: Object.fromEntries(versions),\n client: \"js:express\",\n };\n}\n"],"mappings":"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","names":["useApitally","app","config","client","ApitallyClient","middleware","getMiddleware","use","setStartupData","attempt","appInfo","getAppInfo","basePath","appVersion","paths","length","startSync","setTimeout","errorHandlerConfigured","logsContext","AsyncLocalStorage","requestLogger","captureLogs","patchConsole","patchWinston","patchNestLogger","req","res","next","isEnabled","method","toUpperCase","err","locals","serverError","patchPinoLogger","log","run","startTime","performance","now","originalSend","send","body","contentType","get","isSupportedContentType","call","spanHandle","spanCollector","startSpan","runInContext","once","responseTime","path","getRoutePath","setName","spans","end","traceId","consumer","getConsumer","consumerRegistry","addOrUpdateConsumer","requestSize","parseContentLength","responseSize","requestCounter","addRequest","identifier","statusCode","jsonBody","JSON","parse","validationErrors","push","extractExpressValidatorErrors","extractCelebrateErrors","extractNestValidationErrors","forEach","error","validationErrorCounter","addValidationError","serverErrorCounter","addServerError","type","name","msg","message","traceback","stack","enabled","logs","getStore","logRequest","timestamp","Date","url","protocol","host","originalUrl","headers","convertHeaders","size","convertBody","getHeaders","logger","request","response","route","routePath","Array","isArray","find","p","stripExpressPathParamRegex","RegExp","formatRegExpRoutePath","baseUrl","routerInfo","getRouterInfo","routerPath","getRouterPath","routerPaths","routerLayer","layer","_a","startsWith","regexp","test","keys","parsedPath","parseExpressPathRegExp","params","Object","parseExpressPath","handle","slice","filter","join","setConsumer","apitallyConsumer","undefined","consumerFromStringOrObject","consumerIdentifier","process","emitWarning","responseBody","errors","location","loc","validation","values","source","key","subsetJoiMessage","messageWithKey","split","includes","versions","version","replace","expressVersion","getPackageVersion","nestjsVersion","apitallyVersion","getEndpoints","fromEntries"]}
@@ -8,7 +8,7 @@ import { parseContentLength } from "../common/headers.js";
8
8
  import { getPackageVersion } from "../common/packageVersions.js";
9
9
  import { convertBody, convertHeaders } from "../common/requestLogger.js";
10
10
  import { patchConsole, patchNestLogger, patchPinoLogger, patchWinston } from "../loggers/index.js";
11
- import { getEndpoints, getRouterInfo, parseExpressPath, parseExpressPathRegExp } from "./utils.js";
11
+ import { formatRegExpRoutePath, getEndpoints, getRouterInfo, parseExpressPath, parseExpressPathRegExp, stripExpressPathParamRegex } from "./utils.js";
12
12
  function useApitally(app, config) {
13
13
  const client = new ApitallyClient(config);
14
14
  const middleware = getMiddleware(app, client);
@@ -168,14 +168,26 @@ function getRoutePath(req) {
168
168
  if (!req.route) {
169
169
  return;
170
170
  }
171
+ let routePath = req.route.path;
172
+ if (Array.isArray(routePath)) {
173
+ routePath = routePath.find((p) => typeof p === "string") ?? routePath[0];
174
+ }
175
+ if (typeof routePath === "string") {
176
+ routePath = stripExpressPathParamRegex(routePath);
177
+ } else if (routePath instanceof RegExp) {
178
+ routePath = formatRegExpRoutePath(routePath);
179
+ }
180
+ if (typeof routePath !== "string") {
181
+ return;
182
+ }
171
183
  if (req.baseUrl) {
172
184
  const routerInfo = getRouterInfo(req.app);
173
185
  if (routerInfo.stack) {
174
186
  const routerPath = getRouterPath(routerInfo.stack, req.baseUrl);
175
- return req.route.path === "/" ? routerPath : routerPath + req.route.path;
187
+ return routePath === "/" ? routerPath : routerPath + routePath;
176
188
  }
177
189
  }
178
- return req.route.path;
190
+ return routePath;
179
191
  }
180
192
  __name(getRoutePath, "getRoutePath");
181
193
  function getRouterPath(stack, baseUrl) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/express/middleware.ts"],"sourcesContent":["import type { Express, NextFunction, Request, Response, Router } from \"express\";\nimport { AsyncLocalStorage } from \"node:async_hooks\";\nimport { performance } from \"node:perf_hooks\";\n\nimport { ApitallyClient } from \"../common/client.js\";\nimport { consumerFromStringOrObject } from \"../common/consumerRegistry.js\";\nimport { parseContentLength } from \"../common/headers.js\";\nimport { getPackageVersion } from \"../common/packageVersions.js\";\nimport type { LogRecord } from \"../common/requestLogger.js\";\nimport { convertBody, convertHeaders } from \"../common/requestLogger.js\";\nimport {\n ApitallyConfig,\n ApitallyConsumer,\n StartupData,\n ValidationError,\n} from \"../common/types.js\";\nimport {\n patchConsole,\n patchNestLogger,\n patchPinoLogger,\n patchWinston,\n} from \"../loggers/index.js\";\nimport {\n getEndpoints,\n getRouterInfo,\n parseExpressPath,\n parseExpressPathRegExp,\n} from \"./utils.js\";\n\ndeclare module \"express\" {\n interface Request {\n apitallyConsumer?: ApitallyConsumer | string | null;\n consumerIdentifier?: ApitallyConsumer | string | null; // For backwards compatibility\n }\n}\n\nexport function useApitally(\n app: Express | Router,\n config: ApitallyConfig & { basePath?: string },\n) {\n const client = new ApitallyClient(config);\n const middleware = getMiddleware(app, client);\n app.use(middleware);\n\n const setStartupData = (attempt: number = 1) => {\n const appInfo = getAppInfo(app, config.basePath, 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\nfunction getMiddleware(app: Express | Router, client: ApitallyClient) {\n let errorHandlerConfigured = false;\n const logsContext = new AsyncLocalStorage<LogRecord[]>();\n\n if (client.requestLogger.config.captureLogs) {\n patchConsole(logsContext);\n patchWinston(logsContext);\n patchNestLogger(logsContext);\n }\n\n return async (req: Request, res: Response, next: NextFunction) => {\n if (!client.isEnabled() || req.method.toUpperCase() === \"OPTIONS\") {\n next();\n return;\n }\n\n if (!errorHandlerConfigured) {\n // Add error handling middleware to the bottom of the stack when handling the first request\n app.use((err: Error, req: Request, res: Response, next: NextFunction) => {\n res.locals.serverError = err;\n next(err);\n });\n errorHandlerConfigured = true;\n }\n\n if (client.requestLogger.config.captureLogs && \"log\" in req) {\n await patchPinoLogger((req as any).log, logsContext);\n }\n\n logsContext.run([], () => {\n try {\n const startTime = performance.now();\n\n const originalSend = res.send;\n res.send = (body) => {\n const contentType = res.get(\"content-type\");\n if (client.requestLogger.isSupportedContentType(contentType)) {\n res.locals.body = body;\n }\n return originalSend.call(res, body);\n };\n\n const spanHandle = client.spanCollector.startSpan();\n spanHandle.runInContext(() => {\n res.once(\"finish\", () => {\n try {\n const responseTime = performance.now() - startTime;\n const path = getRoutePath(req);\n\n spanHandle.setName(`${req.method} ${path}`);\n const spans = spanHandle.end();\n const traceId = spanHandle.traceId;\n\n const consumer = getConsumer(req);\n client.consumerRegistry.addOrUpdateConsumer(consumer);\n\n const requestSize = parseContentLength(req.get(\"content-length\"));\n const responseSize = parseContentLength(\n res.get(\"content-length\"),\n );\n\n if (path) {\n client.requestCounter.addRequest({\n consumer: consumer?.identifier,\n method: req.method,\n path,\n statusCode: res.statusCode,\n responseTime: responseTime,\n requestSize,\n responseSize,\n });\n\n if (\n (res.statusCode === 400 || res.statusCode === 422) &&\n res.locals.body\n ) {\n let jsonBody: any;\n try {\n jsonBody = JSON.parse(res.locals.body);\n } catch {\n // Ignore\n }\n if (jsonBody) {\n const validationErrors: ValidationError[] = [];\n if (validationErrors.length === 0) {\n validationErrors.push(\n ...extractExpressValidatorErrors(jsonBody),\n );\n }\n if (validationErrors.length === 0) {\n validationErrors.push(\n ...extractCelebrateErrors(jsonBody),\n );\n }\n if (validationErrors.length === 0) {\n validationErrors.push(\n ...extractNestValidationErrors(jsonBody),\n );\n }\n validationErrors.forEach((error) => {\n client.validationErrorCounter.addValidationError({\n consumer: consumer?.identifier,\n method: req.method,\n path,\n ...error,\n });\n });\n }\n }\n\n if (res.statusCode === 500 && res.locals.serverError) {\n const serverError = res.locals.serverError as Error;\n client.serverErrorCounter.addServerError({\n consumer: consumer?.identifier,\n method: req.method,\n path,\n type: serverError.name,\n msg: serverError.message,\n traceback: serverError.stack || \"\",\n });\n }\n }\n\n if (client.requestLogger.enabled) {\n const logs = logsContext.getStore();\n client.requestLogger.logRequest(\n {\n timestamp: Date.now() / 1000,\n method: req.method,\n path,\n url: `${req.protocol}://${req.host}${req.originalUrl}`,\n headers: convertHeaders(req.headers),\n size: requestSize,\n consumer: consumer?.identifier,\n body: convertBody(req.body, req.get(\"content-type\")),\n },\n {\n statusCode: res.statusCode,\n responseTime: responseTime / 1000,\n headers: convertHeaders(res.getHeaders()),\n size: responseSize,\n body: convertBody(res.locals.body, res.get(\"content-type\")),\n },\n res.locals.serverError,\n logs,\n spans,\n traceId,\n );\n }\n } catch (error) {\n client.logger.error(\n \"Error while logging request in Apitally middleware\",\n { request: req, response: res, error },\n );\n }\n });\n\n res.once(\"close\", () => {\n spanHandle.end();\n });\n\n next();\n });\n } catch (error) {\n client.logger.error(\"Error in Apitally middleware\", {\n request: req,\n response: res,\n error,\n });\n }\n });\n };\n}\n\nfunction getRoutePath(req: Request) {\n if (!req.route) {\n return;\n }\n if (req.baseUrl) {\n const routerInfo = getRouterInfo(req.app);\n if (routerInfo.stack) {\n const routerPath = getRouterPath(routerInfo.stack, req.baseUrl);\n return req.route.path === \"/\" ? routerPath : routerPath + req.route.path;\n }\n }\n return req.route.path;\n}\n\nfunction getRouterPath(stack: any[], baseUrl: string) {\n const routerPaths: string[] = [];\n while (stack && stack.length > 0) {\n const routerLayer = stack.find(\n (layer) =>\n layer.name === \"router\" &&\n layer.path &&\n (baseUrl.startsWith(layer.path) || layer.regexp?.test(baseUrl)),\n );\n if (routerLayer) {\n if (\n routerLayer.regexp &&\n routerLayer.keys &&\n routerLayer.keys.length > 0\n ) {\n const parsedPath = parseExpressPathRegExp(\n routerLayer.regexp,\n routerLayer.keys,\n );\n routerPaths.push(\"/\" + parsedPath);\n } else if (\n routerLayer.params &&\n Object.keys(routerLayer.params).length > 0\n ) {\n const parsedPath = parseExpressPath(\n routerLayer.path,\n routerLayer.params,\n );\n routerPaths.push(parsedPath);\n } else {\n routerPaths.push(routerLayer.path);\n }\n stack = routerLayer.handle?.stack;\n baseUrl = baseUrl.slice(routerLayer.path.length);\n } else {\n break;\n }\n }\n return routerPaths.filter((path) => path !== \"/\").join(\"\");\n}\n\nexport function setConsumer(\n req: Request,\n consumer: ApitallyConsumer | string | null | undefined,\n) {\n req.apitallyConsumer = consumer || undefined;\n}\n\nfunction getConsumer(req: Request) {\n if (req.apitallyConsumer) {\n return consumerFromStringOrObject(req.apitallyConsumer);\n } else if (req.consumerIdentifier) {\n // For backwards compatibility\n process.emitWarning(\n \"The consumerIdentifier property on the request object is deprecated. Use apitallyConsumer instead.\",\n \"DeprecationWarning\",\n );\n return consumerFromStringOrObject(req.consumerIdentifier);\n }\n return null;\n}\n\nfunction extractExpressValidatorErrors(responseBody: any) {\n try {\n const errors: ValidationError[] = [];\n if (\n responseBody &&\n responseBody.errors &&\n Array.isArray(responseBody.errors)\n ) {\n responseBody.errors.forEach((error: any) => {\n if (error.location && error.path && error.msg && error.type) {\n errors.push({\n loc: `${error.location}.${error.path}`,\n msg: error.msg,\n type: error.type,\n });\n }\n });\n }\n return errors;\n } catch (error) {\n return [];\n }\n}\n\nfunction extractCelebrateErrors(responseBody: any) {\n try {\n const errors: ValidationError[] = [];\n if (responseBody && responseBody.validation) {\n Object.values(responseBody.validation).forEach((error: any) => {\n if (\n error.source &&\n error.keys &&\n Array.isArray(error.keys) &&\n error.message\n ) {\n error.keys.forEach((key: string) => {\n errors.push({\n loc: `${error.source}.${key}`,\n msg: subsetJoiMessage(error.message, key),\n type: \"\",\n });\n });\n }\n });\n }\n return errors;\n } catch (error) {\n return [];\n }\n}\n\nfunction extractNestValidationErrors(responseBody: any) {\n try {\n const errors: ValidationError[] = [];\n if (responseBody && Array.isArray(responseBody.message)) {\n responseBody.message.forEach((message: any) => {\n errors.push({\n loc: \"\",\n msg: message,\n type: \"\",\n });\n });\n }\n return errors;\n } catch (error) {\n return [];\n }\n}\n\nfunction subsetJoiMessage(message: string, key: string) {\n const messageWithKey = message\n .split(\". \")\n .find((message) => message.includes(`\"${key}\"`));\n return messageWithKey ? messageWithKey : message;\n}\n\nfunction getAppInfo(\n app: Express | Router,\n basePath?: string,\n appVersion?: string,\n): StartupData {\n const versions: Array<[string, string]> = [\n [\"nodejs\", process.version.replace(/^v/, \"\")],\n ];\n const expressVersion = getPackageVersion(\"express\");\n const nestjsVersion = getPackageVersion(\"@nestjs/core\");\n const apitallyVersion = getPackageVersion(\"../..\");\n if (expressVersion) {\n versions.push([\"express\", expressVersion]);\n }\n if (nestjsVersion) {\n versions.push([\"nestjs\", nestjsVersion]);\n }\n if (apitallyVersion) {\n versions.push([\"apitally\", apitallyVersion]);\n }\n if (appVersion) {\n versions.push([\"app\", appVersion]);\n }\n return {\n paths: getEndpoints(app, basePath || \"\"),\n versions: Object.fromEntries(versions),\n client: \"js:express\",\n };\n}\n"],"mappings":";;AACA,SAASA,yBAAyB;AAClC,SAASC,mBAAmB;AAE5B,SAASC,sBAAsB;AAC/B,SAASC,kCAAkC;AAC3C,SAASC,0BAA0B;AACnC,SAASC,yBAAyB;AAElC,SAASC,aAAaC,sBAAsB;AAO5C,SACEC,cACAC,iBACAC,iBACAC,oBACK;AACP,SACEC,cACAC,eACAC,kBACAC,8BACK;AASA,SAASC,YACdC,KACAC,QAA8C;AAE9C,QAAMC,SAAS,IAAIjB,eAAegB,MAAAA;AAClC,QAAME,aAAaC,cAAcJ,KAAKE,MAAAA;AACtCF,MAAIK,IAAIF,UAAAA;AAER,QAAMG,iBAAiB,wBAACC,UAAkB,MAAC;AACzC,UAAMC,UAAUC,WAAWT,KAAKC,OAAOS,UAAUT,OAAOU,UAAU;AAClE,QAAIH,QAAQI,MAAMC,SAAS,KAAKN,WAAW,IAAI;AAC7CL,aAAOI,eAAeE,OAAAA;AACtBN,aAAOY,UAAS;IAClB,OAAO;AACLC,iBAAW,MAAMT,eAAeC,UAAU,CAAA,GAAI,GAAA;IAChD;EACF,GARuB;AASvBQ,aAAW,MAAMT,eAAAA,GAAkB,GAAA;AACrC;AAlBgBP;AAoBhB,SAASK,cAAcJ,KAAuBE,QAAsB;AAClE,MAAIc,yBAAyB;AAC7B,QAAMC,cAAc,IAAIlC,kBAAAA;AAExB,MAAImB,OAAOgB,cAAcjB,OAAOkB,aAAa;AAC3C5B,iBAAa0B,WAAAA;AACbvB,iBAAauB,WAAAA;AACbzB,oBAAgByB,WAAAA;EAClB;AAEA,SAAO,OAAOG,KAAcC,KAAeC,SAAAA;AACzC,QAAI,CAACpB,OAAOqB,UAAS,KAAMH,IAAII,OAAOC,YAAW,MAAO,WAAW;AACjEH,WAAAA;AACA;IACF;AAEA,QAAI,CAACN,wBAAwB;AAE3BhB,UAAIK,IAAI,CAACqB,KAAYN,MAAcC,MAAeC,UAAAA;AAChDD,QAAAA,KAAIM,OAAOC,cAAcF;AACzBJ,QAAAA,MAAKI,GAAAA;MACP,CAAA;AACAV,+BAAyB;IAC3B;AAEA,QAAId,OAAOgB,cAAcjB,OAAOkB,eAAe,SAASC,KAAK;AAC3D,YAAM3B,gBAAiB2B,IAAYS,KAAKZ,WAAAA;IAC1C;AAEAA,gBAAYa,IAAI,CAAA,GAAI,MAAA;AAClB,UAAI;AACF,cAAMC,YAAY/C,YAAYgD,IAAG;AAEjC,cAAMC,eAAeZ,IAAIa;AACzBb,YAAIa,OAAO,CAACC,SAAAA;AACV,gBAAMC,cAAcf,IAAIgB,IAAI,cAAA;AAC5B,cAAInC,OAAOgB,cAAcoB,uBAAuBF,WAAAA,GAAc;AAC5Df,gBAAIM,OAAOQ,OAAOA;UACpB;AACA,iBAAOF,aAAaM,KAAKlB,KAAKc,IAAAA;QAChC;AAEA,cAAMK,aAAatC,OAAOuC,cAAcC,UAAS;AACjDF,mBAAWG,aAAa,MAAA;AACtBtB,cAAIuB,KAAK,UAAU,MAAA;AACjB,gBAAI;AACF,oBAAMC,eAAe7D,YAAYgD,IAAG,IAAKD;AACzC,oBAAMe,OAAOC,aAAa3B,GAAAA;AAE1BoB,yBAAWQ,QAAQ,GAAG5B,IAAII,MAAM,IAAIsB,IAAAA,EAAM;AAC1C,oBAAMG,QAAQT,WAAWU,IAAG;AAC5B,oBAAMC,UAAUX,WAAWW;AAE3B,oBAAMC,WAAWC,YAAYjC,GAAAA;AAC7BlB,qBAAOoD,iBAAiBC,oBAAoBH,QAAAA;AAE5C,oBAAMI,cAAcrE,mBAAmBiC,IAAIiB,IAAI,gBAAA,CAAA;AAC/C,oBAAMoB,eAAetE,mBACnBkC,IAAIgB,IAAI,gBAAA,CAAA;AAGV,kBAAIS,MAAM;AACR5C,uBAAOwD,eAAeC,WAAW;kBAC/BP,UAAUA,qCAAUQ;kBACpBpC,QAAQJ,IAAII;kBACZsB;kBACAe,YAAYxC,IAAIwC;kBAChBhB;kBACAW;kBACAC;gBACF,CAAA;AAEA,qBACGpC,IAAIwC,eAAe,OAAOxC,IAAIwC,eAAe,QAC9CxC,IAAIM,OAAOQ,MACX;AACA,sBAAI2B;AACJ,sBAAI;AACFA,+BAAWC,KAAKC,MAAM3C,IAAIM,OAAOQ,IAAI;kBACvC,QAAQ;kBAER;AACA,sBAAI2B,UAAU;AACZ,0BAAMG,mBAAsC,CAAA;AAC5C,wBAAIA,iBAAiBpD,WAAW,GAAG;AACjCoD,uCAAiBC,KAAI,GAChBC,8BAA8BL,QAAAA,CAAAA;oBAErC;AACA,wBAAIG,iBAAiBpD,WAAW,GAAG;AACjCoD,uCAAiBC,KAAI,GAChBE,uBAAuBN,QAAAA,CAAAA;oBAE9B;AACA,wBAAIG,iBAAiBpD,WAAW,GAAG;AACjCoD,uCAAiBC,KAAI,GAChBG,4BAA4BP,QAAAA,CAAAA;oBAEnC;AACAG,qCAAiBK,QAAQ,CAACC,UAAAA;AACxBrE,6BAAOsE,uBAAuBC,mBAAmB;wBAC/CrB,UAAUA,qCAAUQ;wBACpBpC,QAAQJ,IAAII;wBACZsB;wBACA,GAAGyB;sBACL,CAAA;oBACF,CAAA;kBACF;gBACF;AAEA,oBAAIlD,IAAIwC,eAAe,OAAOxC,IAAIM,OAAOC,aAAa;AACpD,wBAAMA,cAAcP,IAAIM,OAAOC;AAC/B1B,yBAAOwE,mBAAmBC,eAAe;oBACvCvB,UAAUA,qCAAUQ;oBACpBpC,QAAQJ,IAAII;oBACZsB;oBACA8B,MAAMhD,YAAYiD;oBAClBC,KAAKlD,YAAYmD;oBACjBC,WAAWpD,YAAYqD,SAAS;kBAClC,CAAA;gBACF;cACF;AAEA,kBAAI/E,OAAOgB,cAAcgE,SAAS;AAChC,sBAAMC,OAAOlE,YAAYmE,SAAQ;AACjClF,uBAAOgB,cAAcmE,WACnB;kBACEC,WAAWC,KAAKvD,IAAG,IAAK;kBACxBR,QAAQJ,IAAII;kBACZsB;kBACA0C,KAAK,GAAGpE,IAAIqE,QAAQ,MAAMrE,IAAIsE,IAAI,GAAGtE,IAAIuE,WAAW;kBACpDC,SAAStG,eAAe8B,IAAIwE,OAAO;kBACnCC,MAAMrC;kBACNJ,UAAUA,qCAAUQ;kBACpBzB,MAAM9C,YAAY+B,IAAIe,MAAMf,IAAIiB,IAAI,cAAA,CAAA;gBACtC,GACA;kBACEwB,YAAYxC,IAAIwC;kBAChBhB,cAAcA,eAAe;kBAC7B+C,SAAStG,eAAe+B,IAAIyE,WAAU,CAAA;kBACtCD,MAAMpC;kBACNtB,MAAM9C,YAAYgC,IAAIM,OAAOQ,MAAMd,IAAIgB,IAAI,cAAA,CAAA;gBAC7C,GACAhB,IAAIM,OAAOC,aACXuD,MACAlC,OACAE,OAAAA;cAEJ;YACF,SAASoB,OAAO;AACdrE,qBAAO6F,OAAOxB,MACZ,sDACA;gBAAEyB,SAAS5E;gBAAK6E,UAAU5E;gBAAKkD;cAAM,CAAA;YAEzC;UACF,CAAA;AAEAlD,cAAIuB,KAAK,SAAS,MAAA;AAChBJ,uBAAWU,IAAG;UAChB,CAAA;AAEA5B,eAAAA;QACF,CAAA;MACF,SAASiD,OAAO;AACdrE,eAAO6F,OAAOxB,MAAM,gCAAgC;UAClDyB,SAAS5E;UACT6E,UAAU5E;UACVkD;QACF,CAAA;MACF;IACF,CAAA;EACF;AACF;AA5KSnE;AA8KT,SAAS2C,aAAa3B,KAAY;AAChC,MAAI,CAACA,IAAI8E,OAAO;AACd;EACF;AACA,MAAI9E,IAAI+E,SAAS;AACf,UAAMC,aAAaxG,cAAcwB,IAAIpB,GAAG;AACxC,QAAIoG,WAAWnB,OAAO;AACpB,YAAMoB,aAAaC,cAAcF,WAAWnB,OAAO7D,IAAI+E,OAAO;AAC9D,aAAO/E,IAAI8E,MAAMpD,SAAS,MAAMuD,aAAaA,aAAajF,IAAI8E,MAAMpD;IACtE;EACF;AACA,SAAO1B,IAAI8E,MAAMpD;AACnB;AAZSC;AAcT,SAASuD,cAAcrB,OAAckB,SAAe;AAnPpD;AAoPE,QAAMI,cAAwB,CAAA;AAC9B,SAAOtB,SAASA,MAAMpE,SAAS,GAAG;AAChC,UAAM2F,cAAcvB,MAAMwB,KACxB,CAACC,UAAAA;AAvPP,UAAAC;AAwPQD,mBAAM7B,SAAS,YACf6B,MAAM5D,SACLqD,QAAQS,WAAWF,MAAM5D,IAAI,OAAK4D,MAAAA,MAAMG,WAANH,gBAAAA,IAAcI,KAAKX;KAAO;AAEjE,QAAIK,aAAa;AACf,UACEA,YAAYK,UACZL,YAAYO,QACZP,YAAYO,KAAKlG,SAAS,GAC1B;AACA,cAAMmG,aAAalH,uBACjB0G,YAAYK,QACZL,YAAYO,IAAI;AAElBR,oBAAYrC,KAAK,MAAM8C,UAAAA;MACzB,WACER,YAAYS,UACZC,OAAOH,KAAKP,YAAYS,MAAM,EAAEpG,SAAS,GACzC;AACA,cAAMmG,aAAanH,iBACjB2G,YAAY1D,MACZ0D,YAAYS,MAAM;AAEpBV,oBAAYrC,KAAK8C,UAAAA;MACnB,OAAO;AACLT,oBAAYrC,KAAKsC,YAAY1D,IAAI;MACnC;AACAmC,eAAQuB,iBAAYW,WAAZX,mBAAoBvB;AAC5BkB,gBAAUA,QAAQiB,MAAMZ,YAAY1D,KAAKjC,MAAM;IACjD,OAAO;AACL;IACF;EACF;AACA,SAAO0F,YAAYc,OAAO,CAACvE,SAASA,SAAS,GAAA,EAAKwE,KAAK,EAAA;AACzD;AAvCShB;AAyCF,SAASiB,YACdnG,KACAgC,UAAsD;AAEtDhC,MAAIoG,mBAAmBpE,YAAYqE;AACrC;AALgBF;AAOhB,SAASlE,YAAYjC,KAAY;AAC/B,MAAIA,IAAIoG,kBAAkB;AACxB,WAAOtI,2BAA2BkC,IAAIoG,gBAAgB;EACxD,WAAWpG,IAAIsG,oBAAoB;AAEjCC,YAAQC,YACN,sGACA,oBAAA;AAEF,WAAO1I,2BAA2BkC,IAAIsG,kBAAkB;EAC1D;AACA,SAAO;AACT;AAZSrE;AAcT,SAASc,8BAA8B0D,cAAiB;AACtD,MAAI;AACF,UAAMC,SAA4B,CAAA;AAClC,QACED,gBACAA,aAAaC,UACbC,MAAMC,QAAQH,aAAaC,MAAM,GACjC;AACAD,mBAAaC,OAAOxD,QAAQ,CAACC,UAAAA;AAC3B,YAAIA,MAAM0D,YAAY1D,MAAMzB,QAAQyB,MAAMO,OAAOP,MAAMK,MAAM;AAC3DkD,iBAAO5D,KAAK;YACVgE,KAAK,GAAG3D,MAAM0D,QAAQ,IAAI1D,MAAMzB,IAAI;YACpCgC,KAAKP,MAAMO;YACXF,MAAML,MAAMK;UACd,CAAA;QACF;MACF,CAAA;IACF;AACA,WAAOkD;EACT,SAASvD,OAAO;AACd,WAAO,CAAA;EACT;AACF;AAtBSJ;AAwBT,SAASC,uBAAuByD,cAAiB;AAC/C,MAAI;AACF,UAAMC,SAA4B,CAAA;AAClC,QAAID,gBAAgBA,aAAaM,YAAY;AAC3CjB,aAAOkB,OAAOP,aAAaM,UAAU,EAAE7D,QAAQ,CAACC,UAAAA;AAC9C,YACEA,MAAM8D,UACN9D,MAAMwC,QACNgB,MAAMC,QAAQzD,MAAMwC,IAAI,KACxBxC,MAAMQ,SACN;AACAR,gBAAMwC,KAAKzC,QAAQ,CAACgE,QAAAA;AAClBR,mBAAO5D,KAAK;cACVgE,KAAK,GAAG3D,MAAM8D,MAAM,IAAIC,GAAAA;cACxBxD,KAAKyD,iBAAiBhE,MAAMQ,SAASuD,GAAAA;cACrC1D,MAAM;YACR,CAAA;UACF,CAAA;QACF;MACF,CAAA;IACF;AACA,WAAOkD;EACT,SAASvD,OAAO;AACd,WAAO,CAAA;EACT;AACF;AAzBSH;AA2BT,SAASC,4BAA4BwD,cAAiB;AACpD,MAAI;AACF,UAAMC,SAA4B,CAAA;AAClC,QAAID,gBAAgBE,MAAMC,QAAQH,aAAa9C,OAAO,GAAG;AACvD8C,mBAAa9C,QAAQT,QAAQ,CAACS,YAAAA;AAC5B+C,eAAO5D,KAAK;UACVgE,KAAK;UACLpD,KAAKC;UACLH,MAAM;QACR,CAAA;MACF,CAAA;IACF;AACA,WAAOkD;EACT,SAASvD,OAAO;AACd,WAAO,CAAA;EACT;AACF;AAhBSF;AAkBT,SAASkE,iBAAiBxD,SAAiBuD,KAAW;AACpD,QAAME,iBAAiBzD,QACpB0D,MAAM,IAAA,EACNhC,KAAK,CAAC1B,aAAYA,SAAQ2D,SAAS,IAAIJ,GAAAA,GAAM,CAAA;AAChD,SAAOE,iBAAiBA,iBAAiBzD;AAC3C;AALSwD;AAOT,SAAS9H,WACPT,KACAU,UACAC,YAAmB;AAEnB,QAAMgI,WAAoC;IACxC;MAAC;MAAUhB,QAAQiB,QAAQC,QAAQ,MAAM,EAAA;;;AAE3C,QAAMC,iBAAiB1J,kBAAkB,SAAA;AACzC,QAAM2J,gBAAgB3J,kBAAkB,cAAA;AACxC,QAAM4J,kBAAkB5J,kBAAkB,OAAA;AAC1C,MAAI0J,gBAAgB;AAClBH,aAASzE,KAAK;MAAC;MAAW4E;KAAe;EAC3C;AACA,MAAIC,eAAe;AACjBJ,aAASzE,KAAK;MAAC;MAAU6E;KAAc;EACzC;AACA,MAAIC,iBAAiB;AACnBL,aAASzE,KAAK;MAAC;MAAY8E;KAAgB;EAC7C;AACA,MAAIrI,YAAY;AACdgI,aAASzE,KAAK;MAAC;MAAOvD;KAAW;EACnC;AACA,SAAO;IACLC,OAAOjB,aAAaK,KAAKU,YAAY,EAAA;IACrCiI,UAAUzB,OAAO+B,YAAYN,QAAAA;IAC7BzI,QAAQ;EACV;AACF;AA5BSO;","names":["AsyncLocalStorage","performance","ApitallyClient","consumerFromStringOrObject","parseContentLength","getPackageVersion","convertBody","convertHeaders","patchConsole","patchNestLogger","patchPinoLogger","patchWinston","getEndpoints","getRouterInfo","parseExpressPath","parseExpressPathRegExp","useApitally","app","config","client","middleware","getMiddleware","use","setStartupData","attempt","appInfo","getAppInfo","basePath","appVersion","paths","length","startSync","setTimeout","errorHandlerConfigured","logsContext","requestLogger","captureLogs","req","res","next","isEnabled","method","toUpperCase","err","locals","serverError","log","run","startTime","now","originalSend","send","body","contentType","get","isSupportedContentType","call","spanHandle","spanCollector","startSpan","runInContext","once","responseTime","path","getRoutePath","setName","spans","end","traceId","consumer","getConsumer","consumerRegistry","addOrUpdateConsumer","requestSize","responseSize","requestCounter","addRequest","identifier","statusCode","jsonBody","JSON","parse","validationErrors","push","extractExpressValidatorErrors","extractCelebrateErrors","extractNestValidationErrors","forEach","error","validationErrorCounter","addValidationError","serverErrorCounter","addServerError","type","name","msg","message","traceback","stack","enabled","logs","getStore","logRequest","timestamp","Date","url","protocol","host","originalUrl","headers","size","getHeaders","logger","request","response","route","baseUrl","routerInfo","routerPath","getRouterPath","routerPaths","routerLayer","find","layer","_a","startsWith","regexp","test","keys","parsedPath","params","Object","handle","slice","filter","join","setConsumer","apitallyConsumer","undefined","consumerIdentifier","process","emitWarning","responseBody","errors","Array","isArray","location","loc","validation","values","source","key","subsetJoiMessage","messageWithKey","split","includes","versions","version","replace","expressVersion","nestjsVersion","apitallyVersion","fromEntries"]}
1
+ {"version":3,"sources":["../../src/express/middleware.ts"],"sourcesContent":["import type { Express, NextFunction, Request, Response, Router } from \"express\";\nimport { AsyncLocalStorage } from \"node:async_hooks\";\nimport { performance } from \"node:perf_hooks\";\n\nimport { ApitallyClient } from \"../common/client.js\";\nimport { consumerFromStringOrObject } from \"../common/consumerRegistry.js\";\nimport { parseContentLength } from \"../common/headers.js\";\nimport { getPackageVersion } from \"../common/packageVersions.js\";\nimport type { LogRecord } from \"../common/requestLogger.js\";\nimport { convertBody, convertHeaders } from \"../common/requestLogger.js\";\nimport {\n ApitallyConfig,\n ApitallyConsumer,\n StartupData,\n ValidationError,\n} from \"../common/types.js\";\nimport {\n patchConsole,\n patchNestLogger,\n patchPinoLogger,\n patchWinston,\n} from \"../loggers/index.js\";\nimport {\n formatRegExpRoutePath,\n getEndpoints,\n getRouterInfo,\n parseExpressPath,\n parseExpressPathRegExp,\n stripExpressPathParamRegex,\n} from \"./utils.js\";\n\ndeclare module \"express\" {\n interface Request {\n apitallyConsumer?: ApitallyConsumer | string | null;\n consumerIdentifier?: ApitallyConsumer | string | null; // For backwards compatibility\n }\n}\n\nexport function useApitally(\n app: Express | Router,\n config: ApitallyConfig & { basePath?: string },\n) {\n const client = new ApitallyClient(config);\n const middleware = getMiddleware(app, client);\n app.use(middleware);\n\n const setStartupData = (attempt: number = 1) => {\n const appInfo = getAppInfo(app, config.basePath, 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\nfunction getMiddleware(app: Express | Router, client: ApitallyClient) {\n let errorHandlerConfigured = false;\n const logsContext = new AsyncLocalStorage<LogRecord[]>();\n\n if (client.requestLogger.config.captureLogs) {\n patchConsole(logsContext);\n patchWinston(logsContext);\n patchNestLogger(logsContext);\n }\n\n return async (req: Request, res: Response, next: NextFunction) => {\n if (!client.isEnabled() || req.method.toUpperCase() === \"OPTIONS\") {\n next();\n return;\n }\n\n if (!errorHandlerConfigured) {\n // Add error handling middleware to the bottom of the stack when handling the first request\n app.use((err: Error, req: Request, res: Response, next: NextFunction) => {\n res.locals.serverError = err;\n next(err);\n });\n errorHandlerConfigured = true;\n }\n\n if (client.requestLogger.config.captureLogs && \"log\" in req) {\n await patchPinoLogger((req as any).log, logsContext);\n }\n\n logsContext.run([], () => {\n try {\n const startTime = performance.now();\n\n const originalSend = res.send;\n res.send = (body) => {\n const contentType = res.get(\"content-type\");\n if (client.requestLogger.isSupportedContentType(contentType)) {\n res.locals.body = body;\n }\n return originalSend.call(res, body);\n };\n\n const spanHandle = client.spanCollector.startSpan();\n spanHandle.runInContext(() => {\n res.once(\"finish\", () => {\n try {\n const responseTime = performance.now() - startTime;\n const path = getRoutePath(req);\n\n spanHandle.setName(`${req.method} ${path}`);\n const spans = spanHandle.end();\n const traceId = spanHandle.traceId;\n\n const consumer = getConsumer(req);\n client.consumerRegistry.addOrUpdateConsumer(consumer);\n\n const requestSize = parseContentLength(req.get(\"content-length\"));\n const responseSize = parseContentLength(\n res.get(\"content-length\"),\n );\n\n if (path) {\n client.requestCounter.addRequest({\n consumer: consumer?.identifier,\n method: req.method,\n path,\n statusCode: res.statusCode,\n responseTime: responseTime,\n requestSize,\n responseSize,\n });\n\n if (\n (res.statusCode === 400 || res.statusCode === 422) &&\n res.locals.body\n ) {\n let jsonBody: any;\n try {\n jsonBody = JSON.parse(res.locals.body);\n } catch {\n // Ignore\n }\n if (jsonBody) {\n const validationErrors: ValidationError[] = [];\n if (validationErrors.length === 0) {\n validationErrors.push(\n ...extractExpressValidatorErrors(jsonBody),\n );\n }\n if (validationErrors.length === 0) {\n validationErrors.push(\n ...extractCelebrateErrors(jsonBody),\n );\n }\n if (validationErrors.length === 0) {\n validationErrors.push(\n ...extractNestValidationErrors(jsonBody),\n );\n }\n validationErrors.forEach((error) => {\n client.validationErrorCounter.addValidationError({\n consumer: consumer?.identifier,\n method: req.method,\n path,\n ...error,\n });\n });\n }\n }\n\n if (res.statusCode === 500 && res.locals.serverError) {\n const serverError = res.locals.serverError as Error;\n client.serverErrorCounter.addServerError({\n consumer: consumer?.identifier,\n method: req.method,\n path,\n type: serverError.name,\n msg: serverError.message,\n traceback: serverError.stack || \"\",\n });\n }\n }\n\n if (client.requestLogger.enabled) {\n const logs = logsContext.getStore();\n client.requestLogger.logRequest(\n {\n timestamp: Date.now() / 1000,\n method: req.method,\n path,\n url: `${req.protocol}://${req.host}${req.originalUrl}`,\n headers: convertHeaders(req.headers),\n size: requestSize,\n consumer: consumer?.identifier,\n body: convertBody(req.body, req.get(\"content-type\")),\n },\n {\n statusCode: res.statusCode,\n responseTime: responseTime / 1000,\n headers: convertHeaders(res.getHeaders()),\n size: responseSize,\n body: convertBody(res.locals.body, res.get(\"content-type\")),\n },\n res.locals.serverError,\n logs,\n spans,\n traceId,\n );\n }\n } catch (error) {\n client.logger.error(\n \"Error while logging request in Apitally middleware\",\n { request: req, response: res, error },\n );\n }\n });\n\n res.once(\"close\", () => {\n spanHandle.end();\n });\n\n next();\n });\n } catch (error) {\n client.logger.error(\"Error in Apitally middleware\", {\n request: req,\n response: res,\n error,\n });\n }\n });\n };\n}\n\nfunction getRoutePath(req: Request) {\n if (!req.route) {\n return;\n }\n // req.route.path can be a string, a RegExp, or an array of either\n let routePath: unknown = req.route.path;\n if (Array.isArray(routePath)) {\n routePath = routePath.find((p) => typeof p === \"string\") ?? routePath[0];\n }\n if (typeof routePath === \"string\") {\n routePath = stripExpressPathParamRegex(routePath);\n } else if (routePath instanceof RegExp) {\n routePath = formatRegExpRoutePath(routePath);\n }\n if (typeof routePath !== \"string\") {\n return;\n }\n if (req.baseUrl) {\n const routerInfo = getRouterInfo(req.app);\n if (routerInfo.stack) {\n const routerPath = getRouterPath(routerInfo.stack, req.baseUrl);\n return routePath === \"/\" ? routerPath : routerPath + routePath;\n }\n }\n return routePath;\n}\n\nfunction getRouterPath(stack: any[], baseUrl: string) {\n const routerPaths: string[] = [];\n while (stack && stack.length > 0) {\n const routerLayer = stack.find(\n (layer) =>\n layer.name === \"router\" &&\n layer.path &&\n (baseUrl.startsWith(layer.path) || layer.regexp?.test(baseUrl)),\n );\n if (routerLayer) {\n if (\n routerLayer.regexp &&\n routerLayer.keys &&\n routerLayer.keys.length > 0\n ) {\n const parsedPath = parseExpressPathRegExp(\n routerLayer.regexp,\n routerLayer.keys,\n );\n routerPaths.push(\"/\" + parsedPath);\n } else if (\n routerLayer.params &&\n Object.keys(routerLayer.params).length > 0\n ) {\n const parsedPath = parseExpressPath(\n routerLayer.path,\n routerLayer.params,\n );\n routerPaths.push(parsedPath);\n } else {\n routerPaths.push(routerLayer.path);\n }\n stack = routerLayer.handle?.stack;\n baseUrl = baseUrl.slice(routerLayer.path.length);\n } else {\n break;\n }\n }\n return routerPaths.filter((path) => path !== \"/\").join(\"\");\n}\n\nexport function setConsumer(\n req: Request,\n consumer: ApitallyConsumer | string | null | undefined,\n) {\n req.apitallyConsumer = consumer || undefined;\n}\n\nfunction getConsumer(req: Request) {\n if (req.apitallyConsumer) {\n return consumerFromStringOrObject(req.apitallyConsumer);\n } else if (req.consumerIdentifier) {\n // For backwards compatibility\n process.emitWarning(\n \"The consumerIdentifier property on the request object is deprecated. Use apitallyConsumer instead.\",\n \"DeprecationWarning\",\n );\n return consumerFromStringOrObject(req.consumerIdentifier);\n }\n return null;\n}\n\nfunction extractExpressValidatorErrors(responseBody: any) {\n try {\n const errors: ValidationError[] = [];\n if (\n responseBody &&\n responseBody.errors &&\n Array.isArray(responseBody.errors)\n ) {\n responseBody.errors.forEach((error: any) => {\n if (error.location && error.path && error.msg && error.type) {\n errors.push({\n loc: `${error.location}.${error.path}`,\n msg: error.msg,\n type: error.type,\n });\n }\n });\n }\n return errors;\n } catch (error) {\n return [];\n }\n}\n\nfunction extractCelebrateErrors(responseBody: any) {\n try {\n const errors: ValidationError[] = [];\n if (responseBody && responseBody.validation) {\n Object.values(responseBody.validation).forEach((error: any) => {\n if (\n error.source &&\n error.keys &&\n Array.isArray(error.keys) &&\n error.message\n ) {\n error.keys.forEach((key: string) => {\n errors.push({\n loc: `${error.source}.${key}`,\n msg: subsetJoiMessage(error.message, key),\n type: \"\",\n });\n });\n }\n });\n }\n return errors;\n } catch (error) {\n return [];\n }\n}\n\nfunction extractNestValidationErrors(responseBody: any) {\n try {\n const errors: ValidationError[] = [];\n if (responseBody && Array.isArray(responseBody.message)) {\n responseBody.message.forEach((message: any) => {\n errors.push({\n loc: \"\",\n msg: message,\n type: \"\",\n });\n });\n }\n return errors;\n } catch (error) {\n return [];\n }\n}\n\nfunction subsetJoiMessage(message: string, key: string) {\n const messageWithKey = message\n .split(\". \")\n .find((message) => message.includes(`\"${key}\"`));\n return messageWithKey ? messageWithKey : message;\n}\n\nfunction getAppInfo(\n app: Express | Router,\n basePath?: string,\n appVersion?: string,\n): StartupData {\n const versions: Array<[string, string]> = [\n [\"nodejs\", process.version.replace(/^v/, \"\")],\n ];\n const expressVersion = getPackageVersion(\"express\");\n const nestjsVersion = getPackageVersion(\"@nestjs/core\");\n const apitallyVersion = getPackageVersion(\"../..\");\n if (expressVersion) {\n versions.push([\"express\", expressVersion]);\n }\n if (nestjsVersion) {\n versions.push([\"nestjs\", nestjsVersion]);\n }\n if (apitallyVersion) {\n versions.push([\"apitally\", apitallyVersion]);\n }\n if (appVersion) {\n versions.push([\"app\", appVersion]);\n }\n return {\n paths: getEndpoints(app, basePath || \"\"),\n versions: Object.fromEntries(versions),\n client: \"js:express\",\n };\n}\n"],"mappings":"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","names":["AsyncLocalStorage","performance","ApitallyClient","consumerFromStringOrObject","parseContentLength","getPackageVersion","convertBody","convertHeaders","patchConsole","patchNestLogger","patchPinoLogger","patchWinston","formatRegExpRoutePath","getEndpoints","getRouterInfo","parseExpressPath","parseExpressPathRegExp","stripExpressPathParamRegex","useApitally","app","config","client","middleware","getMiddleware","use","setStartupData","attempt","appInfo","getAppInfo","basePath","appVersion","paths","length","startSync","setTimeout","errorHandlerConfigured","logsContext","requestLogger","captureLogs","req","res","next","isEnabled","method","toUpperCase","err","locals","serverError","log","run","startTime","now","originalSend","send","body","contentType","get","isSupportedContentType","call","spanHandle","spanCollector","startSpan","runInContext","once","responseTime","path","getRoutePath","setName","spans","end","traceId","consumer","getConsumer","consumerRegistry","addOrUpdateConsumer","requestSize","responseSize","requestCounter","addRequest","identifier","statusCode","jsonBody","JSON","parse","validationErrors","push","extractExpressValidatorErrors","extractCelebrateErrors","extractNestValidationErrors","forEach","error","validationErrorCounter","addValidationError","serverErrorCounter","addServerError","type","name","msg","message","traceback","stack","enabled","logs","getStore","logRequest","timestamp","Date","url","protocol","host","originalUrl","headers","size","getHeaders","logger","request","response","route","routePath","Array","isArray","find","p","RegExp","baseUrl","routerInfo","routerPath","getRouterPath","routerPaths","routerLayer","layer","_a","startsWith","regexp","test","keys","parsedPath","params","Object","handle","slice","filter","join","setConsumer","apitallyConsumer","undefined","consumerIdentifier","process","emitWarning","responseBody","errors","location","loc","validation","values","source","key","subsetJoiMessage","messageWithKey","split","includes","versions","version","replace","expressVersion","nestjsVersion","apitallyVersion","fromEntries"]}
@@ -19,22 +19,30 @@ var __copyProps = (to, from, except, desc) => {
19
19
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
20
  var utils_exports = {};
21
21
  __export(utils_exports, {
22
+ formatRegExpRoutePath: () => formatRegExpRoutePath,
22
23
  getEndpoints: () => getEndpoints,
23
24
  getRouterInfo: () => getRouterInfo,
24
25
  parseExpressPath: () => parseExpressPath,
25
- parseExpressPathRegExp: () => parseExpressPathRegExp
26
+ parseExpressPathRegExp: () => parseExpressPathRegExp,
27
+ stripExpressPathParamRegex: () => stripExpressPathParamRegex
26
28
  });
27
29
  module.exports = __toCommonJS(utils_exports);
28
30
  const regExpToParseExpressPathRegExp = /^\/\^\\?\/?(?:(:?[\w\\.-]*(?:\\\/:?[\w\\.-]*)*)|(\(\?:\\?\/?\([^)]+\)\)))\\\/.*/;
29
31
  const regExpToReplaceExpressPathRegExpParams = /\(\?:\\?\/?\([^)]+\)\)/;
30
- const regexpExpressParamRegexp = /\(\?:\\?\\?\/?\([^)]+\)\)/g;
31
- const regexpExpressPathParamRegexp = /(:[^)]+)\([^)]+\)/g;
32
- const EXPRESS_ROOT_PATH_REGEXP_VALUE = "/^\\/?(?=\\/|$)/i";
33
- const STACK_ITEM_VALID_NAMES = [
32
+ const regExpExpressParamRegExp = /\(\?:\\?\\?\/?\([^)]+\)\)/g;
33
+ const regExpExpressPathParamRegExp = /(:[^)]+)\([^)]+\)/g;
34
+ const rootPathRegExpValue = "/^\\/?(?=\\/|$)/i";
35
+ const stackItemValidNames = [
34
36
  "router",
35
37
  "bound dispatch",
36
38
  "mounted_app"
37
39
  ];
40
+ const stripExpressPathParamRegex = /* @__PURE__ */ __name(function(path) {
41
+ return path.replace(regExpExpressPathParamRegExp, "$1");
42
+ }, "stripExpressPathParamRegex");
43
+ const formatRegExpRoutePath = /* @__PURE__ */ __name(function(re) {
44
+ return `RegExp(${re})`;
45
+ }, "formatRegExpRoutePath");
38
46
  const getRouterInfo = /* @__PURE__ */ __name(function(app) {
39
47
  var _a, _b;
40
48
  if (app.stack) {
@@ -70,23 +78,29 @@ const getRouteMiddlewares = /* @__PURE__ */ __name(function(route) {
70
78
  });
71
79
  }, "getRouteMiddlewares");
72
80
  const hasParams = /* @__PURE__ */ __name(function(expressPathRegExp) {
73
- return regexpExpressParamRegexp.test(expressPathRegExp);
81
+ return regExpExpressParamRegExp.test(expressPathRegExp);
74
82
  }, "hasParams");
75
83
  const parseExpressRoute = /* @__PURE__ */ __name(function(route, basePath) {
76
- const paths = [];
77
- if (Array.isArray(route.path)) {
78
- paths.push(...route.path);
79
- } else {
80
- paths.push(route.path);
81
- }
82
- const endpoints = paths.map((path) => {
83
- const completePath = basePath && path === "/" ? basePath : `${basePath}${path}`;
84
- const endpoint = {
85
- path: completePath.replace(regexpExpressPathParamRegexp, "$1"),
86
- methods: getRouteMethods(route),
87
- middlewares: getRouteMiddlewares(route)
88
- };
89
- return endpoint;
84
+ const paths = Array.isArray(route.path) ? route.path : [
85
+ route.path
86
+ ];
87
+ const endpoints = paths.flatMap((path) => {
88
+ let pathStr;
89
+ if (typeof path === "string") {
90
+ pathStr = stripExpressPathParamRegex(path);
91
+ } else if (path instanceof RegExp) {
92
+ pathStr = formatRegExpRoutePath(path);
93
+ } else {
94
+ return [];
95
+ }
96
+ const completePath = basePath && pathStr === "/" ? basePath : `${basePath}${pathStr}`;
97
+ return [
98
+ {
99
+ path: completePath,
100
+ methods: getRouteMethods(route),
101
+ middlewares: getRouteMiddlewares(route)
102
+ }
103
+ ];
90
104
  });
91
105
  return endpoints;
92
106
  }, "parseExpressRoute");
@@ -155,16 +169,15 @@ const parseStack = /* @__PURE__ */ __name(function(stack, basePath, endpoints, v
155
169
  if (stackItem.route) {
156
170
  const newEndpoints = parseExpressRoute(stackItem.route, basePath);
157
171
  endpoints = addEndpoints(endpoints, newEndpoints);
158
- } else if (STACK_ITEM_VALID_NAMES.includes(stackItem.name)) {
172
+ } else if (stackItemValidNames.includes(stackItem.name)) {
159
173
  let newBasePath = basePath;
160
174
  if (version === "v4") {
161
175
  const isExpressPathRegExp = regExpToParseExpressPathRegExp.test(stackItem.regexp);
162
176
  if (isExpressPathRegExp) {
163
177
  const parsedPath = parseExpressPathRegExp(stackItem.regexp, stackItem.keys);
164
178
  newBasePath += `/${parsedPath}`;
165
- } else if (!stackItem.path && stackItem.regexp && stackItem.regexp.toString() !== EXPRESS_ROOT_PATH_REGEXP_VALUE) {
166
- const regExpPath = `RegExp(${stackItem.regexp})`;
167
- newBasePath += `/${regExpPath}`;
179
+ } else if (!stackItem.path && stackItem.regexp && stackItem.regexp.toString() !== rootPathRegExpValue) {
180
+ newBasePath += `/${formatRegExpRoutePath(stackItem.regexp)}`;
168
181
  }
169
182
  } else if (version === "v5") {
170
183
  if (!stackItem.path) {
@@ -194,9 +207,11 @@ const getEndpoints = /* @__PURE__ */ __name(function(app, basePath) {
194
207
  }, "getEndpoints");
195
208
  // Annotate the CommonJS export names for ESM import in node:
196
209
  0 && (module.exports = {
210
+ formatRegExpRoutePath,
197
211
  getEndpoints,
198
212
  getRouterInfo,
199
213
  parseExpressPath,
200
- parseExpressPathRegExp
214
+ parseExpressPathRegExp,
215
+ stripExpressPathParamRegex
201
216
  });
202
217
  //# sourceMappingURL=utils.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/express/utils.js"],"sourcesContent":["// Adapted from https://github.com/AlbertoFdzM/express-list-endpoints/blob/305535d43008b46f34e18b01947762e039af6d2d/src/index.js\n// and also incorporated https://github.com/AlbertoFdzM/express-list-endpoints/pull/96\n// and https://github.com/labithiotis/express-list-routes/blob/5432c83a67c2788c56a1cdfc067ec809961c0c05/index.js\n\n/**\n * @typedef {Object} Route\n * @property {Object} methods\n * @property {string | string[]} path\n * @property {any[]} stack\n *\n * @typedef {Object} Endpoint\n * @property {string} path Path name\n * @property {string[]} methods Methods handled\n * @property {string[]} middlewares Mounted middlewares\n */\n\nconst regExpToParseExpressPathRegExp =\n /^\\/\\^\\\\?\\/?(?:(:?[\\w\\\\.-]*(?:\\\\\\/:?[\\w\\\\.-]*)*)|(\\(\\?:\\\\?\\/?\\([^)]+\\)\\)))\\\\\\/.*/;\nconst regExpToReplaceExpressPathRegExpParams = /\\(\\?:\\\\?\\/?\\([^)]+\\)\\)/;\nconst regexpExpressParamRegexp = /\\(\\?:\\\\?\\\\?\\/?\\([^)]+\\)\\)/g;\nconst regexpExpressPathParamRegexp = /(:[^)]+)\\([^)]+\\)/g;\n\nconst EXPRESS_ROOT_PATH_REGEXP_VALUE = \"/^\\\\/?(?=\\\\/|$)/i\";\nconst STACK_ITEM_VALID_NAMES = [\"router\", \"bound dispatch\", \"mounted_app\"];\n\n/**\n * Detects Express version and returns router information\n * @param {import('express').Express | import('express').Router | any} app\n * @returns {{stack: any[] | null, version: 'v4' | 'v5'}}\n */\nexport const getRouterInfo = function (app) {\n if (app.stack) {\n // Express 4 router\n return { stack: app.stack, version: \"v4\" };\n } else if (app._router?.stack) {\n // Express 4\n return { stack: app._router.stack, version: \"v4\" };\n } else if (app.router?.stack) {\n // Express 5\n return { stack: app.router.stack, version: \"v5\" };\n }\n return { stack: null, version: \"v4\" };\n};\n\n/**\n * Returns all the verbs detected for the passed route\n * @param {Route} route\n */\nconst getRouteMethods = function (route) {\n let methods = Object.keys(route.methods);\n\n methods = methods.filter((method) => method !== \"_all\");\n methods = methods.map((method) => method.toUpperCase());\n\n return methods;\n};\n\n/**\n * Returns the names (or anonymous) of all the middlewares attached to the\n * passed route\n * @param {Route} route\n * @returns {string[]}\n */\nconst getRouteMiddlewares = function (route) {\n return route.stack.map((item) => {\n return item.handle.name || \"anonymous\";\n });\n};\n\n/**\n * Returns true if found regexp related with express params\n * @param {string} expressPathRegExp\n * @returns {boolean}\n */\nconst hasParams = function (expressPathRegExp) {\n return regexpExpressParamRegexp.test(expressPathRegExp);\n};\n\n/**\n * @param {Route} route Express route object to be parsed\n * @param {string} basePath The basePath the route is on\n * @return {Endpoint[]} Endpoints info\n */\nconst parseExpressRoute = function (route, basePath) {\n const paths = [];\n\n if (Array.isArray(route.path)) {\n paths.push(...route.path);\n } else {\n paths.push(route.path);\n }\n\n /** @type {Endpoint[]} */\n const endpoints = paths.map((path) => {\n const completePath =\n basePath && path === \"/\" ? basePath : `${basePath}${path}`;\n\n /** @type {Endpoint} */\n const endpoint = {\n path: completePath.replace(regexpExpressPathParamRegexp, \"$1\"),\n methods: getRouteMethods(route),\n middlewares: getRouteMiddlewares(route),\n };\n\n return endpoint;\n });\n\n return endpoints;\n};\n\n/**\n * @param {RegExp} expressPathRegExp\n * @param {any[]} keys\n * @returns {string}\n */\nexport const parseExpressPathRegExp = function (expressPathRegExp, keys) {\n let parsedRegExp = expressPathRegExp.toString();\n let expressPathRegExpExec = regExpToParseExpressPathRegExp.exec(parsedRegExp);\n let paramIndex = 0;\n\n while (hasParams(parsedRegExp)) {\n const paramName = keys[paramIndex].name;\n const paramId = `:${paramName}`;\n\n parsedRegExp = parsedRegExp.replace(\n regExpToReplaceExpressPathRegExpParams,\n (str) => {\n // Express >= 4.20.0 uses a different RegExp for parameters: it\n // captures the slash as part of the parameter. We need to check\n // for this case and add the slash to the value that will replace\n // the parameter in the path.\n if (str.startsWith(\"(?:\\\\/\")) {\n return `\\\\/${paramId}`;\n }\n\n return paramId;\n },\n );\n\n paramIndex++;\n }\n\n if (parsedRegExp !== expressPathRegExp.toString()) {\n expressPathRegExpExec = regExpToParseExpressPathRegExp.exec(parsedRegExp);\n }\n\n return expressPathRegExpExec[1].replace(/\\\\\\//g, \"/\");\n};\n\n/**\n * @param {string} expressPath\n * @param {Object.<string, string>} params\n * @returns {string}\n */\nexport const parseExpressPath = function (expressPath, params) {\n let result = expressPath;\n for (const [paramName, paramValue] of Object.entries(params)) {\n result = result.replace(paramValue, `:${paramName}`);\n }\n return result;\n};\n\n/**\n * @param {import('express').Express | import('express').Router | any} app\n * @param {string} [basePath]\n * @param {Endpoint[]} [endpoints]\n * @returns {Endpoint[]}\n */\nconst parseEndpoints = function (app, basePath, endpoints) {\n const routerInfo = getRouterInfo(app);\n const stack = routerInfo.stack;\n const version = routerInfo.version;\n\n endpoints = endpoints || [];\n basePath = basePath || \"\";\n\n if (!stack) {\n if (endpoints.length) {\n endpoints = addEndpoints(endpoints, [\n {\n path: basePath,\n methods: [],\n middlewares: [],\n },\n ]);\n }\n } else {\n endpoints = parseStack(stack, basePath, endpoints, version);\n }\n\n return endpoints;\n};\n\n/**\n * Ensures the path of the new endpoints isn't yet in the array.\n * If the path is already in the array merges the endpoints with the existing\n * one, if not, it adds them to the array.\n *\n * @param {Endpoint[]} currentEndpoints Array of current endpoints\n * @param {Endpoint[]} endpointsToAdd New endpoints to be added to the array\n * @returns {Endpoint[]} Updated endpoints array\n */\nconst addEndpoints = function (currentEndpoints, endpointsToAdd) {\n endpointsToAdd.forEach((newEndpoint) => {\n const existingEndpoint = currentEndpoints.find(\n (endpoint) => endpoint.path === newEndpoint.path,\n );\n\n if (existingEndpoint !== undefined) {\n const newMethods = newEndpoint.methods.filter(\n (method) => !existingEndpoint.methods.includes(method),\n );\n\n existingEndpoint.methods = existingEndpoint.methods.concat(newMethods);\n } else {\n currentEndpoints.push(newEndpoint);\n }\n });\n\n return currentEndpoints;\n};\n\n/**\n * @param {any[]} stack\n * @param {string} basePath\n * @param {Endpoint[]} endpoints\n * @param {'v4' | 'v5'} [version]\n * @returns {Endpoint[]}\n */\nconst parseStack = function (stack, basePath, endpoints, version) {\n stack.forEach((stackItem) => {\n if (stackItem.route) {\n const newEndpoints = parseExpressRoute(stackItem.route, basePath);\n\n endpoints = addEndpoints(endpoints, newEndpoints);\n } else if (STACK_ITEM_VALID_NAMES.includes(stackItem.name)) {\n let newBasePath = basePath;\n\n if (version === \"v4\") {\n const isExpressPathRegExp = regExpToParseExpressPathRegExp.test(\n stackItem.regexp,\n );\n if (isExpressPathRegExp) {\n const parsedPath = parseExpressPathRegExp(\n stackItem.regexp,\n stackItem.keys,\n );\n newBasePath += `/${parsedPath}`;\n } else if (\n !stackItem.path &&\n stackItem.regexp &&\n stackItem.regexp.toString() !== EXPRESS_ROOT_PATH_REGEXP_VALUE\n ) {\n const regExpPath = `RegExp(${stackItem.regexp})`;\n newBasePath += `/${regExpPath}`;\n }\n } else if (version === \"v5\") {\n if (!stackItem.path) {\n return;\n } else if (stackItem.path !== \"/\") {\n newBasePath += stackItem.path.startsWith(\"/\")\n ? stackItem.path\n : `/${stackItem.path}`;\n }\n }\n\n endpoints = parseEndpoints(stackItem.handle, newBasePath, endpoints);\n }\n });\n\n return endpoints;\n};\n\nexport const getEndpoints = function (app, basePath) {\n const endpoints = parseEndpoints(app);\n const standardHttpMethods = [\"GET\", \"POST\", \"PUT\", \"DELETE\", \"PATCH\"];\n return endpoints.flatMap((route) =>\n route.methods\n .filter((method) => standardHttpMethods.includes(method.toUpperCase()))\n .map((method) => ({\n method,\n path: (basePath + route.path).replace(/\\/\\//g, \"/\"),\n })),\n );\n};\n"],"mappings":"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","names":["regExpToParseExpressPathRegExp","regExpToReplaceExpressPathRegExpParams","regexpExpressParamRegexp","regexpExpressPathParamRegexp","EXPRESS_ROOT_PATH_REGEXP_VALUE","STACK_ITEM_VALID_NAMES","getRouterInfo","app","stack","version","_router","router","getRouteMethods","route","methods","Object","keys","filter","method","map","toUpperCase","getRouteMiddlewares","item","handle","name","hasParams","expressPathRegExp","test","parseExpressRoute","basePath","paths","Array","isArray","path","push","endpoints","completePath","endpoint","replace","middlewares","parseExpressPathRegExp","parsedRegExp","toString","expressPathRegExpExec","exec","paramIndex","paramName","paramId","str","startsWith","parseExpressPath","expressPath","params","result","paramValue","entries","parseEndpoints","routerInfo","length","addEndpoints","parseStack","currentEndpoints","endpointsToAdd","forEach","newEndpoint","existingEndpoint","find","undefined","newMethods","includes","concat","stackItem","newEndpoints","newBasePath","isExpressPathRegExp","regexp","parsedPath","regExpPath","getEndpoints","standardHttpMethods","flatMap"]}
1
+ {"version":3,"sources":["../../src/express/utils.js"],"sourcesContent":["// Adapted from https://github.com/AlbertoFdzM/express-list-endpoints/blob/305535d43008b46f34e18b01947762e039af6d2d/src/index.js\n// and also incorporated https://github.com/AlbertoFdzM/express-list-endpoints/pull/96\n// and https://github.com/labithiotis/express-list-routes/blob/5432c83a67c2788c56a1cdfc067ec809961c0c05/index.js\n\n/**\n * @typedef {Object} Route\n * @property {Object} methods\n * @property {string | string[]} path\n * @property {any[]} stack\n *\n * @typedef {Object} Endpoint\n * @property {string} path Path name\n * @property {string[]} methods Methods handled\n * @property {string[]} middlewares Mounted middlewares\n */\n\nconst regExpToParseExpressPathRegExp =\n /^\\/\\^\\\\?\\/?(?:(:?[\\w\\\\.-]*(?:\\\\\\/:?[\\w\\\\.-]*)*)|(\\(\\?:\\\\?\\/?\\([^)]+\\)\\)))\\\\\\/.*/;\nconst regExpToReplaceExpressPathRegExpParams = /\\(\\?:\\\\?\\/?\\([^)]+\\)\\)/;\nconst regExpExpressParamRegExp = /\\(\\?:\\\\?\\\\?\\/?\\([^)]+\\)\\)/g;\nconst regExpExpressPathParamRegExp = /(:[^)]+)\\([^)]+\\)/g;\nconst rootPathRegExpValue = \"/^\\\\/?(?=\\\\/|$)/i\";\nconst stackItemValidNames = [\"router\", \"bound dispatch\", \"mounted_app\"];\n\n/**\n * Strips inline RegExp validators from Express path params, e.g.\n * \"/users/:id(\\\\d+)\" → \"/users/:id\".\n * @param {string} path\n * @returns {string}\n */\nexport const stripExpressPathParamRegex = function (path) {\n return path.replace(regExpExpressPathParamRegExp, \"$1\");\n};\n\n/**\n * Formats a RegExp route as a stable string identifier, e.g.\n * /^\\/foo$/ → \"RegExp(/^\\\\/foo$/)\".\n * @param {RegExp} re\n * @returns {string}\n */\nexport const formatRegExpRoutePath = function (re) {\n return `RegExp(${re})`;\n};\n\n/**\n * Detects Express version and returns router information\n * @param {import('express').Express | import('express').Router | any} app\n * @returns {{stack: any[] | null, version: 'v4' | 'v5'}}\n */\nexport const getRouterInfo = function (app) {\n if (app.stack) {\n // Express 4 router\n return { stack: app.stack, version: \"v4\" };\n } else if (app._router?.stack) {\n // Express 4\n return { stack: app._router.stack, version: \"v4\" };\n } else if (app.router?.stack) {\n // Express 5\n return { stack: app.router.stack, version: \"v5\" };\n }\n return { stack: null, version: \"v4\" };\n};\n\n/**\n * Returns all the verbs detected for the passed route\n * @param {Route} route\n */\nconst getRouteMethods = function (route) {\n let methods = Object.keys(route.methods);\n\n methods = methods.filter((method) => method !== \"_all\");\n methods = methods.map((method) => method.toUpperCase());\n\n return methods;\n};\n\n/**\n * Returns the names (or anonymous) of all the middlewares attached to the\n * passed route\n * @param {Route} route\n * @returns {string[]}\n */\nconst getRouteMiddlewares = function (route) {\n return route.stack.map((item) => {\n return item.handle.name || \"anonymous\";\n });\n};\n\n/**\n * Returns true if found regexp related with express params\n * @param {string} expressPathRegExp\n * @returns {boolean}\n */\nconst hasParams = function (expressPathRegExp) {\n return regExpExpressParamRegExp.test(expressPathRegExp);\n};\n\n/**\n * @param {Route} route Express route object to be parsed\n * @param {string} basePath The basePath the route is on\n * @return {Endpoint[]} Endpoints info\n */\nconst parseExpressRoute = function (route, basePath) {\n // route.path can be a string, a RegExp, or an array of either\n const paths = Array.isArray(route.path) ? route.path : [route.path];\n\n /** @type {Endpoint[]} */\n const endpoints = paths.flatMap((path) => {\n let pathStr;\n if (typeof path === \"string\") {\n pathStr = stripExpressPathParamRegex(path);\n } else if (path instanceof RegExp) {\n pathStr = formatRegExpRoutePath(path);\n } else {\n return [];\n }\n const completePath =\n basePath && pathStr === \"/\" ? basePath : `${basePath}${pathStr}`;\n return [\n {\n path: completePath,\n methods: getRouteMethods(route),\n middlewares: getRouteMiddlewares(route),\n },\n ];\n });\n\n return endpoints;\n};\n\n/**\n * @param {RegExp} expressPathRegExp\n * @param {any[]} keys\n * @returns {string}\n */\nexport const parseExpressPathRegExp = function (expressPathRegExp, keys) {\n let parsedRegExp = expressPathRegExp.toString();\n let expressPathRegExpExec = regExpToParseExpressPathRegExp.exec(parsedRegExp);\n let paramIndex = 0;\n\n while (hasParams(parsedRegExp)) {\n const paramName = keys[paramIndex].name;\n const paramId = `:${paramName}`;\n\n parsedRegExp = parsedRegExp.replace(\n regExpToReplaceExpressPathRegExpParams,\n (str) => {\n // Express >= 4.20.0 uses a different RegExp for parameters: it\n // captures the slash as part of the parameter. We need to check\n // for this case and add the slash to the value that will replace\n // the parameter in the path.\n if (str.startsWith(\"(?:\\\\/\")) {\n return `\\\\/${paramId}`;\n }\n\n return paramId;\n },\n );\n\n paramIndex++;\n }\n\n if (parsedRegExp !== expressPathRegExp.toString()) {\n expressPathRegExpExec = regExpToParseExpressPathRegExp.exec(parsedRegExp);\n }\n\n return expressPathRegExpExec[1].replace(/\\\\\\//g, \"/\");\n};\n\n/**\n * @param {string} expressPath\n * @param {Object.<string, string>} params\n * @returns {string}\n */\nexport const parseExpressPath = function (expressPath, params) {\n let result = expressPath;\n for (const [paramName, paramValue] of Object.entries(params)) {\n result = result.replace(paramValue, `:${paramName}`);\n }\n return result;\n};\n\n/**\n * @param {import('express').Express | import('express').Router | any} app\n * @param {string} [basePath]\n * @param {Endpoint[]} [endpoints]\n * @returns {Endpoint[]}\n */\nconst parseEndpoints = function (app, basePath, endpoints) {\n const routerInfo = getRouterInfo(app);\n const stack = routerInfo.stack;\n const version = routerInfo.version;\n\n endpoints = endpoints || [];\n basePath = basePath || \"\";\n\n if (!stack) {\n if (endpoints.length) {\n endpoints = addEndpoints(endpoints, [\n {\n path: basePath,\n methods: [],\n middlewares: [],\n },\n ]);\n }\n } else {\n endpoints = parseStack(stack, basePath, endpoints, version);\n }\n\n return endpoints;\n};\n\n/**\n * Ensures the path of the new endpoints isn't yet in the array.\n * If the path is already in the array merges the endpoints with the existing\n * one, if not, it adds them to the array.\n *\n * @param {Endpoint[]} currentEndpoints Array of current endpoints\n * @param {Endpoint[]} endpointsToAdd New endpoints to be added to the array\n * @returns {Endpoint[]} Updated endpoints array\n */\nconst addEndpoints = function (currentEndpoints, endpointsToAdd) {\n endpointsToAdd.forEach((newEndpoint) => {\n const existingEndpoint = currentEndpoints.find(\n (endpoint) => endpoint.path === newEndpoint.path,\n );\n\n if (existingEndpoint !== undefined) {\n const newMethods = newEndpoint.methods.filter(\n (method) => !existingEndpoint.methods.includes(method),\n );\n\n existingEndpoint.methods = existingEndpoint.methods.concat(newMethods);\n } else {\n currentEndpoints.push(newEndpoint);\n }\n });\n\n return currentEndpoints;\n};\n\n/**\n * @param {any[]} stack\n * @param {string} basePath\n * @param {Endpoint[]} endpoints\n * @param {'v4' | 'v5'} [version]\n * @returns {Endpoint[]}\n */\nconst parseStack = function (stack, basePath, endpoints, version) {\n stack.forEach((stackItem) => {\n if (stackItem.route) {\n const newEndpoints = parseExpressRoute(stackItem.route, basePath);\n\n endpoints = addEndpoints(endpoints, newEndpoints);\n } else if (stackItemValidNames.includes(stackItem.name)) {\n let newBasePath = basePath;\n\n if (version === \"v4\") {\n const isExpressPathRegExp = regExpToParseExpressPathRegExp.test(\n stackItem.regexp,\n );\n if (isExpressPathRegExp) {\n const parsedPath = parseExpressPathRegExp(\n stackItem.regexp,\n stackItem.keys,\n );\n newBasePath += `/${parsedPath}`;\n } else if (\n !stackItem.path &&\n stackItem.regexp &&\n stackItem.regexp.toString() !== rootPathRegExpValue\n ) {\n newBasePath += `/${formatRegExpRoutePath(stackItem.regexp)}`;\n }\n } else if (version === \"v5\") {\n if (!stackItem.path) {\n return;\n } else if (stackItem.path !== \"/\") {\n newBasePath += stackItem.path.startsWith(\"/\")\n ? stackItem.path\n : `/${stackItem.path}`;\n }\n }\n\n endpoints = parseEndpoints(stackItem.handle, newBasePath, endpoints);\n }\n });\n\n return endpoints;\n};\n\nexport const getEndpoints = function (app, basePath) {\n const endpoints = parseEndpoints(app);\n const standardHttpMethods = [\"GET\", \"POST\", \"PUT\", \"DELETE\", \"PATCH\"];\n return endpoints.flatMap((route) =>\n route.methods\n .filter((method) => standardHttpMethods.includes(method.toUpperCase()))\n .map((method) => ({\n method,\n path: (basePath + route.path).replace(/\\/\\//g, \"/\"),\n })),\n );\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAAA;;;;;;;;;;AAgBA,MAAMA,iCACJ;AACF,MAAMC,yCAAyC;AAC/C,MAAMC,2BAA2B;AACjC,MAAMC,+BAA+B;AACrC,MAAMC,sBAAsB;AAC5B,MAAMC,sBAAsB;EAAC;EAAU;EAAkB;;AAQlD,MAAMC,6BAA6B,gCAAUC,MAAI;AACtD,SAAOA,KAAKC,QAAQL,8BAA8B,IAAA;AACpD,GAF0C;AAUnC,MAAMM,wBAAwB,gCAAUC,IAAE;AAC/C,SAAO,UAAUA,EAAAA;AACnB,GAFqC;AAS9B,MAAMC,gBAAgB,gCAAUC,KAAG;AAjD1C;AAkDE,MAAIA,IAAIC,OAAO;AAEb,WAAO;MAAEA,OAAOD,IAAIC;MAAOC,SAAS;IAAK;EAC3C,YAAWF,SAAIG,YAAJH,mBAAaC,OAAO;AAE7B,WAAO;MAAEA,OAAOD,IAAIG,QAAQF;MAAOC,SAAS;IAAK;EACnD,YAAWF,SAAII,WAAJJ,mBAAYC,OAAO;AAE5B,WAAO;MAAEA,OAAOD,IAAII,OAAOH;MAAOC,SAAS;IAAK;EAClD;AACA,SAAO;IAAED,OAAO;IAAMC,SAAS;EAAK;AACtC,GAZ6B;AAkB7B,MAAMG,kBAAkB,gCAAUC,OAAK;AACrC,MAAIC,UAAUC,OAAOC,KAAKH,MAAMC,OAAO;AAEvCA,YAAUA,QAAQG,OAAO,CAACC,WAAWA,WAAW,MAAA;AAChDJ,YAAUA,QAAQK,IAAI,CAACD,WAAWA,OAAOE,YAAW,CAAA;AAEpD,SAAON;AACT,GAPwB;AAexB,MAAMO,sBAAsB,gCAAUR,OAAK;AACzC,SAAOA,MAAML,MAAMW,IAAI,CAACG,SAAAA;AACtB,WAAOA,KAAKC,OAAOC,QAAQ;EAC7B,CAAA;AACF,GAJ4B;AAW5B,MAAMC,YAAY,gCAAUC,mBAAiB;AAC3C,SAAO7B,yBAAyB8B,KAAKD,iBAAAA;AACvC,GAFkB;AASlB,MAAME,oBAAoB,gCAAUf,OAAOgB,UAAQ;AAEjD,QAAMC,QAAQC,MAAMC,QAAQnB,MAAMX,IAAI,IAAIW,MAAMX,OAAO;IAACW,MAAMX;;AAG9D,QAAM+B,YAAYH,MAAMI,QAAQ,CAAChC,SAAAA;AAC/B,QAAIiC;AACJ,QAAI,OAAOjC,SAAS,UAAU;AAC5BiC,gBAAUlC,2BAA2BC,IAAAA;IACvC,WAAWA,gBAAgBkC,QAAQ;AACjCD,gBAAU/B,sBAAsBF,IAAAA;IAClC,OAAO;AACL,aAAO,CAAA;IACT;AACA,UAAMmC,eACJR,YAAYM,YAAY,MAAMN,WAAW,GAAGA,QAAAA,GAAWM,OAAAA;AACzD,WAAO;MACL;QACEjC,MAAMmC;QACNvB,SAASF,gBAAgBC,KAAAA;QACzByB,aAAajB,oBAAoBR,KAAAA;MACnC;;EAEJ,CAAA;AAEA,SAAOoB;AACT,GA1B0B;AAiCnB,MAAMM,yBAAyB,gCAAUb,mBAAmBV,MAAI;AACrE,MAAIwB,eAAed,kBAAkBe,SAAQ;AAC7C,MAAIC,wBAAwB/C,+BAA+BgD,KAAKH,YAAAA;AAChE,MAAII,aAAa;AAEjB,SAAOnB,UAAUe,YAAAA,GAAe;AAC9B,UAAMK,YAAY7B,KAAK4B,UAAAA,EAAYpB;AACnC,UAAMsB,UAAU,IAAID,SAAAA;AAEpBL,mBAAeA,aAAarC,QAC1BP,wCACA,CAACmD,QAAAA;AAKC,UAAIA,IAAIC,WAAW,QAAA,GAAW;AAC5B,eAAO,MAAMF,OAAAA;MACf;AAEA,aAAOA;IACT,CAAA;AAGFF;EACF;AAEA,MAAIJ,iBAAiBd,kBAAkBe,SAAQ,GAAI;AACjDC,4BAAwB/C,+BAA+BgD,KAAKH,YAAAA;EAC9D;AAEA,SAAOE,sBAAsB,CAAA,EAAGvC,QAAQ,SAAS,GAAA;AACnD,GAhCsC;AAuC/B,MAAM8C,mBAAmB,gCAAUC,aAAaC,QAAM;AAC3D,MAAIC,SAASF;AACb,aAAW,CAACL,WAAWQ,UAAAA,KAAetC,OAAOuC,QAAQH,MAAAA,GAAS;AAC5DC,aAASA,OAAOjD,QAAQkD,YAAY,IAAIR,SAAAA,EAAW;EACrD;AACA,SAAOO;AACT,GANgC;AAchC,MAAMG,iBAAiB,gCAAUhD,KAAKsB,UAAUI,WAAS;AACvD,QAAMuB,aAAalD,cAAcC,GAAAA;AACjC,QAAMC,QAAQgD,WAAWhD;AACzB,QAAMC,UAAU+C,WAAW/C;AAE3BwB,cAAYA,aAAa,CAAA;AACzBJ,aAAWA,YAAY;AAEvB,MAAI,CAACrB,OAAO;AACV,QAAIyB,UAAUwB,QAAQ;AACpBxB,kBAAYyB,aAAazB,WAAW;QAClC;UACE/B,MAAM2B;UACNf,SAAS,CAAA;UACTwB,aAAa,CAAA;QACf;OACD;IACH;EACF,OAAO;AACLL,gBAAY0B,WAAWnD,OAAOqB,UAAUI,WAAWxB,OAAAA;EACrD;AAEA,SAAOwB;AACT,GAvBuB;AAkCvB,MAAMyB,eAAe,gCAAUE,kBAAkBC,gBAAc;AAC7DA,iBAAeC,QAAQ,CAACC,gBAAAA;AACtB,UAAMC,mBAAmBJ,iBAAiBK,KACxC,CAACC,aAAaA,SAAShE,SAAS6D,YAAY7D,IAAI;AAGlD,QAAI8D,qBAAqBG,QAAW;AAClC,YAAMC,aAAaL,YAAYjD,QAAQG,OACrC,CAACC,WAAW,CAAC8C,iBAAiBlD,QAAQuD,SAASnD,MAAAA,CAAAA;AAGjD8C,uBAAiBlD,UAAUkD,iBAAiBlD,QAAQwD,OAAOF,UAAAA;IAC7D,OAAO;AACLR,uBAAiBW,KAAKR,WAAAA;IACxB;EACF,CAAA;AAEA,SAAOH;AACT,GAlBqB;AA2BrB,MAAMD,aAAa,gCAAUnD,OAAOqB,UAAUI,WAAWxB,SAAO;AAC9DD,QAAMsD,QAAQ,CAACU,cAAAA;AACb,QAAIA,UAAU3D,OAAO;AACnB,YAAM4D,eAAe7C,kBAAkB4C,UAAU3D,OAAOgB,QAAAA;AAExDI,kBAAYyB,aAAazB,WAAWwC,YAAAA;IACtC,WAAWzE,oBAAoBqE,SAASG,UAAUhD,IAAI,GAAG;AACvD,UAAIkD,cAAc7C;AAElB,UAAIpB,YAAY,MAAM;AACpB,cAAMkE,sBAAsBhF,+BAA+BgC,KACzD6C,UAAUI,MAAM;AAElB,YAAID,qBAAqB;AACvB,gBAAME,aAAatC,uBACjBiC,UAAUI,QACVJ,UAAUxD,IAAI;AAEhB0D,yBAAe,IAAIG,UAAAA;QACrB,WACE,CAACL,UAAUtE,QACXsE,UAAUI,UACVJ,UAAUI,OAAOnC,SAAQ,MAAO1C,qBAChC;AACA2E,yBAAe,IAAItE,sBAAsBoE,UAAUI,MAAM,CAAA;QAC3D;MACF,WAAWnE,YAAY,MAAM;AAC3B,YAAI,CAAC+D,UAAUtE,MAAM;AACnB;QACF,WAAWsE,UAAUtE,SAAS,KAAK;AACjCwE,yBAAeF,UAAUtE,KAAK8C,WAAW,GAAA,IACrCwB,UAAUtE,OACV,IAAIsE,UAAUtE,IAAI;QACxB;MACF;AAEA+B,kBAAYsB,eAAeiB,UAAUjD,QAAQmD,aAAazC,SAAAA;IAC5D;EACF,CAAA;AAEA,SAAOA;AACT,GAzCmB;AA2CZ,MAAM6C,eAAe,gCAAUvE,KAAKsB,UAAQ;AACjD,QAAMI,YAAYsB,eAAehD,GAAAA;AACjC,QAAMwE,sBAAsB;IAAC;IAAO;IAAQ;IAAO;IAAU;;AAC7D,SAAO9C,UAAUC,QAAQ,CAACrB,UACxBA,MAAMC,QACHG,OAAO,CAACC,WAAW6D,oBAAoBV,SAASnD,OAAOE,YAAW,CAAA,CAAA,EAClED,IAAI,CAACD,YAAY;IAChBA;IACAhB,OAAO2B,WAAWhB,MAAMX,MAAMC,QAAQ,SAAS,GAAA;EACjD,EAAA,CAAA;AAEN,GAX4B;","names":["regExpToParseExpressPathRegExp","regExpToReplaceExpressPathRegExpParams","regExpExpressParamRegExp","regExpExpressPathParamRegExp","rootPathRegExpValue","stackItemValidNames","stripExpressPathParamRegex","path","replace","formatRegExpRoutePath","re","getRouterInfo","app","stack","version","_router","router","getRouteMethods","route","methods","Object","keys","filter","method","map","toUpperCase","getRouteMiddlewares","item","handle","name","hasParams","expressPathRegExp","test","parseExpressRoute","basePath","paths","Array","isArray","endpoints","flatMap","pathStr","RegExp","completePath","middlewares","parseExpressPathRegExp","parsedRegExp","toString","expressPathRegExpExec","exec","paramIndex","paramName","paramId","str","startsWith","parseExpressPath","expressPath","params","result","paramValue","entries","parseEndpoints","routerInfo","length","addEndpoints","parseStack","currentEndpoints","endpointsToAdd","forEach","newEndpoint","existingEndpoint","find","endpoint","undefined","newMethods","includes","concat","push","stackItem","newEndpoints","newBasePath","isExpressPathRegExp","regexp","parsedPath","getEndpoints","standardHttpMethods"]}
@@ -1,5 +1,7 @@
1
1
  import * as express from 'express';
2
2
 
3
+ declare function stripExpressPathParamRegex(path: string): string;
4
+ declare function formatRegExpRoutePath(re: RegExp): string;
3
5
  declare function getRouterInfo(app: express.Express | express.Router | any): {
4
6
  stack: any[] | null;
5
7
  version: "v4" | "v5";
@@ -32,4 +34,4 @@ type Endpoint = {
32
34
  middlewares: string[];
33
35
  };
34
36
 
35
- export { type Endpoint, type Route, getEndpoints, getRouterInfo, parseExpressPath, parseExpressPathRegExp };
37
+ export { type Endpoint, type Route, formatRegExpRoutePath, getEndpoints, getRouterInfo, parseExpressPath, parseExpressPathRegExp, stripExpressPathParamRegex };
@@ -1,5 +1,7 @@
1
1
  import * as express from 'express';
2
2
 
3
+ declare function stripExpressPathParamRegex(path: string): string;
4
+ declare function formatRegExpRoutePath(re: RegExp): string;
3
5
  declare function getRouterInfo(app: express.Express | express.Router | any): {
4
6
  stack: any[] | null;
5
7
  version: "v4" | "v5";
@@ -32,4 +34,4 @@ type Endpoint = {
32
34
  middlewares: string[];
33
35
  };
34
36
 
35
- export { type Endpoint, type Route, getEndpoints, getRouterInfo, parseExpressPath, parseExpressPathRegExp };
37
+ export { type Endpoint, type Route, formatRegExpRoutePath, getEndpoints, getRouterInfo, parseExpressPath, parseExpressPathRegExp, stripExpressPathParamRegex };
@@ -2,14 +2,20 @@ var __defProp = Object.defineProperty;
2
2
  var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
3
3
  const regExpToParseExpressPathRegExp = /^\/\^\\?\/?(?:(:?[\w\\.-]*(?:\\\/:?[\w\\.-]*)*)|(\(\?:\\?\/?\([^)]+\)\)))\\\/.*/;
4
4
  const regExpToReplaceExpressPathRegExpParams = /\(\?:\\?\/?\([^)]+\)\)/;
5
- const regexpExpressParamRegexp = /\(\?:\\?\\?\/?\([^)]+\)\)/g;
6
- const regexpExpressPathParamRegexp = /(:[^)]+)\([^)]+\)/g;
7
- const EXPRESS_ROOT_PATH_REGEXP_VALUE = "/^\\/?(?=\\/|$)/i";
8
- const STACK_ITEM_VALID_NAMES = [
5
+ const regExpExpressParamRegExp = /\(\?:\\?\\?\/?\([^)]+\)\)/g;
6
+ const regExpExpressPathParamRegExp = /(:[^)]+)\([^)]+\)/g;
7
+ const rootPathRegExpValue = "/^\\/?(?=\\/|$)/i";
8
+ const stackItemValidNames = [
9
9
  "router",
10
10
  "bound dispatch",
11
11
  "mounted_app"
12
12
  ];
13
+ const stripExpressPathParamRegex = /* @__PURE__ */ __name(function(path) {
14
+ return path.replace(regExpExpressPathParamRegExp, "$1");
15
+ }, "stripExpressPathParamRegex");
16
+ const formatRegExpRoutePath = /* @__PURE__ */ __name(function(re) {
17
+ return `RegExp(${re})`;
18
+ }, "formatRegExpRoutePath");
13
19
  const getRouterInfo = /* @__PURE__ */ __name(function(app) {
14
20
  var _a, _b;
15
21
  if (app.stack) {
@@ -45,23 +51,29 @@ const getRouteMiddlewares = /* @__PURE__ */ __name(function(route) {
45
51
  });
46
52
  }, "getRouteMiddlewares");
47
53
  const hasParams = /* @__PURE__ */ __name(function(expressPathRegExp) {
48
- return regexpExpressParamRegexp.test(expressPathRegExp);
54
+ return regExpExpressParamRegExp.test(expressPathRegExp);
49
55
  }, "hasParams");
50
56
  const parseExpressRoute = /* @__PURE__ */ __name(function(route, basePath) {
51
- const paths = [];
52
- if (Array.isArray(route.path)) {
53
- paths.push(...route.path);
54
- } else {
55
- paths.push(route.path);
56
- }
57
- const endpoints = paths.map((path) => {
58
- const completePath = basePath && path === "/" ? basePath : `${basePath}${path}`;
59
- const endpoint = {
60
- path: completePath.replace(regexpExpressPathParamRegexp, "$1"),
61
- methods: getRouteMethods(route),
62
- middlewares: getRouteMiddlewares(route)
63
- };
64
- return endpoint;
57
+ const paths = Array.isArray(route.path) ? route.path : [
58
+ route.path
59
+ ];
60
+ const endpoints = paths.flatMap((path) => {
61
+ let pathStr;
62
+ if (typeof path === "string") {
63
+ pathStr = stripExpressPathParamRegex(path);
64
+ } else if (path instanceof RegExp) {
65
+ pathStr = formatRegExpRoutePath(path);
66
+ } else {
67
+ return [];
68
+ }
69
+ const completePath = basePath && pathStr === "/" ? basePath : `${basePath}${pathStr}`;
70
+ return [
71
+ {
72
+ path: completePath,
73
+ methods: getRouteMethods(route),
74
+ middlewares: getRouteMiddlewares(route)
75
+ }
76
+ ];
65
77
  });
66
78
  return endpoints;
67
79
  }, "parseExpressRoute");
@@ -130,16 +142,15 @@ const parseStack = /* @__PURE__ */ __name(function(stack, basePath, endpoints, v
130
142
  if (stackItem.route) {
131
143
  const newEndpoints = parseExpressRoute(stackItem.route, basePath);
132
144
  endpoints = addEndpoints(endpoints, newEndpoints);
133
- } else if (STACK_ITEM_VALID_NAMES.includes(stackItem.name)) {
145
+ } else if (stackItemValidNames.includes(stackItem.name)) {
134
146
  let newBasePath = basePath;
135
147
  if (version === "v4") {
136
148
  const isExpressPathRegExp = regExpToParseExpressPathRegExp.test(stackItem.regexp);
137
149
  if (isExpressPathRegExp) {
138
150
  const parsedPath = parseExpressPathRegExp(stackItem.regexp, stackItem.keys);
139
151
  newBasePath += `/${parsedPath}`;
140
- } else if (!stackItem.path && stackItem.regexp && stackItem.regexp.toString() !== EXPRESS_ROOT_PATH_REGEXP_VALUE) {
141
- const regExpPath = `RegExp(${stackItem.regexp})`;
142
- newBasePath += `/${regExpPath}`;
152
+ } else if (!stackItem.path && stackItem.regexp && stackItem.regexp.toString() !== rootPathRegExpValue) {
153
+ newBasePath += `/${formatRegExpRoutePath(stackItem.regexp)}`;
143
154
  }
144
155
  } else if (version === "v5") {
145
156
  if (!stackItem.path) {
@@ -168,9 +179,11 @@ const getEndpoints = /* @__PURE__ */ __name(function(app, basePath) {
168
179
  })));
169
180
  }, "getEndpoints");
170
181
  export {
182
+ formatRegExpRoutePath,
171
183
  getEndpoints,
172
184
  getRouterInfo,
173
185
  parseExpressPath,
174
- parseExpressPathRegExp
186
+ parseExpressPathRegExp,
187
+ stripExpressPathParamRegex
175
188
  };
176
189
  //# sourceMappingURL=utils.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/express/utils.js"],"sourcesContent":["// Adapted from https://github.com/AlbertoFdzM/express-list-endpoints/blob/305535d43008b46f34e18b01947762e039af6d2d/src/index.js\n// and also incorporated https://github.com/AlbertoFdzM/express-list-endpoints/pull/96\n// and https://github.com/labithiotis/express-list-routes/blob/5432c83a67c2788c56a1cdfc067ec809961c0c05/index.js\n\n/**\n * @typedef {Object} Route\n * @property {Object} methods\n * @property {string | string[]} path\n * @property {any[]} stack\n *\n * @typedef {Object} Endpoint\n * @property {string} path Path name\n * @property {string[]} methods Methods handled\n * @property {string[]} middlewares Mounted middlewares\n */\n\nconst regExpToParseExpressPathRegExp =\n /^\\/\\^\\\\?\\/?(?:(:?[\\w\\\\.-]*(?:\\\\\\/:?[\\w\\\\.-]*)*)|(\\(\\?:\\\\?\\/?\\([^)]+\\)\\)))\\\\\\/.*/;\nconst regExpToReplaceExpressPathRegExpParams = /\\(\\?:\\\\?\\/?\\([^)]+\\)\\)/;\nconst regexpExpressParamRegexp = /\\(\\?:\\\\?\\\\?\\/?\\([^)]+\\)\\)/g;\nconst regexpExpressPathParamRegexp = /(:[^)]+)\\([^)]+\\)/g;\n\nconst EXPRESS_ROOT_PATH_REGEXP_VALUE = \"/^\\\\/?(?=\\\\/|$)/i\";\nconst STACK_ITEM_VALID_NAMES = [\"router\", \"bound dispatch\", \"mounted_app\"];\n\n/**\n * Detects Express version and returns router information\n * @param {import('express').Express | import('express').Router | any} app\n * @returns {{stack: any[] | null, version: 'v4' | 'v5'}}\n */\nexport const getRouterInfo = function (app) {\n if (app.stack) {\n // Express 4 router\n return { stack: app.stack, version: \"v4\" };\n } else if (app._router?.stack) {\n // Express 4\n return { stack: app._router.stack, version: \"v4\" };\n } else if (app.router?.stack) {\n // Express 5\n return { stack: app.router.stack, version: \"v5\" };\n }\n return { stack: null, version: \"v4\" };\n};\n\n/**\n * Returns all the verbs detected for the passed route\n * @param {Route} route\n */\nconst getRouteMethods = function (route) {\n let methods = Object.keys(route.methods);\n\n methods = methods.filter((method) => method !== \"_all\");\n methods = methods.map((method) => method.toUpperCase());\n\n return methods;\n};\n\n/**\n * Returns the names (or anonymous) of all the middlewares attached to the\n * passed route\n * @param {Route} route\n * @returns {string[]}\n */\nconst getRouteMiddlewares = function (route) {\n return route.stack.map((item) => {\n return item.handle.name || \"anonymous\";\n });\n};\n\n/**\n * Returns true if found regexp related with express params\n * @param {string} expressPathRegExp\n * @returns {boolean}\n */\nconst hasParams = function (expressPathRegExp) {\n return regexpExpressParamRegexp.test(expressPathRegExp);\n};\n\n/**\n * @param {Route} route Express route object to be parsed\n * @param {string} basePath The basePath the route is on\n * @return {Endpoint[]} Endpoints info\n */\nconst parseExpressRoute = function (route, basePath) {\n const paths = [];\n\n if (Array.isArray(route.path)) {\n paths.push(...route.path);\n } else {\n paths.push(route.path);\n }\n\n /** @type {Endpoint[]} */\n const endpoints = paths.map((path) => {\n const completePath =\n basePath && path === \"/\" ? basePath : `${basePath}${path}`;\n\n /** @type {Endpoint} */\n const endpoint = {\n path: completePath.replace(regexpExpressPathParamRegexp, \"$1\"),\n methods: getRouteMethods(route),\n middlewares: getRouteMiddlewares(route),\n };\n\n return endpoint;\n });\n\n return endpoints;\n};\n\n/**\n * @param {RegExp} expressPathRegExp\n * @param {any[]} keys\n * @returns {string}\n */\nexport const parseExpressPathRegExp = function (expressPathRegExp, keys) {\n let parsedRegExp = expressPathRegExp.toString();\n let expressPathRegExpExec = regExpToParseExpressPathRegExp.exec(parsedRegExp);\n let paramIndex = 0;\n\n while (hasParams(parsedRegExp)) {\n const paramName = keys[paramIndex].name;\n const paramId = `:${paramName}`;\n\n parsedRegExp = parsedRegExp.replace(\n regExpToReplaceExpressPathRegExpParams,\n (str) => {\n // Express >= 4.20.0 uses a different RegExp for parameters: it\n // captures the slash as part of the parameter. We need to check\n // for this case and add the slash to the value that will replace\n // the parameter in the path.\n if (str.startsWith(\"(?:\\\\/\")) {\n return `\\\\/${paramId}`;\n }\n\n return paramId;\n },\n );\n\n paramIndex++;\n }\n\n if (parsedRegExp !== expressPathRegExp.toString()) {\n expressPathRegExpExec = regExpToParseExpressPathRegExp.exec(parsedRegExp);\n }\n\n return expressPathRegExpExec[1].replace(/\\\\\\//g, \"/\");\n};\n\n/**\n * @param {string} expressPath\n * @param {Object.<string, string>} params\n * @returns {string}\n */\nexport const parseExpressPath = function (expressPath, params) {\n let result = expressPath;\n for (const [paramName, paramValue] of Object.entries(params)) {\n result = result.replace(paramValue, `:${paramName}`);\n }\n return result;\n};\n\n/**\n * @param {import('express').Express | import('express').Router | any} app\n * @param {string} [basePath]\n * @param {Endpoint[]} [endpoints]\n * @returns {Endpoint[]}\n */\nconst parseEndpoints = function (app, basePath, endpoints) {\n const routerInfo = getRouterInfo(app);\n const stack = routerInfo.stack;\n const version = routerInfo.version;\n\n endpoints = endpoints || [];\n basePath = basePath || \"\";\n\n if (!stack) {\n if (endpoints.length) {\n endpoints = addEndpoints(endpoints, [\n {\n path: basePath,\n methods: [],\n middlewares: [],\n },\n ]);\n }\n } else {\n endpoints = parseStack(stack, basePath, endpoints, version);\n }\n\n return endpoints;\n};\n\n/**\n * Ensures the path of the new endpoints isn't yet in the array.\n * If the path is already in the array merges the endpoints with the existing\n * one, if not, it adds them to the array.\n *\n * @param {Endpoint[]} currentEndpoints Array of current endpoints\n * @param {Endpoint[]} endpointsToAdd New endpoints to be added to the array\n * @returns {Endpoint[]} Updated endpoints array\n */\nconst addEndpoints = function (currentEndpoints, endpointsToAdd) {\n endpointsToAdd.forEach((newEndpoint) => {\n const existingEndpoint = currentEndpoints.find(\n (endpoint) => endpoint.path === newEndpoint.path,\n );\n\n if (existingEndpoint !== undefined) {\n const newMethods = newEndpoint.methods.filter(\n (method) => !existingEndpoint.methods.includes(method),\n );\n\n existingEndpoint.methods = existingEndpoint.methods.concat(newMethods);\n } else {\n currentEndpoints.push(newEndpoint);\n }\n });\n\n return currentEndpoints;\n};\n\n/**\n * @param {any[]} stack\n * @param {string} basePath\n * @param {Endpoint[]} endpoints\n * @param {'v4' | 'v5'} [version]\n * @returns {Endpoint[]}\n */\nconst parseStack = function (stack, basePath, endpoints, version) {\n stack.forEach((stackItem) => {\n if (stackItem.route) {\n const newEndpoints = parseExpressRoute(stackItem.route, basePath);\n\n endpoints = addEndpoints(endpoints, newEndpoints);\n } else if (STACK_ITEM_VALID_NAMES.includes(stackItem.name)) {\n let newBasePath = basePath;\n\n if (version === \"v4\") {\n const isExpressPathRegExp = regExpToParseExpressPathRegExp.test(\n stackItem.regexp,\n );\n if (isExpressPathRegExp) {\n const parsedPath = parseExpressPathRegExp(\n stackItem.regexp,\n stackItem.keys,\n );\n newBasePath += `/${parsedPath}`;\n } else if (\n !stackItem.path &&\n stackItem.regexp &&\n stackItem.regexp.toString() !== EXPRESS_ROOT_PATH_REGEXP_VALUE\n ) {\n const regExpPath = `RegExp(${stackItem.regexp})`;\n newBasePath += `/${regExpPath}`;\n }\n } else if (version === \"v5\") {\n if (!stackItem.path) {\n return;\n } else if (stackItem.path !== \"/\") {\n newBasePath += stackItem.path.startsWith(\"/\")\n ? stackItem.path\n : `/${stackItem.path}`;\n }\n }\n\n endpoints = parseEndpoints(stackItem.handle, newBasePath, endpoints);\n }\n });\n\n return endpoints;\n};\n\nexport const getEndpoints = function (app, basePath) {\n const endpoints = parseEndpoints(app);\n const standardHttpMethods = [\"GET\", \"POST\", \"PUT\", \"DELETE\", \"PATCH\"];\n return endpoints.flatMap((route) =>\n route.methods\n .filter((method) => standardHttpMethods.includes(method.toUpperCase()))\n .map((method) => ({\n method,\n path: (basePath + route.path).replace(/\\/\\//g, \"/\"),\n })),\n );\n};\n"],"mappings":"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","names":["regExpToParseExpressPathRegExp","regExpToReplaceExpressPathRegExpParams","regexpExpressParamRegexp","regexpExpressPathParamRegexp","EXPRESS_ROOT_PATH_REGEXP_VALUE","STACK_ITEM_VALID_NAMES","getRouterInfo","app","stack","version","_router","router","getRouteMethods","route","methods","Object","keys","filter","method","map","toUpperCase","getRouteMiddlewares","item","handle","name","hasParams","expressPathRegExp","test","parseExpressRoute","basePath","paths","Array","isArray","path","push","endpoints","completePath","endpoint","replace","middlewares","parseExpressPathRegExp","parsedRegExp","toString","expressPathRegExpExec","exec","paramIndex","paramName","paramId","str","startsWith","parseExpressPath","expressPath","params","result","paramValue","entries","parseEndpoints","routerInfo","length","addEndpoints","parseStack","currentEndpoints","endpointsToAdd","forEach","newEndpoint","existingEndpoint","find","undefined","newMethods","includes","concat","stackItem","newEndpoints","newBasePath","isExpressPathRegExp","regexp","parsedPath","regExpPath","getEndpoints","standardHttpMethods","flatMap"]}
1
+ {"version":3,"sources":["../../src/express/utils.js"],"sourcesContent":["// Adapted from https://github.com/AlbertoFdzM/express-list-endpoints/blob/305535d43008b46f34e18b01947762e039af6d2d/src/index.js\n// and also incorporated https://github.com/AlbertoFdzM/express-list-endpoints/pull/96\n// and https://github.com/labithiotis/express-list-routes/blob/5432c83a67c2788c56a1cdfc067ec809961c0c05/index.js\n\n/**\n * @typedef {Object} Route\n * @property {Object} methods\n * @property {string | string[]} path\n * @property {any[]} stack\n *\n * @typedef {Object} Endpoint\n * @property {string} path Path name\n * @property {string[]} methods Methods handled\n * @property {string[]} middlewares Mounted middlewares\n */\n\nconst regExpToParseExpressPathRegExp =\n /^\\/\\^\\\\?\\/?(?:(:?[\\w\\\\.-]*(?:\\\\\\/:?[\\w\\\\.-]*)*)|(\\(\\?:\\\\?\\/?\\([^)]+\\)\\)))\\\\\\/.*/;\nconst regExpToReplaceExpressPathRegExpParams = /\\(\\?:\\\\?\\/?\\([^)]+\\)\\)/;\nconst regExpExpressParamRegExp = /\\(\\?:\\\\?\\\\?\\/?\\([^)]+\\)\\)/g;\nconst regExpExpressPathParamRegExp = /(:[^)]+)\\([^)]+\\)/g;\nconst rootPathRegExpValue = \"/^\\\\/?(?=\\\\/|$)/i\";\nconst stackItemValidNames = [\"router\", \"bound dispatch\", \"mounted_app\"];\n\n/**\n * Strips inline RegExp validators from Express path params, e.g.\n * \"/users/:id(\\\\d+)\" → \"/users/:id\".\n * @param {string} path\n * @returns {string}\n */\nexport const stripExpressPathParamRegex = function (path) {\n return path.replace(regExpExpressPathParamRegExp, \"$1\");\n};\n\n/**\n * Formats a RegExp route as a stable string identifier, e.g.\n * /^\\/foo$/ → \"RegExp(/^\\\\/foo$/)\".\n * @param {RegExp} re\n * @returns {string}\n */\nexport const formatRegExpRoutePath = function (re) {\n return `RegExp(${re})`;\n};\n\n/**\n * Detects Express version and returns router information\n * @param {import('express').Express | import('express').Router | any} app\n * @returns {{stack: any[] | null, version: 'v4' | 'v5'}}\n */\nexport const getRouterInfo = function (app) {\n if (app.stack) {\n // Express 4 router\n return { stack: app.stack, version: \"v4\" };\n } else if (app._router?.stack) {\n // Express 4\n return { stack: app._router.stack, version: \"v4\" };\n } else if (app.router?.stack) {\n // Express 5\n return { stack: app.router.stack, version: \"v5\" };\n }\n return { stack: null, version: \"v4\" };\n};\n\n/**\n * Returns all the verbs detected for the passed route\n * @param {Route} route\n */\nconst getRouteMethods = function (route) {\n let methods = Object.keys(route.methods);\n\n methods = methods.filter((method) => method !== \"_all\");\n methods = methods.map((method) => method.toUpperCase());\n\n return methods;\n};\n\n/**\n * Returns the names (or anonymous) of all the middlewares attached to the\n * passed route\n * @param {Route} route\n * @returns {string[]}\n */\nconst getRouteMiddlewares = function (route) {\n return route.stack.map((item) => {\n return item.handle.name || \"anonymous\";\n });\n};\n\n/**\n * Returns true if found regexp related with express params\n * @param {string} expressPathRegExp\n * @returns {boolean}\n */\nconst hasParams = function (expressPathRegExp) {\n return regExpExpressParamRegExp.test(expressPathRegExp);\n};\n\n/**\n * @param {Route} route Express route object to be parsed\n * @param {string} basePath The basePath the route is on\n * @return {Endpoint[]} Endpoints info\n */\nconst parseExpressRoute = function (route, basePath) {\n // route.path can be a string, a RegExp, or an array of either\n const paths = Array.isArray(route.path) ? route.path : [route.path];\n\n /** @type {Endpoint[]} */\n const endpoints = paths.flatMap((path) => {\n let pathStr;\n if (typeof path === \"string\") {\n pathStr = stripExpressPathParamRegex(path);\n } else if (path instanceof RegExp) {\n pathStr = formatRegExpRoutePath(path);\n } else {\n return [];\n }\n const completePath =\n basePath && pathStr === \"/\" ? basePath : `${basePath}${pathStr}`;\n return [\n {\n path: completePath,\n methods: getRouteMethods(route),\n middlewares: getRouteMiddlewares(route),\n },\n ];\n });\n\n return endpoints;\n};\n\n/**\n * @param {RegExp} expressPathRegExp\n * @param {any[]} keys\n * @returns {string}\n */\nexport const parseExpressPathRegExp = function (expressPathRegExp, keys) {\n let parsedRegExp = expressPathRegExp.toString();\n let expressPathRegExpExec = regExpToParseExpressPathRegExp.exec(parsedRegExp);\n let paramIndex = 0;\n\n while (hasParams(parsedRegExp)) {\n const paramName = keys[paramIndex].name;\n const paramId = `:${paramName}`;\n\n parsedRegExp = parsedRegExp.replace(\n regExpToReplaceExpressPathRegExpParams,\n (str) => {\n // Express >= 4.20.0 uses a different RegExp for parameters: it\n // captures the slash as part of the parameter. We need to check\n // for this case and add the slash to the value that will replace\n // the parameter in the path.\n if (str.startsWith(\"(?:\\\\/\")) {\n return `\\\\/${paramId}`;\n }\n\n return paramId;\n },\n );\n\n paramIndex++;\n }\n\n if (parsedRegExp !== expressPathRegExp.toString()) {\n expressPathRegExpExec = regExpToParseExpressPathRegExp.exec(parsedRegExp);\n }\n\n return expressPathRegExpExec[1].replace(/\\\\\\//g, \"/\");\n};\n\n/**\n * @param {string} expressPath\n * @param {Object.<string, string>} params\n * @returns {string}\n */\nexport const parseExpressPath = function (expressPath, params) {\n let result = expressPath;\n for (const [paramName, paramValue] of Object.entries(params)) {\n result = result.replace(paramValue, `:${paramName}`);\n }\n return result;\n};\n\n/**\n * @param {import('express').Express | import('express').Router | any} app\n * @param {string} [basePath]\n * @param {Endpoint[]} [endpoints]\n * @returns {Endpoint[]}\n */\nconst parseEndpoints = function (app, basePath, endpoints) {\n const routerInfo = getRouterInfo(app);\n const stack = routerInfo.stack;\n const version = routerInfo.version;\n\n endpoints = endpoints || [];\n basePath = basePath || \"\";\n\n if (!stack) {\n if (endpoints.length) {\n endpoints = addEndpoints(endpoints, [\n {\n path: basePath,\n methods: [],\n middlewares: [],\n },\n ]);\n }\n } else {\n endpoints = parseStack(stack, basePath, endpoints, version);\n }\n\n return endpoints;\n};\n\n/**\n * Ensures the path of the new endpoints isn't yet in the array.\n * If the path is already in the array merges the endpoints with the existing\n * one, if not, it adds them to the array.\n *\n * @param {Endpoint[]} currentEndpoints Array of current endpoints\n * @param {Endpoint[]} endpointsToAdd New endpoints to be added to the array\n * @returns {Endpoint[]} Updated endpoints array\n */\nconst addEndpoints = function (currentEndpoints, endpointsToAdd) {\n endpointsToAdd.forEach((newEndpoint) => {\n const existingEndpoint = currentEndpoints.find(\n (endpoint) => endpoint.path === newEndpoint.path,\n );\n\n if (existingEndpoint !== undefined) {\n const newMethods = newEndpoint.methods.filter(\n (method) => !existingEndpoint.methods.includes(method),\n );\n\n existingEndpoint.methods = existingEndpoint.methods.concat(newMethods);\n } else {\n currentEndpoints.push(newEndpoint);\n }\n });\n\n return currentEndpoints;\n};\n\n/**\n * @param {any[]} stack\n * @param {string} basePath\n * @param {Endpoint[]} endpoints\n * @param {'v4' | 'v5'} [version]\n * @returns {Endpoint[]}\n */\nconst parseStack = function (stack, basePath, endpoints, version) {\n stack.forEach((stackItem) => {\n if (stackItem.route) {\n const newEndpoints = parseExpressRoute(stackItem.route, basePath);\n\n endpoints = addEndpoints(endpoints, newEndpoints);\n } else if (stackItemValidNames.includes(stackItem.name)) {\n let newBasePath = basePath;\n\n if (version === \"v4\") {\n const isExpressPathRegExp = regExpToParseExpressPathRegExp.test(\n stackItem.regexp,\n );\n if (isExpressPathRegExp) {\n const parsedPath = parseExpressPathRegExp(\n stackItem.regexp,\n stackItem.keys,\n );\n newBasePath += `/${parsedPath}`;\n } else if (\n !stackItem.path &&\n stackItem.regexp &&\n stackItem.regexp.toString() !== rootPathRegExpValue\n ) {\n newBasePath += `/${formatRegExpRoutePath(stackItem.regexp)}`;\n }\n } else if (version === \"v5\") {\n if (!stackItem.path) {\n return;\n } else if (stackItem.path !== \"/\") {\n newBasePath += stackItem.path.startsWith(\"/\")\n ? stackItem.path\n : `/${stackItem.path}`;\n }\n }\n\n endpoints = parseEndpoints(stackItem.handle, newBasePath, endpoints);\n }\n });\n\n return endpoints;\n};\n\nexport const getEndpoints = function (app, basePath) {\n const endpoints = parseEndpoints(app);\n const standardHttpMethods = [\"GET\", \"POST\", \"PUT\", \"DELETE\", \"PATCH\"];\n return endpoints.flatMap((route) =>\n route.methods\n .filter((method) => standardHttpMethods.includes(method.toUpperCase()))\n .map((method) => ({\n method,\n path: (basePath + route.path).replace(/\\/\\//g, \"/\"),\n })),\n );\n};\n"],"mappings":";;AAgBA,MAAMA,iCACJ;AACF,MAAMC,yCAAyC;AAC/C,MAAMC,2BAA2B;AACjC,MAAMC,+BAA+B;AACrC,MAAMC,sBAAsB;AAC5B,MAAMC,sBAAsB;EAAC;EAAU;EAAkB;;AAQlD,MAAMC,6BAA6B,gCAAUC,MAAI;AACtD,SAAOA,KAAKC,QAAQL,8BAA8B,IAAA;AACpD,GAF0C;AAUnC,MAAMM,wBAAwB,gCAAUC,IAAE;AAC/C,SAAO,UAAUA,EAAAA;AACnB,GAFqC;AAS9B,MAAMC,gBAAgB,gCAAUC,KAAG;AAjD1C;AAkDE,MAAIA,IAAIC,OAAO;AAEb,WAAO;MAAEA,OAAOD,IAAIC;MAAOC,SAAS;IAAK;EAC3C,YAAWF,SAAIG,YAAJH,mBAAaC,OAAO;AAE7B,WAAO;MAAEA,OAAOD,IAAIG,QAAQF;MAAOC,SAAS;IAAK;EACnD,YAAWF,SAAII,WAAJJ,mBAAYC,OAAO;AAE5B,WAAO;MAAEA,OAAOD,IAAII,OAAOH;MAAOC,SAAS;IAAK;EAClD;AACA,SAAO;IAAED,OAAO;IAAMC,SAAS;EAAK;AACtC,GAZ6B;AAkB7B,MAAMG,kBAAkB,gCAAUC,OAAK;AACrC,MAAIC,UAAUC,OAAOC,KAAKH,MAAMC,OAAO;AAEvCA,YAAUA,QAAQG,OAAO,CAACC,WAAWA,WAAW,MAAA;AAChDJ,YAAUA,QAAQK,IAAI,CAACD,WAAWA,OAAOE,YAAW,CAAA;AAEpD,SAAON;AACT,GAPwB;AAexB,MAAMO,sBAAsB,gCAAUR,OAAK;AACzC,SAAOA,MAAML,MAAMW,IAAI,CAACG,SAAAA;AACtB,WAAOA,KAAKC,OAAOC,QAAQ;EAC7B,CAAA;AACF,GAJ4B;AAW5B,MAAMC,YAAY,gCAAUC,mBAAiB;AAC3C,SAAO7B,yBAAyB8B,KAAKD,iBAAAA;AACvC,GAFkB;AASlB,MAAME,oBAAoB,gCAAUf,OAAOgB,UAAQ;AAEjD,QAAMC,QAAQC,MAAMC,QAAQnB,MAAMX,IAAI,IAAIW,MAAMX,OAAO;IAACW,MAAMX;;AAG9D,QAAM+B,YAAYH,MAAMI,QAAQ,CAAChC,SAAAA;AAC/B,QAAIiC;AACJ,QAAI,OAAOjC,SAAS,UAAU;AAC5BiC,gBAAUlC,2BAA2BC,IAAAA;IACvC,WAAWA,gBAAgBkC,QAAQ;AACjCD,gBAAU/B,sBAAsBF,IAAAA;IAClC,OAAO;AACL,aAAO,CAAA;IACT;AACA,UAAMmC,eACJR,YAAYM,YAAY,MAAMN,WAAW,GAAGA,QAAAA,GAAWM,OAAAA;AACzD,WAAO;MACL;QACEjC,MAAMmC;QACNvB,SAASF,gBAAgBC,KAAAA;QACzByB,aAAajB,oBAAoBR,KAAAA;MACnC;;EAEJ,CAAA;AAEA,SAAOoB;AACT,GA1B0B;AAiCnB,MAAMM,yBAAyB,gCAAUb,mBAAmBV,MAAI;AACrE,MAAIwB,eAAed,kBAAkBe,SAAQ;AAC7C,MAAIC,wBAAwB/C,+BAA+BgD,KAAKH,YAAAA;AAChE,MAAII,aAAa;AAEjB,SAAOnB,UAAUe,YAAAA,GAAe;AAC9B,UAAMK,YAAY7B,KAAK4B,UAAAA,EAAYpB;AACnC,UAAMsB,UAAU,IAAID,SAAAA;AAEpBL,mBAAeA,aAAarC,QAC1BP,wCACA,CAACmD,QAAAA;AAKC,UAAIA,IAAIC,WAAW,QAAA,GAAW;AAC5B,eAAO,MAAMF,OAAAA;MACf;AAEA,aAAOA;IACT,CAAA;AAGFF;EACF;AAEA,MAAIJ,iBAAiBd,kBAAkBe,SAAQ,GAAI;AACjDC,4BAAwB/C,+BAA+BgD,KAAKH,YAAAA;EAC9D;AAEA,SAAOE,sBAAsB,CAAA,EAAGvC,QAAQ,SAAS,GAAA;AACnD,GAhCsC;AAuC/B,MAAM8C,mBAAmB,gCAAUC,aAAaC,QAAM;AAC3D,MAAIC,SAASF;AACb,aAAW,CAACL,WAAWQ,UAAAA,KAAetC,OAAOuC,QAAQH,MAAAA,GAAS;AAC5DC,aAASA,OAAOjD,QAAQkD,YAAY,IAAIR,SAAAA,EAAW;EACrD;AACA,SAAOO;AACT,GANgC;AAchC,MAAMG,iBAAiB,gCAAUhD,KAAKsB,UAAUI,WAAS;AACvD,QAAMuB,aAAalD,cAAcC,GAAAA;AACjC,QAAMC,QAAQgD,WAAWhD;AACzB,QAAMC,UAAU+C,WAAW/C;AAE3BwB,cAAYA,aAAa,CAAA;AACzBJ,aAAWA,YAAY;AAEvB,MAAI,CAACrB,OAAO;AACV,QAAIyB,UAAUwB,QAAQ;AACpBxB,kBAAYyB,aAAazB,WAAW;QAClC;UACE/B,MAAM2B;UACNf,SAAS,CAAA;UACTwB,aAAa,CAAA;QACf;OACD;IACH;EACF,OAAO;AACLL,gBAAY0B,WAAWnD,OAAOqB,UAAUI,WAAWxB,OAAAA;EACrD;AAEA,SAAOwB;AACT,GAvBuB;AAkCvB,MAAMyB,eAAe,gCAAUE,kBAAkBC,gBAAc;AAC7DA,iBAAeC,QAAQ,CAACC,gBAAAA;AACtB,UAAMC,mBAAmBJ,iBAAiBK,KACxC,CAACC,aAAaA,SAAShE,SAAS6D,YAAY7D,IAAI;AAGlD,QAAI8D,qBAAqBG,QAAW;AAClC,YAAMC,aAAaL,YAAYjD,QAAQG,OACrC,CAACC,WAAW,CAAC8C,iBAAiBlD,QAAQuD,SAASnD,MAAAA,CAAAA;AAGjD8C,uBAAiBlD,UAAUkD,iBAAiBlD,QAAQwD,OAAOF,UAAAA;IAC7D,OAAO;AACLR,uBAAiBW,KAAKR,WAAAA;IACxB;EACF,CAAA;AAEA,SAAOH;AACT,GAlBqB;AA2BrB,MAAMD,aAAa,gCAAUnD,OAAOqB,UAAUI,WAAWxB,SAAO;AAC9DD,QAAMsD,QAAQ,CAACU,cAAAA;AACb,QAAIA,UAAU3D,OAAO;AACnB,YAAM4D,eAAe7C,kBAAkB4C,UAAU3D,OAAOgB,QAAAA;AAExDI,kBAAYyB,aAAazB,WAAWwC,YAAAA;IACtC,WAAWzE,oBAAoBqE,SAASG,UAAUhD,IAAI,GAAG;AACvD,UAAIkD,cAAc7C;AAElB,UAAIpB,YAAY,MAAM;AACpB,cAAMkE,sBAAsBhF,+BAA+BgC,KACzD6C,UAAUI,MAAM;AAElB,YAAID,qBAAqB;AACvB,gBAAME,aAAatC,uBACjBiC,UAAUI,QACVJ,UAAUxD,IAAI;AAEhB0D,yBAAe,IAAIG,UAAAA;QACrB,WACE,CAACL,UAAUtE,QACXsE,UAAUI,UACVJ,UAAUI,OAAOnC,SAAQ,MAAO1C,qBAChC;AACA2E,yBAAe,IAAItE,sBAAsBoE,UAAUI,MAAM,CAAA;QAC3D;MACF,WAAWnE,YAAY,MAAM;AAC3B,YAAI,CAAC+D,UAAUtE,MAAM;AACnB;QACF,WAAWsE,UAAUtE,SAAS,KAAK;AACjCwE,yBAAeF,UAAUtE,KAAK8C,WAAW,GAAA,IACrCwB,UAAUtE,OACV,IAAIsE,UAAUtE,IAAI;QACxB;MACF;AAEA+B,kBAAYsB,eAAeiB,UAAUjD,QAAQmD,aAAazC,SAAAA;IAC5D;EACF,CAAA;AAEA,SAAOA;AACT,GAzCmB;AA2CZ,MAAM6C,eAAe,gCAAUvE,KAAKsB,UAAQ;AACjD,QAAMI,YAAYsB,eAAehD,GAAAA;AACjC,QAAMwE,sBAAsB;IAAC;IAAO;IAAQ;IAAO;IAAU;;AAC7D,SAAO9C,UAAUC,QAAQ,CAACrB,UACxBA,MAAMC,QACHG,OAAO,CAACC,WAAW6D,oBAAoBV,SAASnD,OAAOE,YAAW,CAAA,CAAA,EAClED,IAAI,CAACD,YAAY;IAChBA;IACAhB,OAAO2B,WAAWhB,MAAMX,MAAMC,QAAQ,SAAS,GAAA;EACjD,EAAA,CAAA;AAEN,GAX4B;","names":["regExpToParseExpressPathRegExp","regExpToReplaceExpressPathRegExpParams","regExpExpressParamRegExp","regExpExpressPathParamRegExp","rootPathRegExpValue","stackItemValidNames","stripExpressPathParamRegex","path","replace","formatRegExpRoutePath","re","getRouterInfo","app","stack","version","_router","router","getRouteMethods","route","methods","Object","keys","filter","method","map","toUpperCase","getRouteMiddlewares","item","handle","name","hasParams","expressPathRegExp","test","parseExpressRoute","basePath","paths","Array","isArray","endpoints","flatMap","pathStr","RegExp","completePath","middlewares","parseExpressPathRegExp","parsedRegExp","toString","expressPathRegExpExec","exec","paramIndex","paramName","paramId","str","startsWith","parseExpressPath","expressPath","params","result","paramValue","entries","parseEndpoints","routerInfo","length","addEndpoints","parseStack","currentEndpoints","endpointsToAdd","forEach","newEndpoint","existingEndpoint","find","endpoint","undefined","newMethods","includes","concat","push","stackItem","newEndpoints","newBasePath","isExpressPathRegExp","regexp","parsedPath","getEndpoints","standardHttpMethods"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apitally",
3
- "version": "0.25.4",
3
+ "version": "0.25.6",
4
4
  "description": "Simple API monitoring & analytics for REST APIs built with Express, Fastify, NestJS, AdonisJS, Hono, H3, Elysia, Hapi, and Koa.",
5
5
  "author": "Apitally <hello@apitally.io>",
6
6
  "license": "MIT",
@@ -323,7 +323,7 @@
323
323
  "fastify": "^5.0.0",
324
324
  "fastify-plugin": "^5.0.1",
325
325
  "globals": "^17.0.0",
326
- "h3": "2.0.1-rc.16",
326
+ "h3": "2.0.1-rc.20",
327
327
  "hono": "^4.6.5",
328
328
  "koa": "^3.0.0",
329
329
  "koa-bodyparser": "^4.4.1",