UNPKG

@click-chutney/analytics

Version:

Complete website analytics solution. Everything you need to understand your visitors - no Google Analytics required.

3 lines (2 loc) 15.5 kB
class e{static isLocalStorageAvailable(){if("undefined"==typeof window||"undefined"==typeof localStorage)return!1;try{const e="__cc_test__";return localStorage.setItem(e,e),localStorage.removeItem(e),!0}catch{return!1}}static get(e){if(!this.isLocalStorageAvailable())return null;try{return localStorage.getItem(e)}catch{return null}}static set(e,t){if(this.isLocalStorageAvailable())try{localStorage.setItem(e,t)}catch{}}static remove(e){if(this.isLocalStorageAvailable())try{localStorage.removeItem(e)}catch{}}}function t(){return`cc_${Date.now()}_${Math.random().toString(36).substring(2,11)}`}function i(){return"undefined"==typeof window?"":window.location.href}function n(){return"undefined"==typeof window?"":window.location.hostname}function s(){return"undefined"==typeof document?"":document.referrer}class o{static getSession(){const t=e.get(this.SESSION_KEY);if(!t)return null;try{const e=JSON.parse(t);return Date.now()-e.lastActivity>this.SESSION_TIMEOUT?(this.clearSession(),null):e}catch{return this.clearSession(),null}}static createSession(){const e={id:t(),startTime:Date.now(),lastActivity:Date.now(),pageViews:0,events:0,isActive:!0};return this.saveSession(e),e}static updateSession(e){const t={...this.getSession()||this.createSession(),...e,lastActivity:Date.now()};return this.saveSession(t),t}static saveSession(t){e.set(this.SESSION_KEY,JSON.stringify(t))}static clearSession(){e.remove(this.SESSION_KEY)}}o.SESSION_KEY="__cc_session__",o.sessionTimeout=18e5,o.currentSession=null,o.SESSION_TIMEOUT=18e5;class r{static getUser(){const t=e.get(this.USER_KEY);if(!t)return null;try{return JSON.parse(t)}catch{return null}}static setUser(t){const i=this.getUser()||{},n=Date.now(),s={...i,...t,firstSeen:i.firstSeen||n,lastSeen:n};e.set(this.USER_KEY,JSON.stringify(s))}static clearUser(){e.remove(this.USER_KEY)}}r.USER_KEY="__cc_user__";class a{constructor(e){this.eventQueue=[],this.isInitialized=!1,this.config={debug:!1,autoTrack:!0,sessionTimeout:18e5,apiUrl:e.apiUrl||"https://qpbibuv2t3.execute-api.ap-south-1.amazonaws.com/v1/tracker",...e},this.config.debug&&console.log(`🚀 ClickChutney Analytics Initialized\n• Tracking ID: ${this.config.trackingId}\n• API URL: ${this.config.apiUrl}\n• Auto-tracking: ${this.config.autoTrack?"enabled":"disabled"}\n• Ready to track events!`),this.log("Initializing ClickChutney tracker",this.config),this.initialize()}initialize(){if(this.isInitialized)return;if("undefined"==typeof window||"undefined"==typeof document)return void this.log("Skipping initialization - not in browser environment");o.getSession()||o.createSession(),this.config.autoTrack&&this.setupAutoTracking(),this.setupPerformanceTracking(),this.flushTimer=setInterval(()=>{this.flush()},3e3);window.addEventListener("beforeunload",()=>{this.flush(!0)}),document.addEventListener("visibilitychange",()=>{"hidden"===document.visibilityState&&this.flush(!0)}),this.config.autoTrack&&this.page(),this.isInitialized=!0,this.log("ClickChutney tracker initialized")}setupAutoTracking(){"undefined"!=typeof document&&(document.addEventListener("visibilitychange",()=>{document.hidden?this.track("page_hidden"):this.track("page_visible")}),document.addEventListener("click",e=>{const t=e.target;"BUTTON"!==t.tagName&&"A"!==t.tagName||this.track("click",{element:t.tagName.toLowerCase(),text:t.textContent?.trim(),href:t.getAttribute("href"),className:t.className})}),document.addEventListener("submit",e=>{const t=e.target;this.track("form_submit",{formId:t.id,formName:t.name,action:t.action})}))}setupPerformanceTracking(){if("undefined"!=typeof document&&"undefined"!=typeof window)if("complete"===document.readyState)setTimeout(()=>this.trackPerformance(),1e3);else{const e=()=>{setTimeout(()=>this.trackPerformance(),1e3),window.removeEventListener("load",e)};window.addEventListener("load",e)}}trackPerformance(){const e=function(){if("undefined"==typeof window||!window.performance)return{};try{const e=performance.getEntriesByType("navigation")[0],t=performance.getEntriesByType("paint").find(e=>"first-contentful-paint"===e.name);let i;try{i=performance.getEntriesByType("largest-contentful-paint")[0]}catch{i=null}const n={};if(e&&e.loadEventEnd&&e.fetchStart){const t=e.loadEventEnd-e.fetchStart;t>0&&t<6e4&&(n.loadTime=Math.round(t))}return t&&t.startTime>0&&(n.fcp=Math.round(t.startTime)),i&&i.startTime>0&&(n.lcp=Math.round(i.startTime)),n}catch(e){return{}}}();Object.keys(e).length>0&&this.track("performance",e)}page(e,t){if("undefined"==typeof window)return void this.log("Skipping page tracking - not in browser environment");const n={type:"pageview",url:e||i(),title:t||("undefined"==typeof document?"":document.title),referrer:s(),path:"undefined"==typeof window?"":window.location.pathname};o.updateSession({pageViews:(o.getSession()?.pageViews||0)+1}),this.enqueueEvent("pageview",n),this.log("Page view tracked",n)}track(e,t){if("undefined"==typeof window)return void this.log("Skipping event tracking - not in browser environment");const i={type:"event",name:e,properties:t};o.updateSession({events:(o.getSession()?.events||0)+1}),this.enqueueEvent(e,i),this.log("Event tracked",i)}identify(e,t){r.setUser({id:e,traits:t}),this.enqueueEvent("identify",{type:"identify",userId:e,traits:t}),this.log("User identified",{userId:e,traits:t})}set(e){const t=r.getUser()||{};r.setUser({...t,traits:{...t.traits,...e}}),this.log("User properties set",e)}enqueueEvent(e,a){const c=o.getSession(),d=r.getUser(),u={trackingId:this.config.trackingId,event:e,domain:n(),timestamp:(new Date).toISOString(),sessionId:c?.id||t(),userId:d?.id,data:a,userAgent:"undefined"==typeof navigator?"":navigator.userAgent,url:i(),referrer:s()};this.eventQueue.push(u),this.log("Event enqueued",u),(this.eventQueue.length>=5||"pageview"===e)&&setTimeout(()=>this.flush(),100)}flush(e=!1){if("undefined"==typeof window||0===this.eventQueue.length)return Promise.resolve();const t=[...this.eventQueue];this.eventQueue=[],this.log("Flushing events",t);const i=async()=>{try{const i=await fetch(this.config.apiUrl,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({events:t,trackingId:this.config.trackingId,domain:n()}),...e&&{keepalive:!0}});if(!i.ok)throw new Error(`HTTP ${i.status}`);this.log("Events sent successfully"),this.config.debug&&console.log("✅ ClickChutney: Successfully sent",t.length,"events to",this.config.apiUrl)}catch(e){this.log("Error sending events",e),this.config.debug&&(console.error("❌ ClickChutney: Failed to send events:",e),console.log("Retrying events:",t)),this.eventQueue.unshift(...t)}};return e?("undefined"!=typeof navigator&&navigator.sendBeacon&&navigator.sendBeacon(this.config.apiUrl,JSON.stringify({events:t,trackingId:this.config.trackingId,domain:n()})),Promise.resolve()):i()}reset(){o.clearSession(),r.clearUser(),this.eventQueue=[],this.log("Tracker reset")}destroy(){this.flushTimer&&clearInterval(this.flushTimer),this.flush(!0),this.log("Tracker destroyed")}forceFlush(){return this.flush()}log(e,t){this.config.debug&&console.log(`[ClickChutney] ${e}`,t||"")}}class c{constructor(e){this.eventQueue=[],this.isInitialized=!1,this.config={debug:!1,autoTrack:!0,trackOutbound:!0,trackScrolls:!0,trackClicks:!0,trackForms:!0,respectDNT:!0,sessionTimeout:18e5,apiUrl:this.detectApiUrl(),...e},this.sessionId=this.generateSessionId(),this.log("ClickChutney Analytics initialized",{trackingId:this.config.trackingId}),this.initialize()}detectApiUrl(){if("undefined"!=typeof window){const e=window.location.hostname;if("localhost"===e||"127.0.0.1"===e||e.endsWith(".local"))return`${window.location.protocol}//${window.location.host}/api/analytics`}return"https://api.clickchutney.com/track"}initialize(){this.isInitialized||"undefined"==typeof window||(this.config.respectDNT&&"1"===navigator.doNotTrack?this.log("Do Not Track enabled, analytics disabled"):(this.setupAutoTracking(),this.setupPeriodicFlush(),this.config.autoTrack&&this.pageView(),this.isInitialized=!0))}setupAutoTracking(){this.config.autoTrack&&(this.trackSPANavigation(),this.config.trackOutbound&&this.trackOutboundLinks(),this.config.trackScrolls&&this.trackScrollDepth(),this.config.trackClicks&&this.trackClicks(),this.config.trackForms&&this.trackFormSubmissions(),this.trackFileDownloads(),this.trackTimeOnPage())}trackSPANavigation(){let e=window.location.pathname;const t=history.pushState,i=history.replaceState;history.pushState=function(...i){t.apply(history,i),window.location.pathname!==e&&(e=window.location.pathname,setTimeout(()=>n.pageView(),100))},history.replaceState=function(...t){i.apply(history,t),window.location.pathname!==e&&(e=window.location.pathname,setTimeout(()=>n.pageView(),100))};const n=this;window.addEventListener("popstate",()=>{window.location.pathname!==e&&(e=window.location.pathname,setTimeout(()=>n.pageView(),100))})}trackOutboundLinks(){document.addEventListener("click",e=>{const t=e.target.closest("a");if(t&&t.href){new URL(t.href,window.location.href).hostname!==window.location.hostname&&this.track("outbound_click",{url:t.href,text:t.textContent?.trim().substring(0,100)})}})}trackScrollDepth(){let e=0,t=[25,50,75,90],i=new Set;const n=this.debounce(()=>{const n=Math.round(window.scrollY/(document.body.scrollHeight-window.innerHeight)*100);n>e&&(e=n,t.forEach(e=>{n>=e&&!i.has(e)&&(i.add(e),this.track("scroll",{depth:e}))}))},500);window.addEventListener("scroll",n)}trackClicks(){document.addEventListener("click",e=>{const t=e.target;("BUTTON"===t.tagName||t.hasAttribute("data-track"))&&this.track("click",{element:t.tagName.toLowerCase(),text:t.textContent?.trim().substring(0,50),id:t.id,className:t.className,page:window.location.pathname})})}trackFormSubmissions(){document.addEventListener("submit",e=>{const t=e.target;this.track("form_submit",{formId:t.id,formName:t.name,action:t.action||window.location.href,page:window.location.pathname})})}trackFileDownloads(){document.addEventListener("click",e=>{const t=e.target.closest("a");if(t&&t.href){if(/\.(pdf|doc|docx|xls|xlsx|ppt|pptx|zip|rar|mp3|mp4|avi|mov|jpg|png|gif)$/i.test(t.href)){const e=t.href.split(".").pop()?.toLowerCase();this.track("file_download",{file:t.href,extension:e,fileName:t.href.split("/").pop()})}}})}trackTimeOnPage(){let e=Date.now(),t=!0;const i=()=>{if(t){const i=Math.round((Date.now()-e)/1e3);this.track("time_on_page",{seconds:i,page:window.location.pathname}),t=!1}},n=()=>{t||(e=Date.now(),t=!0)};["mousedown","mousemove","keypress","scroll","touchstart"].forEach(e=>{document.addEventListener(e,n,!0)}),window.addEventListener("beforeunload",i),document.addEventListener("visibilitychange",()=>{document.hidden?i():n()}),setInterval(()=>{if(t){const t=Math.round((Date.now()-e)/1e3);t>=30&&(this.track("engagement",{seconds:t,page:window.location.pathname}),e=Date.now())}},3e4)}setupPeriodicFlush(){this.flushTimer=setInterval(()=>{this.flush()},5e3),window.addEventListener("beforeunload",()=>{this.flush(!0)}),document.addEventListener("visibilitychange",()=>{document.hidden&&this.flush(!0)})}pageView(e,t){this.track("pageview",{page:e||window.location.pathname,title:t||document.title,referrer:document.referrer})}track(e,t,i){const n={event:e,trackingId:this.config.trackingId,sessionId:this.sessionId,userId:this.userId,timestamp:(new Date).toISOString(),page:window.location.pathname,referrer:document.referrer,userAgent:navigator.userAgent,properties:t||{},value:i};this.eventQueue.push(n),this.log("Event tracked",n),["pageview","conversion","purchase"].includes(e)&&setTimeout(()=>this.flush(),100),this.eventQueue.length>=10&&this.flush()}identify(e,t){this.userId=e,this.track("identify",{userId:e,...t})}conversion(e,t,i){this.track("conversion",{conversionEvent:e,value:t,currency:i||"USD"},t)}ecommerce(e,t){this.track(`ecommerce_${e}`,{...t,currency:t.currency||"USD"},t.value)}flush(e=!1){if(0===this.eventQueue.length)return Promise.resolve();const t=[...this.eventQueue];return this.eventQueue=[],e&&navigator.sendBeacon?(navigator.sendBeacon(this.config.apiUrl,JSON.stringify({events:t})),Promise.resolve()):fetch(this.config.apiUrl,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({events:t})}).then(e=>{e.ok?this.log("Events sent successfully",t.length):(this.log("Failed to send events",e.status),this.eventQueue.unshift(...t))}).catch(e=>{this.log("Error sending events",e),this.eventQueue.unshift(...t)})}generateSessionId(){return"session_"+Date.now()+"_"+Math.random().toString(36).substr(2,10)}debounce(e,t){let i;return function(...n){clearTimeout(i),i=setTimeout(()=>{clearTimeout(i),e(...n)},t)}}log(e,t){this.config.debug&&console.log(`[ClickChutney] ${e}`,t||"")}destroy(){this.flushTimer&&clearInterval(this.flushTimer),this.flush(!0)}}const d=new class{constructor(){this.tracker=null}init(e,t){const i=e||"undefined"!=typeof process&&process.env?.NEXT_PUBLIC_CLICKCHUTNEY_ID||"undefined"!=typeof process&&process.env?.NEXT_PUBLIC_CLICKCHUTNEY_TRACKING_ID||"undefined"!=typeof process&&process.env?.CLICKCHUTNEY_TRACKING_ID;if(!i)throw console.error("🚨 ClickChutney Error: Missing Tracking ID\n\nYour ClickChutney analytics is not properly configured!\n\nSolutions:\n• For React/Next.js: Set NEXT_PUBLIC_CLICKCHUTNEY_ID in your .env.local file\n• For script tags: Use cc('init', 'your-tracking-id')\n• Or pass it directly: ClickChutney.init('your-tracking-id')\n\nGet your tracking ID from: https://clickchutney.com/dashboard"),new Error("ClickChutney: trackingId is required. Provide it as a parameter or set NEXT_PUBLIC_CLICKCHUTNEY_ID environment variable.");const n="undefined"!=typeof process&&"development"===process.env?.NODE_ENV;this.tracker=new a({trackingId:i,debug:n,...t}),this.tracker}page(e,t){this.ensureInitialized(),this.tracker.page(e,t)}track(e,t){this.ensureInitialized(),this.tracker.track(e,t)}identify(e,t){this.ensureInitialized(),this.tracker.identify(e,t)}set(e){this.ensureInitialized(),this.tracker.set(e)}flush(){return this.ensureInitialized(),this.tracker.flush()}reset(){this.ensureInitialized(),this.tracker.reset()}destroy(){this.tracker&&(this.tracker.destroy(),this.tracker=null)}forceFlush(){return this.ensureInitialized(),this.tracker.forceFlush()}simple(e,t){return new c({trackingId:e,...t})}ensureInitialized(){if(!this.tracker)throw new Error("ClickChutney: Must call init() before using other methods")}};function u(){if("undefined"==typeof window||"undefined"==typeof document)return;const e=document.querySelector('meta[name="clickchutney-site-id"]');e&&e.content&&d.init(e.content,{debug:!1});const t=document.querySelector('meta[name="clickchutney-verification"]');t&&t.content&&!e&&d.init(t.content,{debug:!1})}"undefined"!=typeof window&&("loading"===document.readyState?document.addEventListener("DOMContentLoaded",u):u()),"undefined"!=typeof window&&(window.cc=(e,...t)=>{switch(e){case"init":d.init(t[0],t[1]);break;case"page":d.page(t[0],t[1]);break;case"track":d.track(t[0],t[1]);break;case"identify":d.identify(t[0],t[1]);break;case"set":d.set(t[0]);break;case"flush":try{d.forceFlush()}catch(e){console.warn("ClickChutney: Cannot flush - not initialized")}break;default:console.warn(`ClickChutney: Unknown command "${e}"`)}},window.analytics=(e,t)=>new c({trackingId:e,...t}),window.SimpleAnalytics=c,window.ClickChutney=d),"undefined"!=typeof module&&module.exports&&(module.exports=d,module.exports.default=d,module.exports.ClickChutneyTracker=a);export{a as ClickChutneyTracker,c as SimpleAnalyticsTracker,d as default}; //# sourceMappingURL=index.esm.js.map