a cache for slack profile pictures and emojis
1/**
2 * Analytics wrapper utility to eliminate boilerplate in route handlers
3 */
4
5import type { SlackCache } from "../cache";
6
7// Cache will be injected by the route system
8
9export type AnalyticsRecorder = (statusCode: number) => Promise<void>;
10export type RouteHandlerWithAnalytics = (
11 request: Request,
12 recordAnalytics: AnalyticsRecorder,
13) => Promise<Response> | Response;
14
15/**
16 * Creates analytics wrapper with injected cache
17 */
18export function createAnalyticsWrapper(cache: SlackCache) {
19 return function withAnalytics(
20 path: string,
21 method: string,
22 handler: RouteHandlerWithAnalytics,
23 ) {
24 return async (request: Request): Promise<Response> => {
25 const startTime = Date.now();
26
27 const recordAnalytics: AnalyticsRecorder = async (statusCode: number) => {
28 const userAgent = request.headers.get("user-agent") || "";
29 const ipAddress =
30 request.headers.get("x-forwarded-for") ||
31 request.headers.get("x-real-ip") ||
32 "unknown";
33 const referer = request.headers.get("referer") || undefined;
34
35 // Use the pathname for dynamic paths to ensure proper endpoint grouping
36 const requestUrl = new URL(request.url);
37 const analyticsPath = path.includes(":") ? requestUrl.pathname : path;
38
39 await cache.recordRequest(
40 analyticsPath,
41 method,
42 statusCode,
43 userAgent,
44 ipAddress,
45 Date.now() - startTime,
46 referer,
47 );
48 };
49
50 return handler(request, recordAnalytics);
51 };
52 };
53}
54
55/**
56 * Type-safe analytics wrapper that automatically infers path and method
57 */
58export function createAnalyticsHandler(
59 cache: SlackCache,
60 path: string,
61 method: string,
62) {
63 return (handler: RouteHandlerWithAnalytics) =>
64 createAnalyticsWrapper(cache)(path, method, handler);
65}