@float.js/core 2.0.1

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.
@@ -0,0 +1,499 @@
1
+ // src/analytics/index.ts
2
+ var DEFAULT_CONFIG = {
3
+ enabled: true,
4
+ ignorePaths: [/^\/__float/, /^\/api\//, /\.(ico|png|jpg|css|js)$/],
5
+ maxBufferSize: 100,
6
+ flushInterval: 3e4,
7
+ // 30 seconds
8
+ hashIPs: true,
9
+ trackVitals: true,
10
+ sessionTimeout: 30,
11
+ // minutes
12
+ geoIP: false
13
+ };
14
+ function generateId() {
15
+ return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
16
+ }
17
+ function hashString(str) {
18
+ let hash = 0;
19
+ for (let i = 0; i < str.length; i++) {
20
+ const char = str.charCodeAt(i);
21
+ hash = (hash << 5) - hash + char;
22
+ hash = hash & hash;
23
+ }
24
+ return Math.abs(hash).toString(36);
25
+ }
26
+ function parseUserAgent(ua) {
27
+ if (!ua) {
28
+ return { device: "unknown", browser: "Unknown", os: "Unknown" };
29
+ }
30
+ let device = "desktop";
31
+ if (/Mobile|Android.*Mobile|iPhone|iPod|BlackBerry|IEMobile|Opera Mini/i.test(ua)) {
32
+ device = "mobile";
33
+ } else if (/iPad|Android(?!.*Mobile)|Tablet/i.test(ua)) {
34
+ device = "tablet";
35
+ }
36
+ let browser = "Unknown";
37
+ if (/Firefox/i.test(ua)) browser = "Firefox";
38
+ else if (/Edg/i.test(ua)) browser = "Edge";
39
+ else if (/Chrome/i.test(ua)) browser = "Chrome";
40
+ else if (/Safari/i.test(ua)) browser = "Safari";
41
+ else if (/Opera|OPR/i.test(ua)) browser = "Opera";
42
+ let os = "Unknown";
43
+ if (/Windows/i.test(ua)) os = "Windows";
44
+ else if (/Mac OS X/i.test(ua)) os = "macOS";
45
+ else if (/Linux/i.test(ua)) os = "Linux";
46
+ else if (/Android/i.test(ua)) os = "Android";
47
+ else if (/iOS|iPhone|iPad/i.test(ua)) os = "iOS";
48
+ return { device, browser, os };
49
+ }
50
+ function percentile(arr, p) {
51
+ if (arr.length === 0) return 0;
52
+ const sorted = [...arr].sort((a, b) => a - b);
53
+ const index = Math.ceil(p / 100 * sorted.length) - 1;
54
+ return sorted[Math.max(0, index)];
55
+ }
56
+ var AnalyticsEngine = class {
57
+ config;
58
+ buffer = {
59
+ pageviews: [],
60
+ vitals: [],
61
+ events: []
62
+ };
63
+ flushTimer = null;
64
+ sessions = /* @__PURE__ */ new Map();
65
+ allData = {
66
+ pageviews: [],
67
+ vitals: [],
68
+ events: []
69
+ };
70
+ constructor(config = {}) {
71
+ this.config = { ...DEFAULT_CONFIG, ...config };
72
+ this.startFlushTimer();
73
+ }
74
+ startFlushTimer() {
75
+ if (this.flushTimer) clearInterval(this.flushTimer);
76
+ this.flushTimer = setInterval(() => {
77
+ this.flush();
78
+ }, this.config.flushInterval);
79
+ }
80
+ shouldIgnore(pathname) {
81
+ return this.config.ignorePaths.some((pattern) => {
82
+ if (typeof pattern === "string") {
83
+ return pathname.startsWith(pattern);
84
+ }
85
+ return pattern.test(pathname);
86
+ });
87
+ }
88
+ getOrCreateSession(ip) {
89
+ const sessionKey = this.config.hashIPs ? hashString(ip) : ip;
90
+ const existing = this.sessions.get(sessionKey);
91
+ const now = Date.now();
92
+ const timeout = this.config.sessionTimeout * 60 * 1e3;
93
+ if (existing && now - existing.lastActivity < timeout) {
94
+ existing.lastActivity = now;
95
+ existing.views++;
96
+ return sessionKey;
97
+ }
98
+ this.sessions.set(sessionKey, { lastActivity: now, views: 1 });
99
+ return sessionKey;
100
+ }
101
+ /**
102
+ * Track a page view
103
+ */
104
+ trackPageview(req, options = {}) {
105
+ if (!this.config.enabled) return null;
106
+ const url = new URL(req.url);
107
+ if (this.shouldIgnore(url.pathname)) return null;
108
+ const ip = req.headers.get("x-forwarded-for")?.split(",")[0] || req.headers.get("x-real-ip") || "unknown";
109
+ const userAgent = req.headers.get("user-agent") || void 0;
110
+ const { device, browser, os } = parseUserAgent(userAgent);
111
+ const sessionId = this.getOrCreateSession(ip);
112
+ const pageview = {
113
+ id: generateId(),
114
+ timestamp: Date.now(),
115
+ pathname: url.pathname,
116
+ referrer: req.headers.get("referer") || void 0,
117
+ userAgent: userAgent?.substring(0, 200),
118
+ // Truncate for storage
119
+ country: options.country,
120
+ device,
121
+ browser,
122
+ os,
123
+ sessionId
124
+ };
125
+ this.buffer.pageviews.push(pageview);
126
+ this.checkBufferSize();
127
+ return pageview;
128
+ }
129
+ /**
130
+ * Track Web Vitals
131
+ */
132
+ trackVitals(pathname, metrics) {
133
+ if (!this.config.enabled || !this.config.trackVitals) return null;
134
+ const vitals = {
135
+ id: generateId(),
136
+ timestamp: Date.now(),
137
+ pathname,
138
+ metrics
139
+ };
140
+ this.buffer.vitals.push(vitals);
141
+ this.checkBufferSize();
142
+ return vitals;
143
+ }
144
+ /**
145
+ * Track custom event
146
+ */
147
+ trackEvent(name, properties, req) {
148
+ if (!this.config.enabled) return null;
149
+ let pathname = "/";
150
+ let sessionId = generateId();
151
+ if (req) {
152
+ const url = new URL(req.url);
153
+ pathname = url.pathname;
154
+ const ip = req.headers.get("x-forwarded-for")?.split(",")[0] || req.headers.get("x-real-ip") || "unknown";
155
+ sessionId = this.getOrCreateSession(ip);
156
+ }
157
+ const event = {
158
+ id: generateId(),
159
+ timestamp: Date.now(),
160
+ name,
161
+ pathname,
162
+ properties,
163
+ sessionId
164
+ };
165
+ this.buffer.events.push(event);
166
+ this.checkBufferSize();
167
+ return event;
168
+ }
169
+ checkBufferSize() {
170
+ const totalSize = this.buffer.pageviews.length + this.buffer.vitals.length + this.buffer.events.length;
171
+ if (totalSize >= this.config.maxBufferSize) {
172
+ this.flush();
173
+ }
174
+ }
175
+ /**
176
+ * Flush buffer to storage/handler
177
+ */
178
+ async flush() {
179
+ if (this.buffer.pageviews.length === 0 && this.buffer.vitals.length === 0 && this.buffer.events.length === 0) {
180
+ return;
181
+ }
182
+ const dataToFlush = { ...this.buffer };
183
+ this.buffer = { pageviews: [], vitals: [], events: [] };
184
+ this.allData.pageviews.push(...dataToFlush.pageviews);
185
+ this.allData.vitals.push(...dataToFlush.vitals);
186
+ this.allData.events.push(...dataToFlush.events);
187
+ if (this.allData.pageviews.length > 1e4) {
188
+ this.allData.pageviews = this.allData.pageviews.slice(-1e4);
189
+ }
190
+ if (this.allData.vitals.length > 1e4) {
191
+ this.allData.vitals = this.allData.vitals.slice(-1e4);
192
+ }
193
+ if (this.allData.events.length > 1e4) {
194
+ this.allData.events = this.allData.events.slice(-1e4);
195
+ }
196
+ if (this.config.onFlush) {
197
+ await this.config.onFlush(dataToFlush);
198
+ }
199
+ }
200
+ /**
201
+ * Get analytics summary
202
+ */
203
+ getSummary(startDate, endDate) {
204
+ const start = startDate?.getTime() || Date.now() - 7 * 24 * 60 * 60 * 1e3;
205
+ const end = endDate?.getTime() || Date.now();
206
+ const pageviews = this.allData.pageviews.filter(
207
+ (pv) => pv.timestamp >= start && pv.timestamp <= end
208
+ );
209
+ const vitals = this.allData.vitals.filter(
210
+ (v) => v.timestamp >= start && v.timestamp <= end
211
+ );
212
+ const events = this.allData.events.filter(
213
+ (e) => e.timestamp >= start && e.timestamp <= end
214
+ );
215
+ const uniqueSessions = new Set(pageviews.map((pv) => pv.sessionId));
216
+ const byPath = {};
217
+ const byReferrer = {};
218
+ const byDevice = {};
219
+ const byBrowser = {};
220
+ const byCountry = {};
221
+ for (const pv of pageviews) {
222
+ byPath[pv.pathname] = (byPath[pv.pathname] || 0) + 1;
223
+ if (pv.referrer) {
224
+ try {
225
+ const ref = new URL(pv.referrer).hostname;
226
+ byReferrer[ref] = (byReferrer[ref] || 0) + 1;
227
+ } catch {
228
+ byReferrer["direct"] = (byReferrer["direct"] || 0) + 1;
229
+ }
230
+ } else {
231
+ byReferrer["direct"] = (byReferrer["direct"] || 0) + 1;
232
+ }
233
+ byDevice[pv.device] = (byDevice[pv.device] || 0) + 1;
234
+ byBrowser[pv.browser] = (byBrowser[pv.browser] || 0) + 1;
235
+ if (pv.country) {
236
+ byCountry[pv.country] = (byCountry[pv.country] || 0) + 1;
237
+ }
238
+ }
239
+ const fcpValues = vitals.filter((v) => v.metrics.FCP).map((v) => v.metrics.FCP);
240
+ const lcpValues = vitals.filter((v) => v.metrics.LCP).map((v) => v.metrics.LCP);
241
+ const fidValues = vitals.filter((v) => v.metrics.FID).map((v) => v.metrics.FID);
242
+ const clsValues = vitals.filter((v) => v.metrics.CLS).map((v) => v.metrics.CLS);
243
+ const ttfbValues = vitals.filter((v) => v.metrics.TTFB).map((v) => v.metrics.TTFB);
244
+ const avg = (arr) => arr.length ? arr.reduce((a, b) => a + b, 0) / arr.length : 0;
245
+ const sessionViews = /* @__PURE__ */ new Map();
246
+ for (const pv of pageviews) {
247
+ sessionViews.set(pv.sessionId, (sessionViews.get(pv.sessionId) || 0) + 1);
248
+ }
249
+ const bounces = Array.from(sessionViews.values()).filter((v) => v === 1).length;
250
+ const bounceRate = sessionViews.size > 0 ? bounces / sessionViews.size * 100 : 0;
251
+ const byEventName = {};
252
+ for (const e of events) {
253
+ byEventName[e.name] = (byEventName[e.name] || 0) + 1;
254
+ }
255
+ return {
256
+ period: { start: new Date(start), end: new Date(end) },
257
+ pageviews: {
258
+ total: pageviews.length,
259
+ unique: uniqueSessions.size,
260
+ byPath,
261
+ byReferrer,
262
+ byDevice,
263
+ byBrowser,
264
+ byCountry
265
+ },
266
+ vitals: {
267
+ avgFCP: Math.round(avg(fcpValues)),
268
+ avgLCP: Math.round(avg(lcpValues)),
269
+ avgFID: Math.round(avg(fidValues)),
270
+ avgCLS: Number(avg(clsValues).toFixed(3)),
271
+ avgTTFB: Math.round(avg(ttfbValues)),
272
+ p75LCP: Math.round(percentile(lcpValues, 75)),
273
+ p75FID: Math.round(percentile(fidValues, 75)),
274
+ p75CLS: Number(percentile(clsValues, 75).toFixed(3))
275
+ },
276
+ events: {
277
+ total: events.length,
278
+ byName: byEventName
279
+ },
280
+ sessions: {
281
+ total: uniqueSessions.size,
282
+ avgDuration: 0,
283
+ // Would need page durations
284
+ bounceRate: Number(bounceRate.toFixed(1))
285
+ }
286
+ };
287
+ }
288
+ /**
289
+ * Get real-time stats (last 5 minutes)
290
+ */
291
+ getRealtime() {
292
+ const fiveMinutesAgo = Date.now() - 5 * 60 * 1e3;
293
+ const recentPageviews = this.allData.pageviews.filter(
294
+ (pv) => pv.timestamp >= fiveMinutesAgo
295
+ );
296
+ const activeSessions = new Set(recentPageviews.map((pv) => pv.sessionId));
297
+ const pathCounts = {};
298
+ for (const pv of recentPageviews) {
299
+ pathCounts[pv.pathname] = (pathCounts[pv.pathname] || 0) + 1;
300
+ }
301
+ const topPages = Object.entries(pathCounts).map(([path, count]) => ({ path, count })).sort((a, b) => b.count - a.count).slice(0, 10);
302
+ return {
303
+ activeUsers: activeSessions.size,
304
+ pageviews: recentPageviews.length,
305
+ topPages
306
+ };
307
+ }
308
+ /**
309
+ * Export data as JSON
310
+ */
311
+ exportData() {
312
+ return { ...this.allData };
313
+ }
314
+ /**
315
+ * Clear all analytics data
316
+ */
317
+ clearData() {
318
+ this.buffer = { pageviews: [], vitals: [], events: [] };
319
+ this.allData = { pageviews: [], vitals: [], events: [] };
320
+ this.sessions.clear();
321
+ }
322
+ /**
323
+ * Stop the analytics engine
324
+ */
325
+ stop() {
326
+ if (this.flushTimer) {
327
+ clearInterval(this.flushTimer);
328
+ this.flushTimer = null;
329
+ }
330
+ this.flush();
331
+ }
332
+ };
333
+ var analyticsEngine = null;
334
+ function getAnalytics(config) {
335
+ if (!analyticsEngine) {
336
+ analyticsEngine = new AnalyticsEngine(config);
337
+ }
338
+ return analyticsEngine;
339
+ }
340
+ function configureAnalytics(config) {
341
+ analyticsEngine = new AnalyticsEngine(config);
342
+ return analyticsEngine;
343
+ }
344
+ function createAnalyticsMiddleware(options = {}) {
345
+ const engine = getAnalytics(options.config);
346
+ return async (req, next) => {
347
+ engine.trackPageview(req, {
348
+ country: options.getCountry?.(req)
349
+ });
350
+ const response = await next();
351
+ return response;
352
+ };
353
+ }
354
+ function createAnalyticsHandler() {
355
+ const engine = getAnalytics();
356
+ return async (req) => {
357
+ const url = new URL(req.url);
358
+ const action = url.searchParams.get("action") || "summary";
359
+ switch (action) {
360
+ case "summary": {
361
+ const startParam = url.searchParams.get("start");
362
+ const endParam = url.searchParams.get("end");
363
+ const summary = engine.getSummary(
364
+ startParam ? new Date(startParam) : void 0,
365
+ endParam ? new Date(endParam) : void 0
366
+ );
367
+ return new Response(JSON.stringify(summary), {
368
+ headers: { "Content-Type": "application/json" }
369
+ });
370
+ }
371
+ case "realtime": {
372
+ const realtime = engine.getRealtime();
373
+ return new Response(JSON.stringify(realtime), {
374
+ headers: { "Content-Type": "application/json" }
375
+ });
376
+ }
377
+ case "vitals": {
378
+ if (req.method !== "POST") {
379
+ return new Response("Method not allowed", { status: 405 });
380
+ }
381
+ try {
382
+ const body = await req.json();
383
+ engine.trackVitals(body.pathname, body.metrics);
384
+ return new Response(JSON.stringify({ success: true }), {
385
+ headers: { "Content-Type": "application/json" }
386
+ });
387
+ } catch {
388
+ return new Response("Invalid body", { status: 400 });
389
+ }
390
+ }
391
+ case "event": {
392
+ if (req.method !== "POST") {
393
+ return new Response("Method not allowed", { status: 405 });
394
+ }
395
+ try {
396
+ const body = await req.json();
397
+ engine.trackEvent(body.name, body.properties, req);
398
+ return new Response(JSON.stringify({ success: true }), {
399
+ headers: { "Content-Type": "application/json" }
400
+ });
401
+ } catch {
402
+ return new Response("Invalid body", { status: 400 });
403
+ }
404
+ }
405
+ case "export": {
406
+ const data = engine.exportData();
407
+ return new Response(JSON.stringify(data), {
408
+ headers: {
409
+ "Content-Type": "application/json",
410
+ "Content-Disposition": 'attachment; filename="analytics-export.json"'
411
+ }
412
+ });
413
+ }
414
+ default:
415
+ return new Response("Unknown action", { status: 400 });
416
+ }
417
+ };
418
+ }
419
+ var analyticsClientScript = `
420
+ <script>
421
+ (function() {
422
+ // Simple Web Vitals reporter
423
+ const endpoint = '/__float/analytics?action=vitals';
424
+
425
+ function sendVitals(metrics) {
426
+ const body = JSON.stringify({
427
+ pathname: window.location.pathname,
428
+ metrics: metrics
429
+ });
430
+
431
+ if (navigator.sendBeacon) {
432
+ navigator.sendBeacon(endpoint, body);
433
+ } else {
434
+ fetch(endpoint, { method: 'POST', body, keepalive: true });
435
+ }
436
+ }
437
+
438
+ // Observe LCP
439
+ if ('PerformanceObserver' in window) {
440
+ const vitals = {};
441
+
442
+ // LCP
443
+ new PerformanceObserver((list) => {
444
+ const entries = list.getEntries();
445
+ const lastEntry = entries[entries.length - 1];
446
+ vitals.LCP = Math.round(lastEntry.startTime);
447
+ }).observe({ type: 'largest-contentful-paint', buffered: true });
448
+
449
+ // FCP
450
+ new PerformanceObserver((list) => {
451
+ const entries = list.getEntries();
452
+ vitals.FCP = Math.round(entries[0].startTime);
453
+ }).observe({ type: 'paint', buffered: true });
454
+
455
+ // CLS
456
+ let clsValue = 0;
457
+ new PerformanceObserver((list) => {
458
+ for (const entry of list.getEntries()) {
459
+ if (!entry.hadRecentInput) {
460
+ clsValue += entry.value;
461
+ }
462
+ }
463
+ vitals.CLS = clsValue;
464
+ }).observe({ type: 'layout-shift', buffered: true });
465
+
466
+ // Send on page hide
467
+ document.addEventListener('visibilitychange', () => {
468
+ if (document.visibilityState === 'hidden') {
469
+ sendVitals(vitals);
470
+ }
471
+ });
472
+ }
473
+ })();
474
+ </script>
475
+ `;
476
+ var analytics = {
477
+ engine: getAnalytics,
478
+ configure: configureAnalytics,
479
+ createMiddleware: createAnalyticsMiddleware,
480
+ createHandler: createAnalyticsHandler,
481
+ clientScript: analyticsClientScript,
482
+ track: {
483
+ pageview: (req, options) => getAnalytics().trackPageview(req, options),
484
+ event: (name, properties, req) => getAnalytics().trackEvent(name, properties, req),
485
+ vitals: (pathname, metrics) => getAnalytics().trackVitals(pathname, metrics)
486
+ }
487
+ };
488
+ var analytics_default = analytics;
489
+ export {
490
+ AnalyticsEngine,
491
+ analytics,
492
+ analyticsClientScript,
493
+ configureAnalytics,
494
+ createAnalyticsHandler,
495
+ createAnalyticsMiddleware,
496
+ analytics_default as default,
497
+ getAnalytics
498
+ };
499
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/analytics/index.ts"],"sourcesContent":["/**\n * Float.js - Built-in Analytics\n * \n * Zero-config privacy-focused analytics built into the framework.\n * No external dependencies, no cookies, GDPR-compliant by default.\n */\n\n// ============================================================================\n// TYPES\n// ============================================================================\n\nexport interface PageView {\n id: string;\n timestamp: number;\n pathname: string;\n referrer?: string;\n userAgent?: string;\n country?: string;\n device: 'desktop' | 'mobile' | 'tablet' | 'unknown';\n browser: string;\n os: string;\n sessionId: string;\n duration?: number;\n}\n\nexport interface WebVitals {\n id: string;\n timestamp: number;\n pathname: string;\n metrics: {\n FCP?: number; // First Contentful Paint\n LCP?: number; // Largest Contentful Paint\n FID?: number; // First Input Delay\n CLS?: number; // Cumulative Layout Shift\n TTFB?: number; // Time to First Byte\n INP?: number; // Interaction to Next Paint\n };\n}\n\nexport interface CustomEvent {\n id: string;\n timestamp: number;\n name: string;\n pathname: string;\n properties?: Record<string, string | number | boolean>;\n sessionId: string;\n}\n\nexport interface AnalyticsData {\n pageviews: PageView[];\n vitals: WebVitals[];\n events: CustomEvent[];\n}\n\nexport interface AnalyticsConfig {\n /** Enable/disable analytics */\n enabled: boolean;\n /** Ignore paths (regex patterns) */\n ignorePaths: (string | RegExp)[];\n /** Max events in memory before flush */\n maxBufferSize: number;\n /** Flush interval in ms */\n flushInterval: number;\n /** Custom event handler */\n onFlush?: (data: AnalyticsData) => Promise<void>;\n /** Hash IP addresses for privacy */\n hashIPs: boolean;\n /** Track web vitals */\n trackVitals: boolean;\n /** Session timeout in minutes */\n sessionTimeout: number;\n /** GeoIP lookup */\n geoIP: boolean;\n}\n\nexport interface AnalyticsSummary {\n period: { start: Date; end: Date };\n pageviews: {\n total: number;\n unique: number;\n byPath: Record<string, number>;\n byReferrer: Record<string, number>;\n byDevice: Record<string, number>;\n byBrowser: Record<string, number>;\n byCountry: Record<string, number>;\n };\n vitals: {\n avgFCP: number;\n avgLCP: number;\n avgFID: number;\n avgCLS: number;\n avgTTFB: number;\n p75LCP: number;\n p75FID: number;\n p75CLS: number;\n };\n events: {\n total: number;\n byName: Record<string, number>;\n };\n sessions: {\n total: number;\n avgDuration: number;\n bounceRate: number;\n };\n}\n\n// ============================================================================\n// DEFAULT CONFIG\n// ============================================================================\n\nconst DEFAULT_CONFIG: AnalyticsConfig = {\n enabled: true,\n ignorePaths: [/^\\/__float/, /^\\/api\\//, /\\.(ico|png|jpg|css|js)$/],\n maxBufferSize: 100,\n flushInterval: 30000, // 30 seconds\n hashIPs: true,\n trackVitals: true,\n sessionTimeout: 30, // minutes\n geoIP: false\n};\n\n// ============================================================================\n// UTILITIES\n// ============================================================================\n\nfunction generateId(): string {\n return Math.random().toString(36).substring(2, 15) + \n Math.random().toString(36).substring(2, 15);\n}\n\nfunction hashString(str: string): string {\n let hash = 0;\n for (let i = 0; i < str.length; i++) {\n const char = str.charCodeAt(i);\n hash = ((hash << 5) - hash) + char;\n hash = hash & hash;\n }\n return Math.abs(hash).toString(36);\n}\n\nfunction parseUserAgent(ua?: string): { device: PageView['device']; browser: string; os: string } {\n if (!ua) {\n return { device: 'unknown', browser: 'Unknown', os: 'Unknown' };\n }\n\n // Device detection\n let device: PageView['device'] = 'desktop';\n if (/Mobile|Android.*Mobile|iPhone|iPod|BlackBerry|IEMobile|Opera Mini/i.test(ua)) {\n device = 'mobile';\n } else if (/iPad|Android(?!.*Mobile)|Tablet/i.test(ua)) {\n device = 'tablet';\n }\n\n // Browser detection\n let browser = 'Unknown';\n if (/Firefox/i.test(ua)) browser = 'Firefox';\n else if (/Edg/i.test(ua)) browser = 'Edge';\n else if (/Chrome/i.test(ua)) browser = 'Chrome';\n else if (/Safari/i.test(ua)) browser = 'Safari';\n else if (/Opera|OPR/i.test(ua)) browser = 'Opera';\n\n // OS detection\n let os = 'Unknown';\n if (/Windows/i.test(ua)) os = 'Windows';\n else if (/Mac OS X/i.test(ua)) os = 'macOS';\n else if (/Linux/i.test(ua)) os = 'Linux';\n else if (/Android/i.test(ua)) os = 'Android';\n else if (/iOS|iPhone|iPad/i.test(ua)) os = 'iOS';\n\n return { device, browser, os };\n}\n\nfunction percentile(arr: number[], p: number): number {\n if (arr.length === 0) return 0;\n const sorted = [...arr].sort((a, b) => a - b);\n const index = Math.ceil((p / 100) * sorted.length) - 1;\n return sorted[Math.max(0, index)];\n}\n\n// ============================================================================\n// ANALYTICS ENGINE\n// ============================================================================\n\nexport class AnalyticsEngine {\n private config: AnalyticsConfig;\n private buffer: AnalyticsData = {\n pageviews: [],\n vitals: [],\n events: []\n };\n private flushTimer: NodeJS.Timeout | null = null;\n private sessions: Map<string, { lastActivity: number; views: number }> = new Map();\n private allData: AnalyticsData = {\n pageviews: [],\n vitals: [],\n events: []\n };\n\n constructor(config: Partial<AnalyticsConfig> = {}) {\n this.config = { ...DEFAULT_CONFIG, ...config };\n this.startFlushTimer();\n }\n\n private startFlushTimer(): void {\n if (this.flushTimer) clearInterval(this.flushTimer);\n \n this.flushTimer = setInterval(() => {\n this.flush();\n }, this.config.flushInterval);\n }\n\n private shouldIgnore(pathname: string): boolean {\n return this.config.ignorePaths.some(pattern => {\n if (typeof pattern === 'string') {\n return pathname.startsWith(pattern);\n }\n return pattern.test(pathname);\n });\n }\n\n private getOrCreateSession(ip: string): string {\n const sessionKey = this.config.hashIPs ? hashString(ip) : ip;\n const existing = this.sessions.get(sessionKey);\n const now = Date.now();\n const timeout = this.config.sessionTimeout * 60 * 1000;\n\n if (existing && (now - existing.lastActivity) < timeout) {\n existing.lastActivity = now;\n existing.views++;\n return sessionKey;\n }\n\n this.sessions.set(sessionKey, { lastActivity: now, views: 1 });\n return sessionKey;\n }\n\n /**\n * Track a page view\n */\n trackPageview(req: Request, options: { country?: string } = {}): PageView | null {\n if (!this.config.enabled) return null;\n\n const url = new URL(req.url);\n \n if (this.shouldIgnore(url.pathname)) return null;\n\n const ip = req.headers.get('x-forwarded-for')?.split(',')[0] || \n req.headers.get('x-real-ip') || \n 'unknown';\n const userAgent = req.headers.get('user-agent') || undefined;\n const { device, browser, os } = parseUserAgent(userAgent);\n const sessionId = this.getOrCreateSession(ip);\n\n const pageview: PageView = {\n id: generateId(),\n timestamp: Date.now(),\n pathname: url.pathname,\n referrer: req.headers.get('referer') || undefined,\n userAgent: userAgent?.substring(0, 200), // Truncate for storage\n country: options.country,\n device,\n browser,\n os,\n sessionId\n };\n\n this.buffer.pageviews.push(pageview);\n this.checkBufferSize();\n\n return pageview;\n }\n\n /**\n * Track Web Vitals\n */\n trackVitals(pathname: string, metrics: WebVitals['metrics']): WebVitals | null {\n if (!this.config.enabled || !this.config.trackVitals) return null;\n\n const vitals: WebVitals = {\n id: generateId(),\n timestamp: Date.now(),\n pathname,\n metrics\n };\n\n this.buffer.vitals.push(vitals);\n this.checkBufferSize();\n\n return vitals;\n }\n\n /**\n * Track custom event\n */\n trackEvent(\n name: string, \n properties?: Record<string, string | number | boolean>,\n req?: Request\n ): CustomEvent | null {\n if (!this.config.enabled) return null;\n\n let pathname = '/';\n let sessionId = generateId();\n\n if (req) {\n const url = new URL(req.url);\n pathname = url.pathname;\n \n const ip = req.headers.get('x-forwarded-for')?.split(',')[0] || \n req.headers.get('x-real-ip') || \n 'unknown';\n sessionId = this.getOrCreateSession(ip);\n }\n\n const event: CustomEvent = {\n id: generateId(),\n timestamp: Date.now(),\n name,\n pathname,\n properties,\n sessionId\n };\n\n this.buffer.events.push(event);\n this.checkBufferSize();\n\n return event;\n }\n\n private checkBufferSize(): void {\n const totalSize = this.buffer.pageviews.length + \n this.buffer.vitals.length + \n this.buffer.events.length;\n \n if (totalSize >= this.config.maxBufferSize) {\n this.flush();\n }\n }\n\n /**\n * Flush buffer to storage/handler\n */\n async flush(): Promise<void> {\n if (this.buffer.pageviews.length === 0 && \n this.buffer.vitals.length === 0 && \n this.buffer.events.length === 0) {\n return;\n }\n\n const dataToFlush = { ...this.buffer };\n \n // Reset buffer\n this.buffer = { pageviews: [], vitals: [], events: [] };\n\n // Add to all data for summary\n this.allData.pageviews.push(...dataToFlush.pageviews);\n this.allData.vitals.push(...dataToFlush.vitals);\n this.allData.events.push(...dataToFlush.events);\n\n // Limit stored data (keep last 10000 of each)\n if (this.allData.pageviews.length > 10000) {\n this.allData.pageviews = this.allData.pageviews.slice(-10000);\n }\n if (this.allData.vitals.length > 10000) {\n this.allData.vitals = this.allData.vitals.slice(-10000);\n }\n if (this.allData.events.length > 10000) {\n this.allData.events = this.allData.events.slice(-10000);\n }\n\n // Call custom handler if provided\n if (this.config.onFlush) {\n await this.config.onFlush(dataToFlush);\n }\n }\n\n /**\n * Get analytics summary\n */\n getSummary(startDate?: Date, endDate?: Date): AnalyticsSummary {\n const start = startDate?.getTime() || Date.now() - (7 * 24 * 60 * 60 * 1000); // Last 7 days\n const end = endDate?.getTime() || Date.now();\n\n // Filter by date range\n const pageviews = this.allData.pageviews.filter(\n pv => pv.timestamp >= start && pv.timestamp <= end\n );\n const vitals = this.allData.vitals.filter(\n v => v.timestamp >= start && v.timestamp <= end\n );\n const events = this.allData.events.filter(\n e => e.timestamp >= start && e.timestamp <= end\n );\n\n // Calculate pageview stats\n const uniqueSessions = new Set(pageviews.map(pv => pv.sessionId));\n const byPath: Record<string, number> = {};\n const byReferrer: Record<string, number> = {};\n const byDevice: Record<string, number> = {};\n const byBrowser: Record<string, number> = {};\n const byCountry: Record<string, number> = {};\n\n for (const pv of pageviews) {\n byPath[pv.pathname] = (byPath[pv.pathname] || 0) + 1;\n if (pv.referrer) {\n try {\n const ref = new URL(pv.referrer).hostname;\n byReferrer[ref] = (byReferrer[ref] || 0) + 1;\n } catch {\n byReferrer['direct'] = (byReferrer['direct'] || 0) + 1;\n }\n } else {\n byReferrer['direct'] = (byReferrer['direct'] || 0) + 1;\n }\n byDevice[pv.device] = (byDevice[pv.device] || 0) + 1;\n byBrowser[pv.browser] = (byBrowser[pv.browser] || 0) + 1;\n if (pv.country) {\n byCountry[pv.country] = (byCountry[pv.country] || 0) + 1;\n }\n }\n\n // Calculate vitals averages\n const fcpValues = vitals.filter(v => v.metrics.FCP).map(v => v.metrics.FCP!);\n const lcpValues = vitals.filter(v => v.metrics.LCP).map(v => v.metrics.LCP!);\n const fidValues = vitals.filter(v => v.metrics.FID).map(v => v.metrics.FID!);\n const clsValues = vitals.filter(v => v.metrics.CLS).map(v => v.metrics.CLS!);\n const ttfbValues = vitals.filter(v => v.metrics.TTFB).map(v => v.metrics.TTFB!);\n\n const avg = (arr: number[]) => arr.length ? arr.reduce((a, b) => a + b, 0) / arr.length : 0;\n\n // Calculate session stats\n const sessionViews = new Map<string, number>();\n for (const pv of pageviews) {\n sessionViews.set(pv.sessionId, (sessionViews.get(pv.sessionId) || 0) + 1);\n }\n const bounces = Array.from(sessionViews.values()).filter(v => v === 1).length;\n const bounceRate = sessionViews.size > 0 ? (bounces / sessionViews.size) * 100 : 0;\n\n // Calculate event stats\n const byEventName: Record<string, number> = {};\n for (const e of events) {\n byEventName[e.name] = (byEventName[e.name] || 0) + 1;\n }\n\n return {\n period: { start: new Date(start), end: new Date(end) },\n pageviews: {\n total: pageviews.length,\n unique: uniqueSessions.size,\n byPath,\n byReferrer,\n byDevice,\n byBrowser,\n byCountry\n },\n vitals: {\n avgFCP: Math.round(avg(fcpValues)),\n avgLCP: Math.round(avg(lcpValues)),\n avgFID: Math.round(avg(fidValues)),\n avgCLS: Number(avg(clsValues).toFixed(3)),\n avgTTFB: Math.round(avg(ttfbValues)),\n p75LCP: Math.round(percentile(lcpValues, 75)),\n p75FID: Math.round(percentile(fidValues, 75)),\n p75CLS: Number(percentile(clsValues, 75).toFixed(3))\n },\n events: {\n total: events.length,\n byName: byEventName\n },\n sessions: {\n total: uniqueSessions.size,\n avgDuration: 0, // Would need page durations\n bounceRate: Number(bounceRate.toFixed(1))\n }\n };\n }\n\n /**\n * Get real-time stats (last 5 minutes)\n */\n getRealtime(): {\n activeUsers: number;\n pageviews: number;\n topPages: Array<{ path: string; count: number }>;\n } {\n const fiveMinutesAgo = Date.now() - (5 * 60 * 1000);\n \n const recentPageviews = this.allData.pageviews.filter(\n pv => pv.timestamp >= fiveMinutesAgo\n );\n\n const activeSessions = new Set(recentPageviews.map(pv => pv.sessionId));\n \n const pathCounts: Record<string, number> = {};\n for (const pv of recentPageviews) {\n pathCounts[pv.pathname] = (pathCounts[pv.pathname] || 0) + 1;\n }\n\n const topPages = Object.entries(pathCounts)\n .map(([path, count]) => ({ path, count }))\n .sort((a, b) => b.count - a.count)\n .slice(0, 10);\n\n return {\n activeUsers: activeSessions.size,\n pageviews: recentPageviews.length,\n topPages\n };\n }\n\n /**\n * Export data as JSON\n */\n exportData(): AnalyticsData {\n return { ...this.allData };\n }\n\n /**\n * Clear all analytics data\n */\n clearData(): void {\n this.buffer = { pageviews: [], vitals: [], events: [] };\n this.allData = { pageviews: [], vitals: [], events: [] };\n this.sessions.clear();\n }\n\n /**\n * Stop the analytics engine\n */\n stop(): void {\n if (this.flushTimer) {\n clearInterval(this.flushTimer);\n this.flushTimer = null;\n }\n this.flush();\n }\n}\n\n// ============================================================================\n// MIDDLEWARE\n// ============================================================================\n\nexport interface AnalyticsMiddlewareOptions {\n config?: Partial<AnalyticsConfig>;\n getCountry?: (req: Request) => string | undefined;\n}\n\nlet analyticsEngine: AnalyticsEngine | null = null;\n\n/**\n * Get or create analytics engine\n */\nexport function getAnalytics(config?: Partial<AnalyticsConfig>): AnalyticsEngine {\n if (!analyticsEngine) {\n analyticsEngine = new AnalyticsEngine(config);\n }\n return analyticsEngine;\n}\n\n/**\n * Configure analytics\n */\nexport function configureAnalytics(config: Partial<AnalyticsConfig>): AnalyticsEngine {\n analyticsEngine = new AnalyticsEngine(config);\n return analyticsEngine;\n}\n\n/**\n * Create analytics middleware\n */\nexport function createAnalyticsMiddleware(options: AnalyticsMiddlewareOptions = {}) {\n const engine = getAnalytics(options.config);\n\n return async (req: Request, next: () => Promise<Response>): Promise<Response> => {\n // Track pageview\n engine.trackPageview(req, {\n country: options.getCountry?.(req)\n });\n\n // Continue to next handler\n const response = await next();\n\n return response;\n };\n}\n\n/**\n * Create analytics API handler\n */\nexport function createAnalyticsHandler() {\n const engine = getAnalytics();\n\n return async (req: Request): Promise<Response> => {\n const url = new URL(req.url);\n const action = url.searchParams.get('action') || 'summary';\n\n switch (action) {\n case 'summary': {\n const startParam = url.searchParams.get('start');\n const endParam = url.searchParams.get('end');\n const summary = engine.getSummary(\n startParam ? new Date(startParam) : undefined,\n endParam ? new Date(endParam) : undefined\n );\n return new Response(JSON.stringify(summary), {\n headers: { 'Content-Type': 'application/json' }\n });\n }\n\n case 'realtime': {\n const realtime = engine.getRealtime();\n return new Response(JSON.stringify(realtime), {\n headers: { 'Content-Type': 'application/json' }\n });\n }\n\n case 'vitals': {\n if (req.method !== 'POST') {\n return new Response('Method not allowed', { status: 405 });\n }\n try {\n const body = await req.json() as { pathname: string; metrics: WebVitals['metrics'] };\n engine.trackVitals(body.pathname, body.metrics);\n return new Response(JSON.stringify({ success: true }), {\n headers: { 'Content-Type': 'application/json' }\n });\n } catch {\n return new Response('Invalid body', { status: 400 });\n }\n }\n\n case 'event': {\n if (req.method !== 'POST') {\n return new Response('Method not allowed', { status: 405 });\n }\n try {\n const body = await req.json() as { \n name: string; \n properties?: Record<string, string | number | boolean> \n };\n engine.trackEvent(body.name, body.properties, req);\n return new Response(JSON.stringify({ success: true }), {\n headers: { 'Content-Type': 'application/json' }\n });\n } catch {\n return new Response('Invalid body', { status: 400 });\n }\n }\n\n case 'export': {\n const data = engine.exportData();\n return new Response(JSON.stringify(data), {\n headers: { \n 'Content-Type': 'application/json',\n 'Content-Disposition': 'attachment; filename=\"analytics-export.json\"'\n }\n });\n }\n\n default:\n return new Response('Unknown action', { status: 400 });\n }\n };\n}\n\n// ============================================================================\n// CLIENT-SIDE SCRIPT (for Web Vitals)\n// ============================================================================\n\nexport const analyticsClientScript = `\n<script>\n(function() {\n // Simple Web Vitals reporter\n const endpoint = '/__float/analytics?action=vitals';\n \n function sendVitals(metrics) {\n const body = JSON.stringify({\n pathname: window.location.pathname,\n metrics: metrics\n });\n \n if (navigator.sendBeacon) {\n navigator.sendBeacon(endpoint, body);\n } else {\n fetch(endpoint, { method: 'POST', body, keepalive: true });\n }\n }\n\n // Observe LCP\n if ('PerformanceObserver' in window) {\n const vitals = {};\n \n // LCP\n new PerformanceObserver((list) => {\n const entries = list.getEntries();\n const lastEntry = entries[entries.length - 1];\n vitals.LCP = Math.round(lastEntry.startTime);\n }).observe({ type: 'largest-contentful-paint', buffered: true });\n \n // FCP\n new PerformanceObserver((list) => {\n const entries = list.getEntries();\n vitals.FCP = Math.round(entries[0].startTime);\n }).observe({ type: 'paint', buffered: true });\n \n // CLS\n let clsValue = 0;\n new PerformanceObserver((list) => {\n for (const entry of list.getEntries()) {\n if (!entry.hadRecentInput) {\n clsValue += entry.value;\n }\n }\n vitals.CLS = clsValue;\n }).observe({ type: 'layout-shift', buffered: true });\n \n // Send on page hide\n document.addEventListener('visibilitychange', () => {\n if (document.visibilityState === 'hidden') {\n sendVitals(vitals);\n }\n });\n }\n})();\n</script>\n`;\n\n// ============================================================================\n// MAIN EXPORT\n// ============================================================================\n\nexport const analytics = {\n engine: getAnalytics,\n configure: configureAnalytics,\n createMiddleware: createAnalyticsMiddleware,\n createHandler: createAnalyticsHandler,\n clientScript: analyticsClientScript,\n track: {\n pageview: (req: Request, options?: { country?: string }) => \n getAnalytics().trackPageview(req, options),\n event: (name: string, properties?: Record<string, string | number | boolean>, req?: Request) => \n getAnalytics().trackEvent(name, properties, req),\n vitals: (pathname: string, metrics: WebVitals['metrics']) => \n getAnalytics().trackVitals(pathname, metrics)\n }\n};\n\nexport default analytics;\n"],"mappings":"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","names":[]}