cosmic-analytics
Version:
Lightweight analytics for Cosmic projects
419 lines (413 loc) • 13.2 kB
JavaScript
"use strict";
"use client";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var src_exports = {};
__export(src_exports, {
CosmicAnalytics: () => CosmicAnalytics,
CosmicAnalyticsProvider: () => CosmicAnalyticsProvider,
getAnalytics: () => getAnalytics,
getReferrerSource: () => getReferrerSource,
initAnalytics: () => initAnalytics,
isBrowser: () => isBrowser,
parseUserAgent: () => parseUserAgent,
useCosmicAnalytics: () => useCosmicAnalytics
});
module.exports = __toCommonJS(src_exports);
// src/client/useCosmicAnalytics.ts
var import_react = require("react");
var import_navigation = require("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 = (0, import_navigation.usePathname)();
const initializedRef = (0, import_react.useRef)(false);
const prevUrlRef = (0, import_react.useRef)(null);
(0, import_react.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
var import_jsx_runtime = require("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__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children });
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
CosmicAnalytics,
CosmicAnalyticsProvider,
getAnalytics,
getReferrerSource,
initAnalytics,
isBrowser,
parseUserAgent,
useCosmicAnalytics
});
//# sourceMappingURL=index.js.map