Avoid calling undefined umami.track, add safe checks for Performance APIs, and clean up load listeners to prevent .call() crashes in Chrome.
145 lines
4.2 KiB
TypeScript
145 lines
4.2 KiB
TypeScript
// Analytics utilities for Umami with Performance Tracking
|
|
declare global {
|
|
interface Window {
|
|
umami?: {
|
|
track: (event: string, data?: Record<string, unknown>) => void;
|
|
};
|
|
}
|
|
}
|
|
|
|
export interface PerformanceMetric {
|
|
name: string;
|
|
value: number;
|
|
url: string;
|
|
timestamp: number;
|
|
userAgent?: string;
|
|
}
|
|
|
|
export interface WebVitalsMetric {
|
|
name: 'CLS' | 'FID' | 'FCP' | 'LCP' | 'TTFB';
|
|
value: number;
|
|
delta: number;
|
|
id: string;
|
|
url: string;
|
|
}
|
|
|
|
// Track custom events to Umami
|
|
export const trackEvent = (event: string, data?: Record<string, unknown>) => {
|
|
if (typeof window === "undefined") return;
|
|
const trackFn = window.umami?.track;
|
|
if (typeof trackFn !== "function") return;
|
|
|
|
try {
|
|
trackFn(event, {
|
|
...data,
|
|
timestamp: Date.now(),
|
|
url: window.location.pathname,
|
|
});
|
|
} catch (error) {
|
|
// Silently fail - analytics must never break the app
|
|
if (process.env.NODE_ENV === "development") {
|
|
console.warn("Error tracking Umami event:", error);
|
|
}
|
|
}
|
|
};
|
|
|
|
// Track performance metrics
|
|
export const trackPerformance = (metric: PerformanceMetric) => {
|
|
trackEvent('performance', {
|
|
metric: metric.name,
|
|
value: Math.round(metric.value),
|
|
url: metric.url,
|
|
userAgent: metric.userAgent,
|
|
});
|
|
};
|
|
|
|
// Track Web Vitals
|
|
export const trackWebVitals = (metric: WebVitalsMetric) => {
|
|
trackEvent('web-vitals', {
|
|
name: metric.name,
|
|
value: Math.round(metric.value),
|
|
delta: Math.round(metric.delta),
|
|
id: metric.id,
|
|
url: metric.url,
|
|
});
|
|
};
|
|
|
|
// Track page load performance
|
|
export const trackPageLoad = () => {
|
|
if (typeof window === 'undefined' || typeof performance === 'undefined') return;
|
|
|
|
try {
|
|
const navigationEntries = performance.getEntriesByType('navigation');
|
|
const navigation = navigationEntries[0] as PerformanceNavigationTiming | undefined;
|
|
|
|
if (navigation && navigation.loadEventEnd && navigation.fetchStart) {
|
|
trackPerformance({
|
|
name: 'page-load',
|
|
value: navigation.loadEventEnd - navigation.fetchStart,
|
|
url: window.location.pathname,
|
|
timestamp: Date.now(),
|
|
userAgent: navigator.userAgent,
|
|
});
|
|
|
|
// Track individual timing phases
|
|
trackEvent('page-timing', {
|
|
dns: navigation.domainLookupEnd && navigation.domainLookupStart
|
|
? Math.round(navigation.domainLookupEnd - navigation.domainLookupStart)
|
|
: 0,
|
|
tcp: navigation.connectEnd && navigation.connectStart
|
|
? Math.round(navigation.connectEnd - navigation.connectStart)
|
|
: 0,
|
|
request: navigation.responseStart && navigation.requestStart
|
|
? Math.round(navigation.responseStart - navigation.requestStart)
|
|
: 0,
|
|
response: navigation.responseEnd && navigation.responseStart
|
|
? Math.round(navigation.responseEnd - navigation.responseStart)
|
|
: 0,
|
|
dom: navigation.domContentLoadedEventEnd && navigation.responseEnd
|
|
? Math.round(navigation.domContentLoadedEventEnd - navigation.responseEnd)
|
|
: 0,
|
|
load: navigation.loadEventEnd && navigation.domContentLoadedEventEnd
|
|
? Math.round(navigation.loadEventEnd - navigation.domContentLoadedEventEnd)
|
|
: 0,
|
|
url: window.location.pathname,
|
|
});
|
|
}
|
|
} catch (error) {
|
|
// Silently fail - performance tracking is not critical
|
|
if (process.env.NODE_ENV === 'development') {
|
|
console.warn('Error tracking page load:', error);
|
|
}
|
|
}
|
|
};
|
|
|
|
// Track API response times
|
|
export const trackApiCall = (endpoint: string, duration: number, status: number) => {
|
|
if (typeof window === 'undefined') return;
|
|
trackEvent('api-call', {
|
|
endpoint,
|
|
duration: Math.round(duration),
|
|
status,
|
|
url: window.location.pathname,
|
|
});
|
|
};
|
|
|
|
// Track user interactions
|
|
export const trackInteraction = (action: string, element?: string) => {
|
|
if (typeof window === 'undefined') return;
|
|
trackEvent('interaction', {
|
|
action,
|
|
element,
|
|
url: window.location.pathname,
|
|
});
|
|
};
|
|
|
|
// Track errors
|
|
export const trackError = (error: string, context?: string) => {
|
|
if (typeof window === 'undefined') return;
|
|
trackEvent('error', {
|
|
error,
|
|
context,
|
|
url: window.location.pathname,
|
|
});
|
|
};
|