UNPKG

cosmic-analytics

Version:

Lightweight analytics for Cosmic projects

386 lines (381 loc) 11.7 kB
"use client"; // src/client/useCosmicAnalytics.ts import { useEffect, useRef } from "react"; import { usePathname } from "next/navigation"; // src/lib/utils.ts function generateSessionId() { return `${Date.now()}-${Math.random().toString(36).substring(2, 15)}`; } function isBrowser() { return typeof window !== "undefined" && typeof document !== "undefined"; } function isDoNotTrackEnabled() { if (!isBrowser()) return false; const { doNotTrack, navigator: navigator2 } = window; const dnt = doNotTrack || navigator2.doNotTrack || navigator2.msDoNotTrack; return dnt === "1" || dnt === "yes"; } function parseUserAgent(userAgent) { const ua = userAgent.toLowerCase(); let type = "desktop"; if (/tablet|ipad|playbook|silk/i.test(ua)) { type = "tablet"; } else if (/mobile|iphone|ipod|android|blackberry|opera|mini|windows\sce|palm|smartphone|iemobile/i.test(ua)) { type = "mobile"; } let os = "Unknown"; if (/windows nt 10/i.test(ua)) os = "Windows 10"; else if (/windows nt 6.3/i.test(ua)) os = "Windows 8.1"; else if (/windows nt 6.2/i.test(ua)) os = "Windows 8"; else if (/windows nt 6.1/i.test(ua)) os = "Windows 7"; else if (/windows/i.test(ua)) os = "Windows"; else if (/android/i.test(ua)) os = "Android"; else if (/iphone|ipad|ipod/i.test(ua)) os = "iOS"; else if (/mac os x/i.test(ua)) os = "macOS"; else if (/linux/i.test(ua)) os = "Linux"; else if (/chromeos/i.test(ua)) os = "Chrome OS"; let browser = "Unknown"; let browserVersion = ""; if (/edg\//i.test(ua)) { browser = "Edge"; const match = ua.match(/edg\/(\d+(\.\d+)*)/); if (match) browserVersion = match[1]; } else if (/chrome|crios/i.test(ua) && !/edg/i.test(ua)) { browser = "Chrome"; const match = ua.match(/chrome\/(\d+(\.\d+)*)/); if (match) browserVersion = match[1]; } else if (/firefox|fxios/i.test(ua)) { browser = "Firefox"; const match = ua.match(/firefox\/(\d+(\.\d+)*)/); if (match) browserVersion = match[1]; } else if (/safari/i.test(ua) && !/chrome|crios/i.test(ua)) { browser = "Safari"; const match = ua.match(/version\/(\d+(\.\d+)*)/); if (match) browserVersion = match[1]; } else if (/opera|opr/i.test(ua)) { browser = "Opera"; const match = ua.match(/(?:opera|opr)\/(\d+(\.\d+)*)/); if (match) browserVersion = match[1]; } return { type, os, browser, browserVersion }; } function getViewport() { if (!isBrowser()) return ""; return `${window.innerWidth}x${window.innerHeight}`; } function getScreenResolution() { if (!isBrowser()) return ""; return `${window.screen.width}x${window.screen.height}`; } function getReferrerSource(referrer) { if (!referrer || referrer === "") return "direct"; try { const url = new URL(referrer); const hostname = url.hostname.replace("www.", ""); if (hostname.includes("facebook.com") || hostname.includes("fb.com")) return "facebook"; if (hostname.includes("twitter.com") || hostname.includes("t.co")) return "twitter"; if (hostname.includes("linkedin.com")) return "linkedin"; if (hostname.includes("instagram.com")) return "instagram"; if (hostname.includes("youtube.com")) return "youtube"; if (hostname.includes("reddit.com")) return "reddit"; if (hostname.includes("google.")) return "google"; if (hostname.includes("bing.com")) return "bing"; if (hostname.includes("yahoo.")) return "yahoo"; if (hostname.includes("duckduckgo.com")) return "duckduckgo"; if (hostname.includes("baidu.com")) return "baidu"; return hostname; } catch { return "invalid"; } } function safeJsonParse(json, fallback) { try { return JSON.parse(json); } catch { return fallback; } } // src/lib/analytics.ts var SESSION_TIMEOUT = 30 * 60 * 1e3; var SESSION_STORAGE_KEY = "cosmic_analytics_session"; var CosmicAnalytics = class { constructor(config) { this.session = null; this.pageViewStart = 0; this.isEnabled = true; this.queue = []; this.flushTimer = null; this.config = { apiEndpoint: "https://api.cosmic.new/analytics", debug: false, enabled: true, respectDoNotTrack: true, sendInDevelopment: false, ...config }; this.projectId = this.config.projectId || this.getProjectId(); this.apiEndpoint = this.config.apiEndpoint; this.isEnabled = this.shouldEnableAnalytics(); if (this.isEnabled && isBrowser()) { this.initializeSession(); this.setupEventListeners(); this.startFlushTimer(); } } getProjectId() { return ""; } isDevelopmentMode() { if (typeof this.config.isDevelopment === "boolean") { return this.config.isDevelopment; } if (typeof globalThis.process !== "undefined") { return globalThis.process.env?.NODE_ENV === "development"; } if (isBrowser()) { if (window.__NEXT_DATA__?.buildId === "development") { return true; } const hostname = window.location.hostname; return hostname === "localhost" || hostname === "127.0.0.1" || hostname.includes(".local"); } return false; } shouldEnableAnalytics() { if (!this.config.enabled) return false; if (!this.projectId) { if (this.config.debug) { console.warn("Cosmic Analytics: NEXT_PUBLIC_CLIENT_ID not found"); } return false; } if (this.isDevelopmentMode() && !this.config.sendInDevelopment) { if (this.config.debug) { console.log("Cosmic Analytics: Disabled in development mode. Set sendInDevelopment: true to enable."); } return false; } if (this.config.respectDoNotTrack && isDoNotTrackEnabled()) { if (this.config.debug) { console.log("Cosmic Analytics: Respecting Do Not Track preference"); } return false; } return true; } initializeSession() { const storedSession = this.getStoredSession(); if (storedSession && this.isSessionValid(storedSession)) { this.session = storedSession; this.session.lastActivity = Date.now(); } else { this.session = { id: generateSessionId(), startTime: Date.now(), pageViews: 0, lastActivity: Date.now() }; } this.saveSession(); } getStoredSession() { if (!isBrowser()) return null; try { const stored = sessionStorage.getItem(SESSION_STORAGE_KEY); if (!stored) return null; return safeJsonParse(stored, null); } catch { return null; } } isSessionValid(session) { const now = Date.now(); return now - session.lastActivity < SESSION_TIMEOUT; } saveSession() { if (!isBrowser() || !this.session) return; try { sessionStorage.setItem(SESSION_STORAGE_KEY, JSON.stringify(this.session)); } catch (error) { if (this.config.debug) { console.error("Cosmic Analytics: Failed to save session", error); } } } setupEventListeners() { if (!isBrowser()) return; document.addEventListener("visibilitychange", () => { if (document.visibilityState === "hidden") { this.trackPageExit(); this.flush(true); } else if (document.visibilityState === "visible") { this.trackPageView(); } }); window.addEventListener("beforeunload", () => { this.trackPageExit(); this.flush(true); }); } startFlushTimer() { this.flushTimer = setInterval(() => { this.flush(); }, 5e3); } stopFlushTimer() { if (this.flushTimer) { clearInterval(this.flushTimer); this.flushTimer = null; } } addToQueue(event) { this.queue.push(event); if (this.queue.length >= 10) { this.flush(); } } async flush(forceSendNow = false) { if (this.queue.length === 0) return; const events = [...this.queue]; this.queue = []; try { const response = await fetch(`${this.apiEndpoint}/track`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ events }), keepalive: forceSendNow }); if (!response.ok && this.config.debug) { console.error("Cosmic Analytics: Failed to send events", response.status); this.queue.unshift(...events); } } catch (error) { if (this.config.debug) { console.error("Cosmic Analytics: Error sending events", error); } this.queue.unshift(...events); } } trackPageView(overrides) { if (!this.isEnabled || !isBrowser() || !this.session) return; this.pageViewStart = Date.now(); this.session.pageViews++; this.session.lastActivity = Date.now(); this.saveSession(); const event = { event: "pageview", projectId: this.projectId, sessionId: this.session.id, url: window.location.href, title: document.title, referrer: document.referrer || "direct", timestamp: Date.now(), screenResolution: getScreenResolution(), viewport: getViewport(), language: navigator.language, userAgent: navigator.userAgent, ...overrides }; if (this.config.debug) { console.log("Cosmic Analytics: Page view", event); } this.addToQueue(event); } trackPageExit() { if (!this.isEnabled || !isBrowser() || !this.session || !this.pageViewStart) return; const duration = Date.now() - this.pageViewStart; const event = { event: "pageexit", projectId: this.projectId, sessionId: this.session.id, url: window.location.href, duration, timestamp: Date.now() }; if (this.config.debug) { console.log("Cosmic Analytics: Page exit", event); } this.addToQueue(event); } reset() { if (!isBrowser()) return; this.session = null; sessionStorage.removeItem(SESSION_STORAGE_KEY); if (this.isEnabled) { this.initializeSession(); } } disable() { this.isEnabled = false; this.stopFlushTimer(); this.queue = []; } enable() { this.isEnabled = this.shouldEnableAnalytics(); if (this.isEnabled && isBrowser()) { this.initializeSession(); this.startFlushTimer(); } } }; // src/client/useCosmicAnalytics.ts var analyticsInstance = null; function initAnalytics(config) { if (!analyticsInstance) { analyticsInstance = new CosmicAnalytics(config); } return analyticsInstance; } function getAnalytics() { return analyticsInstance; } function useCosmicAnalytics(config) { const pathname = usePathname(); const initializedRef = useRef(false); const prevUrlRef = useRef(null); useEffect(() => { if (!initializedRef.current) { initAnalytics(config); initializedRef.current = true; } const analytics = getAnalytics(); if (!analytics) return; const currentUrl = typeof window !== "undefined" ? window.location.href : pathname; if (prevUrlRef.current !== currentUrl) { analytics.trackPageView(); prevUrlRef.current = currentUrl; } return () => { analytics.trackPageExit(); }; }, [pathname, config]); } // src/client/CosmicAnalyticsProvider.tsx import { Fragment, jsx } from "react/jsx-runtime"; function CosmicAnalyticsProvider({ children, config }) { const projectId = process.env.NEXT_PUBLIC_CLIENT_ID; const isDevelopment = process.env.NODE_ENV === "development"; const mergedConfig = { projectId, isDevelopment, ...config }; useCosmicAnalytics(mergedConfig); return /* @__PURE__ */ jsx(Fragment, { children }); } export { CosmicAnalytics, CosmicAnalyticsProvider, getAnalytics, getReferrerSource, initAnalytics, isBrowser, parseUserAgent, useCosmicAnalytics }; //# sourceMappingURL=index.mjs.map