@od-oneapp/analytics 2026.1.1301
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.
- package/README.md +509 -0
- package/dist/ai-YMnynb-t.mjs +3347 -0
- package/dist/ai-YMnynb-t.mjs.map +1 -0
- package/dist/chunk-DQk6qfdC.mjs +18 -0
- package/dist/client-CTzJVFU5.mjs +9 -0
- package/dist/client-CTzJVFU5.mjs.map +1 -0
- package/dist/client-CcFTauAh.mjs +54 -0
- package/dist/client-CcFTauAh.mjs.map +1 -0
- package/dist/client-CeOLjbac.mjs +281 -0
- package/dist/client-CeOLjbac.mjs.map +1 -0
- package/dist/client-D339NFJS.mjs +267 -0
- package/dist/client-D339NFJS.mjs.map +1 -0
- package/dist/client-next.d.mts +62 -0
- package/dist/client-next.d.mts.map +1 -0
- package/dist/client-next.mjs +525 -0
- package/dist/client-next.mjs.map +1 -0
- package/dist/client.d.mts +30 -0
- package/dist/client.d.mts.map +1 -0
- package/dist/client.mjs +186 -0
- package/dist/client.mjs.map +1 -0
- package/dist/config-DPS6bSYo.d.mts +34 -0
- package/dist/config-DPS6bSYo.d.mts.map +1 -0
- package/dist/config-P6P5adJg.mjs +287 -0
- package/dist/config-P6P5adJg.mjs.map +1 -0
- package/dist/console-8bND3mMU.mjs +128 -0
- package/dist/console-8bND3mMU.mjs.map +1 -0
- package/dist/ecommerce-Cgu4wlux.mjs +993 -0
- package/dist/ecommerce-Cgu4wlux.mjs.map +1 -0
- package/dist/emitters-6-nKo8i-.mjs +208 -0
- package/dist/emitters-6-nKo8i-.mjs.map +1 -0
- package/dist/emitters-DldkVSPp.d.mts +12 -0
- package/dist/emitters-DldkVSPp.d.mts.map +1 -0
- package/dist/index-BfNWgfa5.d.mts +1494 -0
- package/dist/index-BfNWgfa5.d.mts.map +1 -0
- package/dist/index-BkIWe--N.d.mts +953 -0
- package/dist/index-BkIWe--N.d.mts.map +1 -0
- package/dist/index-jPzXRn52.d.mts +184 -0
- package/dist/index-jPzXRn52.d.mts.map +1 -0
- package/dist/manager-DvRRjza6.d.mts +76 -0
- package/dist/manager-DvRRjza6.d.mts.map +1 -0
- package/dist/posthog-bootstrap-CYfIy_WS.mjs +1769 -0
- package/dist/posthog-bootstrap-CYfIy_WS.mjs.map +1 -0
- package/dist/posthog-bootstrap-DWxFrxlt.d.mts +81 -0
- package/dist/posthog-bootstrap-DWxFrxlt.d.mts.map +1 -0
- package/dist/providers-http-client.d.mts +37 -0
- package/dist/providers-http-client.d.mts.map +1 -0
- package/dist/providers-http-client.mjs +320 -0
- package/dist/providers-http-client.mjs.map +1 -0
- package/dist/providers-http-server.d.mts +31 -0
- package/dist/providers-http-server.d.mts.map +1 -0
- package/dist/providers-http-server.mjs +297 -0
- package/dist/providers-http-server.mjs.map +1 -0
- package/dist/providers-http.d.mts +46 -0
- package/dist/providers-http.d.mts.map +1 -0
- package/dist/providers-http.mjs +4 -0
- package/dist/server-edge.d.mts +9 -0
- package/dist/server-edge.d.mts.map +1 -0
- package/dist/server-edge.mjs +373 -0
- package/dist/server-edge.mjs.map +1 -0
- package/dist/server-next.d.mts +67 -0
- package/dist/server-next.d.mts.map +1 -0
- package/dist/server-next.mjs +193 -0
- package/dist/server-next.mjs.map +1 -0
- package/dist/server.d.mts +10 -0
- package/dist/server.mjs +7 -0
- package/dist/service-cYtBBL8x.mjs +945 -0
- package/dist/service-cYtBBL8x.mjs.map +1 -0
- package/dist/shared.d.mts +16 -0
- package/dist/shared.d.mts.map +1 -0
- package/dist/shared.mjs +93 -0
- package/dist/shared.mjs.map +1 -0
- package/dist/types-BxBnNQ0V.d.mts +354 -0
- package/dist/types-BxBnNQ0V.d.mts.map +1 -0
- package/dist/types-CBvxUEaF.d.mts +216 -0
- package/dist/types-CBvxUEaF.d.mts.map +1 -0
- package/dist/types.d.mts +4 -0
- package/dist/types.mjs +0 -0
- package/dist/vercel-types-lwakUfoI.d.mts +102 -0
- package/dist/vercel-types-lwakUfoI.d.mts.map +1 -0
- package/package.json +129 -0
- package/src/client/index.ts +164 -0
- package/src/client/manager.ts +71 -0
- package/src/client/next/components.tsx +270 -0
- package/src/client/next/hooks.ts +217 -0
- package/src/client/next/manager.ts +141 -0
- package/src/client/next.ts +144 -0
- package/src/client-next.ts +101 -0
- package/src/client.ts +89 -0
- package/src/examples/ai-sdk-patterns.ts +583 -0
- package/src/examples/emitter-patterns.ts +476 -0
- package/src/examples/nextjs-emitter-patterns.tsx +403 -0
- package/src/next/app-router.tsx +564 -0
- package/src/next/client.ts +419 -0
- package/src/next/index.ts +84 -0
- package/src/next/middleware.ts +429 -0
- package/src/next/rsc.tsx +300 -0
- package/src/next/server.ts +253 -0
- package/src/next/types.d.ts +220 -0
- package/src/providers/base-provider.ts +419 -0
- package/src/providers/console/client.ts +10 -0
- package/src/providers/console/index.ts +152 -0
- package/src/providers/console/server.ts +6 -0
- package/src/providers/console/types.ts +15 -0
- package/src/providers/http/client.ts +464 -0
- package/src/providers/http/index.ts +30 -0
- package/src/providers/http/server.ts +396 -0
- package/src/providers/http/types.ts +135 -0
- package/src/providers/posthog/client.ts +518 -0
- package/src/providers/posthog/index.ts +11 -0
- package/src/providers/posthog/server.ts +329 -0
- package/src/providers/posthog/types.ts +104 -0
- package/src/providers/segment/client.ts +113 -0
- package/src/providers/segment/index.ts +11 -0
- package/src/providers/segment/server.ts +115 -0
- package/src/providers/segment/types.ts +51 -0
- package/src/providers/vercel/client.ts +102 -0
- package/src/providers/vercel/index.ts +11 -0
- package/src/providers/vercel/server.ts +89 -0
- package/src/providers/vercel/types.ts +27 -0
- package/src/server/index.ts +103 -0
- package/src/server/manager.ts +62 -0
- package/src/server/next.ts +210 -0
- package/src/server-edge.ts +442 -0
- package/src/server-next.ts +39 -0
- package/src/server.ts +106 -0
- package/src/shared/emitters/ai/README.md +981 -0
- package/src/shared/emitters/ai/events/agent.ts +130 -0
- package/src/shared/emitters/ai/events/artifacts.ts +167 -0
- package/src/shared/emitters/ai/events/chat.ts +126 -0
- package/src/shared/emitters/ai/events/chatbot-ecommerce.ts +133 -0
- package/src/shared/emitters/ai/events/completion.ts +103 -0
- package/src/shared/emitters/ai/events/content-generation.ts +347 -0
- package/src/shared/emitters/ai/events/conversation.ts +332 -0
- package/src/shared/emitters/ai/events/product-features.ts +1402 -0
- package/src/shared/emitters/ai/events/streaming.ts +114 -0
- package/src/shared/emitters/ai/events/tool.ts +93 -0
- package/src/shared/emitters/ai/index.ts +69 -0
- package/src/shared/emitters/ai/track-ai-sdk.ts +74 -0
- package/src/shared/emitters/ai/track-ai.ts +50 -0
- package/src/shared/emitters/ai/types.ts +1041 -0
- package/src/shared/emitters/ai/utils.ts +468 -0
- package/src/shared/emitters/ecommerce/events/cart-checkout.ts +106 -0
- package/src/shared/emitters/ecommerce/events/coupon.ts +49 -0
- package/src/shared/emitters/ecommerce/events/engagement.ts +61 -0
- package/src/shared/emitters/ecommerce/events/marketplace.ts +119 -0
- package/src/shared/emitters/ecommerce/events/order.ts +199 -0
- package/src/shared/emitters/ecommerce/events/product.ts +205 -0
- package/src/shared/emitters/ecommerce/events/registry.ts +123 -0
- package/src/shared/emitters/ecommerce/events/wishlist-sharing.ts +140 -0
- package/src/shared/emitters/ecommerce/index.ts +46 -0
- package/src/shared/emitters/ecommerce/track-ecommerce.ts +53 -0
- package/src/shared/emitters/ecommerce/types.ts +314 -0
- package/src/shared/emitters/ecommerce/utils.ts +216 -0
- package/src/shared/emitters/emitter-types.ts +974 -0
- package/src/shared/emitters/emitters.ts +292 -0
- package/src/shared/emitters/helpers.ts +419 -0
- package/src/shared/emitters/index.ts +66 -0
- package/src/shared/index.ts +142 -0
- package/src/shared/ingestion/index.ts +66 -0
- package/src/shared/ingestion/schemas.ts +386 -0
- package/src/shared/ingestion/service.ts +628 -0
- package/src/shared/node22-features.ts +848 -0
- package/src/shared/providers/console-provider.ts +160 -0
- package/src/shared/types/base-types.ts +54 -0
- package/src/shared/types/console-types.ts +19 -0
- package/src/shared/types/posthog-types.ts +131 -0
- package/src/shared/types/segment-types.ts +15 -0
- package/src/shared/types/types.ts +397 -0
- package/src/shared/types/vercel-types.ts +19 -0
- package/src/shared/utils/config-client.ts +19 -0
- package/src/shared/utils/config.ts +250 -0
- package/src/shared/utils/emitter-adapter.ts +212 -0
- package/src/shared/utils/manager.test.ts +36 -0
- package/src/shared/utils/manager.ts +1322 -0
- package/src/shared/utils/posthog-bootstrap.ts +136 -0
- package/src/shared/utils/posthog-client-utils.ts +48 -0
- package/src/shared/utils/posthog-next-utils.ts +282 -0
- package/src/shared/utils/posthog-server-utils.ts +210 -0
- package/src/shared/utils/rate-limit.ts +289 -0
- package/src/shared/utils/security.ts +545 -0
- package/src/shared/utils/validation-client.ts +161 -0
- package/src/shared/utils/validation.ts +399 -0
- package/src/shared.ts +155 -0
- package/src/types/index.ts +62 -0
|
@@ -0,0 +1,429 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Analytics middleware factory - framework agnostic
|
|
3
|
+
*
|
|
4
|
+
* Provides automatic tracking and context extraction for analytics.
|
|
5
|
+
* Framework-agnostic implementation that can be used with Next.js or other frameworks.
|
|
6
|
+
*
|
|
7
|
+
* @remarks
|
|
8
|
+
* This middleware:
|
|
9
|
+
* - Automatically tracks page views and API routes
|
|
10
|
+
* - Extracts analytics context from request headers
|
|
11
|
+
* - Manages PostHog distinct IDs via cookies
|
|
12
|
+
* - Supports route exclusion patterns
|
|
13
|
+
* - Provides framework-agnostic interfaces
|
|
14
|
+
*
|
|
15
|
+
* @module @od-oneapp/analytics/next/middleware
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { generateDistinctId, getDistinctIdFromCookies } from '../shared/utils/posthog-bootstrap';
|
|
19
|
+
|
|
20
|
+
import type { AnalyticsConfig } from '../shared/types/types';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Request interface for middleware (framework-agnostic)
|
|
24
|
+
*/
|
|
25
|
+
export interface MiddlewareRequest {
|
|
26
|
+
cookies: {
|
|
27
|
+
get: (name: string) => { value: string } | undefined;
|
|
28
|
+
set: (name: string, value: string, options?: any) => void;
|
|
29
|
+
};
|
|
30
|
+
headers: Headers;
|
|
31
|
+
method: string;
|
|
32
|
+
nextUrl: {
|
|
33
|
+
pathname: string;
|
|
34
|
+
searchParams: URLSearchParams;
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Response interface for middleware (framework-agnostic)
|
|
40
|
+
*/
|
|
41
|
+
export interface MiddlewareResponse {
|
|
42
|
+
headers: Headers;
|
|
43
|
+
status: number;
|
|
44
|
+
cookies: {
|
|
45
|
+
set: (name: string, value: string, options?: any) => void;
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface AnalyticsMiddlewareConfig {
|
|
50
|
+
/**
|
|
51
|
+
* Analytics configuration
|
|
52
|
+
*/
|
|
53
|
+
analytics: AnalyticsConfig;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* PostHog API key for distinct ID management
|
|
57
|
+
*/
|
|
58
|
+
posthogApiKey?: string;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Routes to exclude from tracking
|
|
62
|
+
*/
|
|
63
|
+
excludeRoutes?: string[] | RegExp[];
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Custom route matcher function
|
|
67
|
+
*/
|
|
68
|
+
shouldTrack?: (pathname: string) => boolean;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Enable debug logging
|
|
72
|
+
*/
|
|
73
|
+
debug?: boolean;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Custom event name generator
|
|
77
|
+
*/
|
|
78
|
+
getEventName?: (pathname: string, method: string) => string;
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Custom properties extractor
|
|
82
|
+
*/
|
|
83
|
+
getProperties?: (request: MiddlewareRequest) => Record<string, any>;
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Track page views automatically
|
|
87
|
+
*/
|
|
88
|
+
trackPageViews?: boolean;
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Track API routes automatically
|
|
92
|
+
*/
|
|
93
|
+
trackApiRoutes?: boolean;
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Factory function to create response (framework-specific)
|
|
97
|
+
*/
|
|
98
|
+
createResponse: () => MiddlewareResponse;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Create analytics middleware (framework-agnostic)
|
|
103
|
+
* @param config - Middleware configuration
|
|
104
|
+
* @returns Middleware function that accepts a request and returns a response
|
|
105
|
+
*/
|
|
106
|
+
export function createAnalyticsMiddleware(config: AnalyticsMiddlewareConfig) {
|
|
107
|
+
return async function analyticsMiddleware(
|
|
108
|
+
request: MiddlewareRequest,
|
|
109
|
+
): Promise<MiddlewareResponse> {
|
|
110
|
+
const { pathname, searchParams } = request.nextUrl;
|
|
111
|
+
const { method } = request;
|
|
112
|
+
|
|
113
|
+
// Check if we should track this route
|
|
114
|
+
if (!shouldTrackRoute(pathname, config)) {
|
|
115
|
+
return config.createResponse();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Create response object to modify headers/cookies
|
|
119
|
+
const response = config.createResponse();
|
|
120
|
+
|
|
121
|
+
try {
|
|
122
|
+
// Handle distinct ID for PostHog
|
|
123
|
+
if (config.posthogApiKey) {
|
|
124
|
+
const distinctId = await ensureDistinctId(request, response, config.posthogApiKey);
|
|
125
|
+
|
|
126
|
+
// Add distinct ID to response headers for downstream use
|
|
127
|
+
response.headers.set('x-analytics-distinct-id', distinctId);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Extract analytics context
|
|
131
|
+
const context = extractAnalyticsContext(request);
|
|
132
|
+
|
|
133
|
+
// Add context to response headers for server components
|
|
134
|
+
response.headers.set('x-analytics-context', JSON.stringify(context));
|
|
135
|
+
|
|
136
|
+
// Track the request if edge runtime analytics is available
|
|
137
|
+
if (globalThis.analytics && typeof globalThis.analytics.track === 'function') {
|
|
138
|
+
const eventName = config.getEventName
|
|
139
|
+
? config.getEventName(pathname, method)
|
|
140
|
+
: getDefaultEventName(pathname, method, config);
|
|
141
|
+
|
|
142
|
+
const properties = {
|
|
143
|
+
...context,
|
|
144
|
+
...(config.getProperties ? config.getProperties(request) : {}),
|
|
145
|
+
pathname,
|
|
146
|
+
method,
|
|
147
|
+
search_params: Object.fromEntries(searchParams.entries()),
|
|
148
|
+
timestamp: new Date().toISOString(),
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
// Track asynchronously without blocking response
|
|
152
|
+
// Use async IIFE to properly handle the promise
|
|
153
|
+
void (async () => {
|
|
154
|
+
try {
|
|
155
|
+
// Re-check analytics exists inside async context for TypeScript
|
|
156
|
+
if (globalThis.analytics && typeof globalThis.analytics.track === 'function') {
|
|
157
|
+
await globalThis.analytics.track(eventName, properties);
|
|
158
|
+
}
|
|
159
|
+
} catch {
|
|
160
|
+
// Silently fail to avoid disrupting request
|
|
161
|
+
}
|
|
162
|
+
})();
|
|
163
|
+
}
|
|
164
|
+
} catch {
|
|
165
|
+
// Don't let analytics errors break the app
|
|
166
|
+
if (config.debug) {
|
|
167
|
+
// Log error in debug mode only
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return response;
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Ensure distinct ID exists for PostHog tracking
|
|
177
|
+
*/
|
|
178
|
+
async function ensureDistinctId(
|
|
179
|
+
request: MiddlewareRequest,
|
|
180
|
+
response: MiddlewareResponse,
|
|
181
|
+
apiKey: string,
|
|
182
|
+
): Promise<string> {
|
|
183
|
+
// Try to get existing distinct ID from cookies
|
|
184
|
+
const existingId = getDistinctIdFromCookies(request.cookies as any, apiKey);
|
|
185
|
+
|
|
186
|
+
if (existingId) {
|
|
187
|
+
return existingId;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Generate new distinct ID
|
|
191
|
+
const newId = generateDistinctId();
|
|
192
|
+
|
|
193
|
+
// Set cookie with appropriate options
|
|
194
|
+
const cookieName = `ph_${apiKey}_posthog`;
|
|
195
|
+
response.cookies.set(cookieName, JSON.stringify({ distinct_id: newId }), {
|
|
196
|
+
httpOnly: false, // PostHog JS needs to read this
|
|
197
|
+
maxAge: 365 * 24 * 60 * 60, // 1 year
|
|
198
|
+
path: '/',
|
|
199
|
+
sameSite: 'lax',
|
|
200
|
+
secure: process.env.NODE_ENV === 'production',
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
return newId;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Check if route should be tracked
|
|
208
|
+
*/
|
|
209
|
+
function shouldTrackRoute(pathname: string, config: AnalyticsMiddlewareConfig): boolean {
|
|
210
|
+
// Use custom matcher if provided
|
|
211
|
+
if (config.shouldTrack) {
|
|
212
|
+
return config.shouldTrack(pathname);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Check exclude routes
|
|
216
|
+
if (config.excludeRoutes) {
|
|
217
|
+
for (const exclude of config.excludeRoutes) {
|
|
218
|
+
if (typeof exclude === 'string') {
|
|
219
|
+
if (pathname.startsWith(exclude)) {
|
|
220
|
+
return false;
|
|
221
|
+
}
|
|
222
|
+
} else if (exclude instanceof RegExp && exclude.test(pathname)) {
|
|
223
|
+
return false;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Default exclusions
|
|
229
|
+
const defaultExcludes = [
|
|
230
|
+
'/_next/',
|
|
231
|
+
'/favicon.ico',
|
|
232
|
+
'/robots.txt',
|
|
233
|
+
'/sitemap.xml',
|
|
234
|
+
'/.well-known/',
|
|
235
|
+
];
|
|
236
|
+
|
|
237
|
+
for (const exclude of defaultExcludes) {
|
|
238
|
+
if (pathname.startsWith(exclude)) {
|
|
239
|
+
return false;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Check if we should track this type of route
|
|
244
|
+
const isApiRoute = pathname.startsWith('/api/');
|
|
245
|
+
|
|
246
|
+
if (isApiRoute && config.trackApiRoutes === false) {
|
|
247
|
+
return false;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (!isApiRoute && config.trackPageViews === false) {
|
|
251
|
+
return false;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return true;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Extract analytics context from request
|
|
259
|
+
*/
|
|
260
|
+
function extractAnalyticsContext(request: MiddlewareRequest): Record<string, any> {
|
|
261
|
+
const { headers } = request;
|
|
262
|
+
|
|
263
|
+
return {
|
|
264
|
+
// User agent info
|
|
265
|
+
user_agent: headers.get('user-agent') ?? undefined,
|
|
266
|
+
|
|
267
|
+
// Referrer info
|
|
268
|
+
referer: headers.get('referer') ?? undefined,
|
|
269
|
+
referrer: headers.get('referrer') ?? undefined,
|
|
270
|
+
|
|
271
|
+
// IP and location info (if available)
|
|
272
|
+
ip:
|
|
273
|
+
headers.get('x-forwarded-for')?.split(',')[0]?.trim() ??
|
|
274
|
+
headers.get('x-real-ip') ??
|
|
275
|
+
undefined,
|
|
276
|
+
|
|
277
|
+
city: headers.get('x-vercel-ip-city') ?? undefined,
|
|
278
|
+
country: headers.get('x-vercel-ip-country') ?? undefined,
|
|
279
|
+
region: headers.get('x-vercel-ip-country-region') ?? undefined,
|
|
280
|
+
|
|
281
|
+
// Request info
|
|
282
|
+
host: headers.get('host') ?? undefined,
|
|
283
|
+
protocol: headers.get('x-forwarded-proto') ?? 'https',
|
|
284
|
+
|
|
285
|
+
accept_encoding: headers.get('accept-encoding') ?? undefined,
|
|
286
|
+
// Accept headers
|
|
287
|
+
accept_language: headers.get('accept-language') ?? undefined,
|
|
288
|
+
|
|
289
|
+
deployment_id: headers.get('x-vercel-deployment-id') ?? undefined,
|
|
290
|
+
// Edge runtime info
|
|
291
|
+
edge_region: headers.get('x-vercel-edge-region') ?? undefined,
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Get default event name based on route type
|
|
297
|
+
*/
|
|
298
|
+
function getDefaultEventName(
|
|
299
|
+
pathname: string,
|
|
300
|
+
method: string,
|
|
301
|
+
_config: AnalyticsMiddlewareConfig,
|
|
302
|
+
): string {
|
|
303
|
+
const isApiRoute = pathname.startsWith('/api/');
|
|
304
|
+
|
|
305
|
+
if (isApiRoute) {
|
|
306
|
+
return `API ${method} Request`;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
return 'Page View';
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Middleware configuration helper for common patterns (framework-agnostic)
|
|
314
|
+
* Note: createResponse must be provided by the framework-specific wrapper
|
|
315
|
+
*/
|
|
316
|
+
export function createAnalyticsMiddlewareConfig(options: {
|
|
317
|
+
createResponse: () => MiddlewareResponse;
|
|
318
|
+
posthogApiKey?: string;
|
|
319
|
+
segmentWriteKey?: string;
|
|
320
|
+
excludeRoutes?: string[] | RegExp[];
|
|
321
|
+
debug?: boolean;
|
|
322
|
+
}): AnalyticsMiddlewareConfig {
|
|
323
|
+
const config: AnalyticsMiddlewareConfig = {
|
|
324
|
+
analytics: {
|
|
325
|
+
providers: {},
|
|
326
|
+
},
|
|
327
|
+
createResponse: options.createResponse,
|
|
328
|
+
...(options.debug !== undefined && { debug: options.debug }),
|
|
329
|
+
...(options.excludeRoutes !== undefined && { excludeRoutes: options.excludeRoutes }),
|
|
330
|
+
...(options.posthogApiKey !== undefined && { posthogApiKey: options.posthogApiKey }),
|
|
331
|
+
trackApiRoutes: true,
|
|
332
|
+
trackPageViews: true,
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
// Add providers based on configuration
|
|
336
|
+
if (options.posthogApiKey) {
|
|
337
|
+
config.analytics.providers.posthog = {
|
|
338
|
+
apiKey: options.posthogApiKey,
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
if (options.segmentWriteKey) {
|
|
343
|
+
config.analytics.providers.segment = {
|
|
344
|
+
writeKey: options.segmentWriteKey,
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
return config;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Extract analytics context from headers (framework-agnostic)
|
|
353
|
+
*/
|
|
354
|
+
export function getAnalyticsContextFromHeaders(headers: Headers): Record<string, any> {
|
|
355
|
+
const contextHeader = headers.get('x-analytics-context');
|
|
356
|
+
|
|
357
|
+
if (!contextHeader) {
|
|
358
|
+
return {};
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
try {
|
|
362
|
+
return JSON.parse(contextHeader);
|
|
363
|
+
} catch {
|
|
364
|
+
return {};
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Get distinct ID from headers (for server components)
|
|
370
|
+
*/
|
|
371
|
+
export function getDistinctIdFromHeaders(headers: Headers): string | null {
|
|
372
|
+
return headers.get('x-analytics-distinct-id');
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Conditional middleware helper
|
|
377
|
+
*/
|
|
378
|
+
export function conditionalAnalyticsMiddleware(
|
|
379
|
+
config: AnalyticsMiddlewareConfig,
|
|
380
|
+
condition: (request: MiddlewareRequest) => boolean,
|
|
381
|
+
) {
|
|
382
|
+
const middleware = createAnalyticsMiddleware(config);
|
|
383
|
+
|
|
384
|
+
return async function (request: MiddlewareRequest): Promise<MiddlewareResponse> {
|
|
385
|
+
if (condition(request)) {
|
|
386
|
+
return middleware(request);
|
|
387
|
+
}
|
|
388
|
+
return config.createResponse();
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* Compose multiple middleware functions
|
|
394
|
+
*/
|
|
395
|
+
export function composeMiddleware(
|
|
396
|
+
...middlewares: ((
|
|
397
|
+
request: MiddlewareRequest,
|
|
398
|
+
createResponse?: () => MiddlewareResponse,
|
|
399
|
+
) => Promise<MiddlewareResponse>)[]
|
|
400
|
+
) {
|
|
401
|
+
return async function composedMiddleware(
|
|
402
|
+
request: MiddlewareRequest,
|
|
403
|
+
createResponse: () => MiddlewareResponse,
|
|
404
|
+
): Promise<MiddlewareResponse> {
|
|
405
|
+
let response = createResponse();
|
|
406
|
+
|
|
407
|
+
for (const middleware of middlewares) {
|
|
408
|
+
response = await middleware(request, createResponse);
|
|
409
|
+
|
|
410
|
+
// If middleware returns a redirect or error, stop processing
|
|
411
|
+
if (response.status !== 200 || response.headers.get('location')) {
|
|
412
|
+
return response;
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
return response;
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// Type declaration for edge runtime global analytics
|
|
421
|
+
declare global {
|
|
422
|
+
var analytics:
|
|
423
|
+
| {
|
|
424
|
+
track: (event: string, properties?: any) => Promise<void>;
|
|
425
|
+
identify: (userId: string, traits?: any) => Promise<void>;
|
|
426
|
+
page: (name?: string, properties?: any) => Promise<void>;
|
|
427
|
+
}
|
|
428
|
+
| undefined;
|
|
429
|
+
}
|