/** * Analytics Service - Main entry point with DI * Clean constructor-based dependency injection */ import type { AnalyticsAdapter, AnalyticsEvent, AnalyticsConfig, } from "./interfaces"; import { PlausibleAdapter } from "./plausible-adapter"; import { UmamiAdapter, type UmamiConfig } from "./umami-adapter"; export class AnalyticsService { private adapter: AnalyticsAdapter; /** * Create analytics service with dependency injection * @param adapter - Analytics adapter implementation */ constructor(adapter: AnalyticsAdapter) { this.adapter = adapter; } getAdapter(): AnalyticsAdapter { return this.adapter; } async track(event: AnalyticsEvent): Promise { return this.adapter.track(event); } async page(path: string, props?: Record): Promise { if (this.adapter.page) { return this.adapter.page(path, props); } return this.track({ name: "Pageview", props: { path, ...props } }); } async identify(userId: string, traits?: Record): Promise { if (this.adapter.identify) { return this.adapter.identify(userId, traits); } } // Convenience methods async trackEvent(name: string, props?: Record): Promise { return this.track({ name, props }); } async trackOutboundLink(url: string, text: string): Promise { return this.track({ name: "Outbound Link", props: { url, text }, }); } async trackSearch(query: string, path: string): Promise { return this.track({ name: "Search", props: { query, path }, }); } async trackPageLoad( loadTime: number, path: string, userAgent: string, ): Promise { return this.track({ name: "Page Load", props: { loadTime: Math.round(loadTime), path, userAgent }, }); } } // Factory functions export function createPlausibleAnalytics( config: AnalyticsConfig, ): AnalyticsService { return new AnalyticsService(new PlausibleAdapter(config)); } export function createUmamiAnalytics(config: UmamiConfig): AnalyticsService { return new AnalyticsService(new UmamiAdapter(config)); } // Default singleton let defaultAnalytics: AnalyticsService | null = null; import { env } from "@/lib/env"; export function getDefaultAnalytics(): AnalyticsService { if (!defaultAnalytics) { const provider = env.NEXT_PUBLIC_ANALYTICS_PROVIDER; if (provider === "umami") { defaultAnalytics = createUmamiAnalytics({ hostUrl: env.UMAMI_API_ENDPOINT, }); } else if (provider === "plausible") { defaultAnalytics = createPlausibleAnalytics({ domain: env.NEXT_PUBLIC_PLAUSIBLE_DOMAIN, scriptUrl: env.NEXT_PUBLIC_PLAUSIBLE_SCRIPT_URL, }); } else { // No analytics provider configured return { getAdapter: () => ({ track: async () => {}, page: async () => {}, getScriptTag: () => null, }), track: async () => {}, page: async () => {}, identify: async () => {}, trackEvent: async () => {}, trackOutboundLink: async () => {}, trackSearch: async () => {}, trackPageLoad: async () => {}, } as any; } } return defaultAnalytics; } // Convenience function export async function track( name: string, props?: Record, ): Promise { return getDefaultAnalytics().trackEvent(name, props); } // Re-export for advanced usage export type { AnalyticsAdapter, AnalyticsEvent, AnalyticsConfig }; export { PlausibleAdapter };