UNPKG

argos-tracker

Version:

A lightweight frontend tracking SDK for data collection and reporting

3 lines (2 loc) 11.6 kB
var e,t;!function(e){e.PAGE_VIEW="page_view",e.PAGE_DURATION="page_duration",e.CLICK="click",e.CUSTOM="custom",e.ERROR="error",e.PERFORMANCE="performance",e.USER_ACTION="user_action"}(e||(e={})),function(e){e.IMMEDIATE="immediate",e.BATCH="batch",e.BEACON="beacon"}(t||(t={}));const s={reportMethod:t.BATCH,batchSize:10,batchInterval:5e3,debug:!1,timeout:1e4,autoTrackPageView:!0,autoTrackClick:!1,autoTrackError:!0,headers:{"Content-Type":"application/json"}},r={SESSION_ID:"argos_session_id",USER_ID:"argos_user_id",PENDING_EVENTS:"argos_pending_events"};var i;function n(){return"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,function(e){const t=16*Math.random()|0;return("x"===e?t:3&t|8).toString(16)})}function o(){return Date.now()}function a(){const e=navigator.userAgent.toLowerCase();return/mobile|android|iphone|ipod|blackberry|iemobile|opera mini/i.test(e)?i.MOBILE:/tablet|ipad/i.test(e)?i.TABLET:i.DESKTOP}function c(e){try{return JSON.stringify(e)}catch(e){return console.warn("Failed to stringify object:",e),"{}"}}function h(e){if(e.id)return`#${e.id}`;if(e.className){const t=e.className.split(" ").filter(e=>e.trim());if(t.length>0)return`.${t.join(".")}`}const t=e.tagName.toLowerCase(),s=e.parentElement;if(!s)return t;const r=Array.from(s.children).filter(t=>t.tagName===e.tagName),i=r.indexOf(e);return r.length>1?`${h(s)} > ${t}:nth-child(${i+1})`:`${h(s)} > ${t}`}!function(e){e.DESKTOP="desktop",e.MOBILE="mobile",e.TABLET="tablet"}(i||(i={}));class l{constructor(){this.sessionId=this.getOrCreateSessionId(),this.userId=this.getUserId()}getOrCreateSessionId(){let e=this.getItem(r.SESSION_ID);return e||(e=n(),this.setItem(r.SESSION_ID,e)),e}getSessionId(){return this.sessionId}setUserId(e){this.userId=e,this.setItem(r.USER_ID,e)}getUserId(){if(this.userId)return this.userId;const e=this.getItem(r.USER_ID);return e?(this.userId=e,e):void 0}clearUserId(){this.userId=void 0,this.removeItem(r.USER_ID)}savePendingEvents(e){const t=[...this.getPendingEvents(),...e];this.setItem(r.PENDING_EVENTS,c(t))}getPendingEvents(){const e=this.getItem(r.PENDING_EVENTS);return e?function(e,t){try{return JSON.parse(e)}catch(e){return console.warn("Failed to parse JSON:",e),t}}(e,[]):[]}clearPendingEvents(){this.removeItem(r.PENDING_EVENTS)}removePendingEvents(e){const t=this.getPendingEvents().slice(e);t.length>0?this.setItem(r.PENDING_EVENTS,c(t)):this.clearPendingEvents()}renewSessionId(){return this.sessionId=n(),this.setItem(r.SESSION_ID,this.sessionId),this.sessionId}clear(){Object.values(r).forEach(e=>{this.removeItem(e)}),this.sessionId=n(),this.userId=void 0}setItem(e,t){try{"undefined"!=typeof window&&window.localStorage&&localStorage.setItem(e,t)}catch(e){console.warn("Failed to set localStorage item:",e)}}getItem(e){try{if("undefined"!=typeof window&&window.localStorage)return localStorage.getItem(e)}catch(e){console.warn("Failed to get localStorage item:",e)}return null}removeItem(e){try{"undefined"!=typeof window&&window.localStorage&&localStorage.removeItem(e)}catch(e){console.warn("Failed to remove localStorage item:",e)}}}class d{constructor(e){this.config=e}async report(e){if(!e.length)return!0;const{reportMethod:s}=this.config;try{switch(s){case t.BEACON:return await this.sendBeacon(e);case t.IMMEDIATE:case t.BATCH:default:return await this.sendFetch(e)}}catch(e){return this.handleError("Report failed",e),!1}}async sendFetch(e){const{reportUrl:t,headers:s={},timeout:r=1e4}=this.config,i=new AbortController,n=setTimeout(()=>i.abort(),r);try{const r=await fetch(t,{method:"POST",headers:{"Content-Type":"application/json",...s},body:this.formatPayload(e),signal:i.signal});return clearTimeout(n),r.ok?(this.log("Events reported successfully",e.length),!0):(this.handleError("HTTP error",{status:r.status,statusText:r.statusText}),!1)}catch(e){return clearTimeout(n),e instanceof Error&&"AbortError"===e.name?this.handleError("Request timeout",e):this.handleError("Network error",e),!1}}async sendBeacon(e){if(!navigator.sendBeacon)return this.log("sendBeacon not supported, fallback to fetch"),this.sendFetch(e).then(e=>e).catch(()=>!1);try{const t=navigator.sendBeacon(this.config.reportUrl,this.formatPayload(e));return t?this.log("Events sent via beacon successfully",e.length):this.handleError("Beacon send failed",null),t}catch(e){return this.handleError("Beacon error",e),!1}}formatPayload(e){return c({events:e.map(e=>{const t={event_name:e.eventName,user_id:e.userId,session_id:e.sessionId,timestamp:e.timestamp,app_id:this.config.appId,platform:e.deviceType||"desktop",user_agent:e.userAgent,page_url:e.pageUrl};return e.properties&&Object.keys(e.properties).length>0&&(t.custom_properties=JSON.stringify(e.properties)),t})})}handleError(e,t){this.config.debug&&console.error(`[ArgosTracker] ${e}:`,t)}log(e,...t){this.config.debug&&console.log(`[ArgosTracker] ${e}`,...t)}updateConfig(e){this.config={...this.config,...e}}}class u{constructor(e){this.isCollecting=!1,this.pageStartTime=0,this.unloadListeners=[],this.lastPageViewUrl="",this.lastPageViewTime=0,this.eventCallback=e}start(e){this.isCollecting||(this.isCollecting=!0,this.pageStartTime=o(),e.autoTrackPageView&&(this.trackPageView(),this.setupPageViewTracking()),e.autoTrackClick&&this.setupClickTracking(),e.autoTrackError&&this.setupErrorTracking())}stop(){this.isCollecting&&(this.isCollecting=!1,this.cleanup())}cleanup(){this.unloadListeners.forEach(e=>e()),this.unloadListeners=[]}trackPageView(){const t=window.location.href,s=o();if(this.lastPageViewUrl===t&&s-this.lastPageViewTime<100)return;this.lastPageViewUrl=t,this.lastPageViewTime=s;const r={url:t,title:document.title,referrer:document.referrer||void 0};this.eventCallback({eventType:e.PAGE_VIEW,eventName:"page_view",properties:r,timestamp:s})}setupPageViewTracking(){const e=history.pushState,t=history.replaceState,s=()=>{setTimeout(()=>{this.trackPageView(),this.pageStartTime=o()},0)};history.pushState=function(...t){e.apply(history,t),s()},history.replaceState=function(...e){t.apply(history,e),s()},this.unloadListeners.push(()=>{history.pushState=e,history.replaceState=t})}setupClickTracking(){const t=function(e,t){let s=0,r=null;return function(...i){const n=o();n-s>=t?(s=n,e.apply(this,i)):r||(r=window.setTimeout(()=>{s=o(),r=null,e.apply(this,i)},t-(n-s)))}}(t=>{var s;const r=t.target;if(!r)return;const i={tagName:r.tagName.toLowerCase(),elementId:r.id||void 0,className:r.className||void 0,textContent:(null===(s=r.textContent)||void 0===s?void 0:s.trim().substring(0,100))||void 0,clientX:t.clientX,clientY:t.clientY};try{i.selector=h(r)}catch(e){}this.eventCallback({eventType:e.CLICK,eventName:"click",properties:i,timestamp:o()})},100);document.addEventListener("click",t,!0),this.unloadListeners.push(()=>{document.removeEventListener("click",t,!0)})}setupErrorTracking(){const t=t=>{var s;const r={message:t.message,stack:null===(s=t.error)||void 0===s?void 0:s.stack,filename:t.filename,lineno:t.lineno,colno:t.colno,errorType:"javascript"};this.eventCallback({eventType:e.ERROR,eventName:"javascript_error",properties:r,timestamp:o()})};window.addEventListener("error",t),this.unloadListeners.push(()=>{window.removeEventListener("error",t)});const s=t=>{var s,r;const i={message:(null===(s=t.reason)||void 0===s?void 0:s.message)||String(t.reason),stack:null===(r=t.reason)||void 0===r?void 0:r.stack,errorType:"promise"};this.eventCallback({eventType:e.ERROR,eventName:"promise_rejection",properties:i,timestamp:o()})};window.addEventListener("unhandledrejection",s),this.unloadListeners.push(()=>{window.removeEventListener("unhandledrejection",s)});const r=t=>{const s=t.target;if(!s)return;const r={message:`Resource load error: ${s.tagName}`,filename:s.src||s.href,errorType:"resource"};this.eventCallback({eventType:e.ERROR,eventName:"resource_error",properties:r,timestamp:o()})};document.addEventListener("error",r,!0),this.unloadListeners.push(()=>{document.removeEventListener("error",r,!0)})}}class g{constructor(e){this.eventQueue=[],this.batchTimer=null,this.isInitialized=!1,this.config={...s,...e},this.storage=new l,this.reporter=new d(this.config),this.collector=new u(this.handleAutoEvent.bind(this)),this.init()}init(){this.isInitialized||(this.config.userId&&this.storage.setUserId(this.config.userId),this.collector.start({autoTrackPageView:this.config.autoTrackPageView,autoTrackClick:this.config.autoTrackClick,autoTrackError:this.config.autoTrackError}),this.setupBeforeUnload(),this.restorePendingEvents(),this.isInitialized=!0,this.log("Tracker initialized"))}track(t,s){this.trackEvent({eventType:e.CUSTOM,eventName:t,properties:s})}trackPageView(t){const s={url:window.location.href,title:document.title,referrer:document.referrer};this.trackEvent({eventType:e.PAGE_VIEW,eventName:"page_view",properties:{...s,...t}})}trackUserAction(t,s){this.trackEvent({eventType:e.USER_ACTION,eventName:t,properties:s})}setUser(t){this.storage.setUserId(t.userId),this.trackEvent({eventType:e.CUSTOM,eventName:"user_set",properties:t.properties})}clearUser(){this.storage.clearUserId(),this.trackEvent({eventType:e.CUSTOM,eventName:"user_clear"})}async flush(){this.batchTimer&&(clearTimeout(this.batchTimer),this.batchTimer=null),await this.sendEvents()}updateConfig(e){this.config={...this.config,...e},this.reporter.updateConfig(this.config),e.userId&&this.storage.setUserId(e.userId)}destroy(){this.collector.stop(),this.batchTimer&&(clearTimeout(this.batchTimer),this.batchTimer=null),this.flush(),this.isInitialized=!1,this.log("Tracker destroyed")}trackEvent(e){if(!this.isInitialized)return void this.log("Tracker not initialized, event ignored");const t={...e,timestamp:e.timestamp||o(),userId:e.userId||this.storage.getUserId(),sessionId:e.sessionId||this.storage.getSessionId(),pageUrl:e.pageUrl||window.location.href,pageTitle:e.pageTitle||document.title,userAgent:e.userAgent||navigator.userAgent,screenResolution:e.screenResolution||`${screen.width}x${screen.height}`,deviceType:e.deviceType||a()};this.addToQueue(t)}handleAutoEvent(e){this.trackEvent(e)}addToQueue(e){this.eventQueue.push(e),this.log("Event added to queue:",e.eventName),this.config.reportMethod===t.IMMEDIATE?this.sendEvents():this.config.reportMethod===t.BATCH&&(this.eventQueue.length>=(this.config.batchSize||10)?this.sendEvents():this.scheduleBatchSend())}scheduleBatchSend(){this.batchTimer||(this.batchTimer=window.setTimeout(()=>{this.sendEvents(),this.batchTimer=null},this.config.batchInterval||5e3))}async sendEvents(){if(0===this.eventQueue.length)return;const e=[...this.eventQueue];this.eventQueue=[];try{await this.reporter.report(e)||(this.storage.savePendingEvents(e),this.log("Events saved to storage due to report failure"))}catch(t){this.storage.savePendingEvents(e),this.log("Events saved to storage due to error:",t)}}restorePendingEvents(){const e=this.storage.getPendingEvents();e.length>0&&(this.log("Restoring pending events:",e.length),this.eventQueue.push(...e),this.storage.clearPendingEvents(),setTimeout(()=>{this.sendEvents()},1e3))}setupBeforeUnload(){const e=()=>{if(this.eventQueue.length>0){new d({...this.config,reportMethod:t.BEACON}).report([...this.eventQueue]),this.storage.savePendingEvents(this.eventQueue)}};window.addEventListener("beforeunload",e),window.addEventListener("pagehide",e)}log(e,...t){this.config.debug&&console.log(`[ArgosTracker] ${e}`,...t)}getConfig(){return{...this.config}}getUserId(){return this.storage.getUserId()}getSessionId(){return this.storage.getSessionId()}renewSession(){return this.storage.renewSessionId()}}export{g as ArgosTracker,e as EventType,t as ReportMethod,g as default}; //# sourceMappingURL=index.esm.js.map