@twinalyze/web-analytics 1.0.9 → 1.0.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/dist/{cdn.global.js → cdn.global.global.js} +11 -11
- package/dist/cdn.global.global.js.map +1 -0
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2 -2
- package/dist/index.mjs.map +1 -1
- package/package.json +8 -1
- package/dist/cdn.global.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
var
|
|
1
|
+
var I=Object.create;var g=Object.defineProperty;var A=Object.getOwnPropertyDescriptor;var E=Object.getOwnPropertyNames;var T=Object.getPrototypeOf,C=Object.prototype.hasOwnProperty;var z=(c,e)=>{for(var i in e)g(c,i,{get:e[i],enumerable:!0})},m=(c,e,i,t)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of E(e))!C.call(c,s)&&s!==i&&g(c,s,{get:()=>e[s],enumerable:!(t=A(e,s))||t.enumerable});return c};var f=(c,e,i)=>(i=c!=null?I(T(c)):{},m(e||!c||!c.__esModule?g(i,"default",{value:c,enumerable:!0}):i,c)),U=c=>m(g({},"__esModule",{value:!0}),c);var D={};z(D,{TwinalyzeAnalytics:()=>b,default:()=>P});module.exports=U(D);var v=require("socket.io-client"),k=f(require("crypto-js")),S=f(require("html2canvas")),x=f(require("ua-parser-js")),R=x.UAParser;function y(){return typeof crypto<"u"&&crypto.randomUUID?crypto.randomUUID():Math.random().toString(16).slice(2)+Date.now().toString(16)}var _=class{constructor(){this.cfg=null,this.socket=null,this.socketReady=!1,this.deviceId=null,this.sessionId=null,this.sessionReady=!1,this._booting=!1,this._disabled=!1,this.pendingEvents=[],this.scrollFired=!1,this._initialized=!1,this._sentSessionUpdate=!1,this._sentUserUpdate=!1,this.indexId=0,this._indexKey=null,this._lastShotAt=0,this._shotInFlight=!1}init(e){var n;if(typeof window>"u"||this._initialized)return;this._initialized=!0;let i=e.organization||e.orgId,t={enabled:!1,apiUrl:((n=e.screenActivity)==null?void 0:n.apiUrl)??`${e.socketUrl}/api/app/screenActivity/screenActivityDetails_post`,captureOn:["page_view","click","form_start","form_submit","scroll","view_search_results","custom_event"],throttleMs:2e3,jpegQuality:.7},s=typeof e.screenActivity=="object"?{...t,...e.screenActivity}:{...t};if(this.cfg={apiKey:e.apiKey,organization:i,socketUrl:"https://api.twinalyze.com",debug:!0,encrypt:!0,persistSession:e.persistSession!==!1,sessionKey:"twinalyze_session_id",responseSuffix:e.responseSuffix||":res",responseTimeoutMs:e.responseTimeoutMs||15e3,source:e.source||"web",indexId:e.indexId??null,enhancedMeasurement:{pageViews:!0,scrolls:!0,outboundClicks:!0,siteSearch:{params:["q","s","search","query"]},formInteractions:!0,fileDownloads:{extensions:["pdf","zip","apk","doc","docx","xls","xlsx","ppt","pptx"]},...e.enhancedMeasurement||{}},apiBaseUrl:e.apiBaseUrl||e.endpoint||e.socketUrl,screenActivity:s},!this.cfg.apiKey||!this.cfg.organization){console.log("[Twinalyze] \u274C Missing apiKey / organization / socketUrl");return}this.deviceId=this._getOrCreateDeviceId(),this.cfg.persistSession&&(this.sessionId=sessionStorage.getItem(this.cfg.sessionKey)||null),this._connectSocket(),this._setupEnhancedMeasurement()}track(e,i={}){var t;if(!this._disabled){if((t=this.cfg)!=null&&t.debug&&console.log("[Twinalyze][CUSTOM]",e,i),!this.sessionReady){this.pendingEvents.push({eventName:e,properties:{...this._pageContext(),...i},ts:Date.now(),eventType:"custom"});return}this._emitEventAdd(e,{...this._pageContext(),...i},"custom")}}_connectSocket(){if(this.socket)return;let e={transports:["websocket"],reconnection:!0,reconnectionAttempts:1/0,reconnectionDelay:2e3,reconnectionDelayMax:1e4,timeout:2e4,path:"/socket.io/",auth:{"x-api-key":this.cfg.apiKey,"x-organization-id":this.cfg.organization,"x-app-version-name":this.cfg.appVersionName||"not_found","x-analytics-sdk-version":this.cfg.analyticsSdkVersion||this.cfg.version||"1.0.0","x-ad-analytics-sdk-version":this.cfg.adAnalyticsSdkVersion||"not_found","x-app-package-name":this.cfg.appPackageName||location.hostname,"x-platform":"web","x-sdk-version":this.cfg.version||"1.0.0"},extraHeaders:{Origin:this.cfg.socketUrl,"x-api-key":this.cfg.apiKey,"x-organization-id":this.cfg.organization,"x-app-version-name":this.cfg.appVersionName||"not_found","x-analytics-sdk-version":this.cfg.analyticsSdkVersion||this.cfg.version||"1.0.0","x-ad-analytics-sdk-version":this.cfg.adAnalyticsSdkVersion||"not_found","x-app-package-name":this.cfg.appPackageName||location.hostname,"x-platform":"web","x-sdk-version":this.cfg.version||"1.0.0"},methods:["GET","POST"]};this.socket=(0,v.io)(this.cfg.socketUrl,e),this.socket.on("connect",async()=>{this.socketReady=!0,this.cfg.debug&&console.log("[Twinalyze][SOCKET] \u2705 connected CHANGED NEW 1",{id:this.socket.id,transport:this.socket.io.engine.transport.name}),await this._startFlow()}),this.socket.on("disconnect",i=>{this.socketReady=!1,this.sessionReady=!1,this._booting=!1,this._sentSessionUpdate=!1,this._sentUserUpdate=!1,this._resetIndexId(),this.cfg.debug&&console.log("[Twinalyze][SOCKET] \u274C disconnected:",i)}),this.socket.on("connect_error",i=>{this.socketReady=!1,this.cfg.debug&&console.log("[Twinalyze][SOCKET] \u274C connect_error:",(i==null?void 0:i.message)||i)}),this.socket.on("sdk_upgrade_warning",i=>{i&&i.message?(console.log("SDK Upgrade Warning:",i.message),alert(i.message),i.updateUrl&&confirm("A new SDK version is available. Do you want to upgrade now?")&&(window.location.href=i.updateUrl)):console.error("Received invalid data in sdk_upgrade_warning event:",i)})}_initIndexId(){this._indexKey=`twinalyze_event_index_${this.sessionId}`;let e=sessionStorage.getItem(this._indexKey);this.indexId=e?parseInt(e,10):0,Number.isNaN(this.indexId)&&(this.indexId=0)}_nextIndexId(){let e=this.indexId;return this.indexId+=1,this._indexKey&&sessionStorage.setItem(this._indexKey,String(this.indexId)),e}_resetIndexId(){this.indexId=0,this._indexKey&&sessionStorage.removeItem(this._indexKey),this._indexKey=null}_normalizeHttpBase(e){return String(e||"").replace(/^ws:\/\//,"http://").replace(/^wss:\/\//,"https://").replace(/\/$/,"")}async _startFlow(){if(!this._disabled&&this.socketReady&&!this._booting){this._booting=!0;try{let e=await this._request("user|userCheckApp",{apiKey:this.cfg.apiKey,organization:this.cfg.organization});this.cfg.debug&&console.log("[Twinalyze][FLOW] userCheckRes:",e);let i=e&&e.success===!0&&(e.status===0||e.status==="0"),t=!!(e!=null&&e.debugScreenshotCapture);if(this.cfg.screenActivity.enabled=t,!i){this._disabled=!0,this.sessionReady=!1,this.cfg.screenActivity.enabled=!1,this.cfg.debug&&console.log("[Twinalyze][FLOW] \u{1F6D1} STOP (userCheck failed)");return}let s=await this._request("user|userExistsApp",{apiKey:this.cfg.apiKey,deviceId:this.deviceId,organization:this.cfg.organization});if(this.cfg.debug&&console.log("[Twinalyze][FLOW] userExistsRes:",s),!!!(s&&s.success===!0&&s.userRegister===!0)){let o=await this._request("user|web|userCreateApp",{apiKey:this.cfg.apiKey,deviceId:this.deviceId,properties:this._userProperties(),organization:this.cfg.organization});if(this.cfg.debug&&console.log("[Twinalyze][FLOW] userCreateRes:",o),o&&o.success===!1){this._disabled=!0,this.sessionReady=!1,this.cfg.debug&&console.log("[Twinalyze][FLOW] \u{1F6D1} STOP (userCreate failed)");return}}this.sessionId||(this.sessionId=`sess_${y()}`,this.cfg.persistSession&&sessionStorage.setItem(this.cfg.sessionKey,this.sessionId));let r=await this._request("session|web|sessionCreateApp",{apiKey:this.cfg.apiKey,deviceId:this.deviceId,socketId:this.socket.id,uniqueSessionId:this.sessionId,deviceProperties:this._deviceProperties(),organization:this.cfg.organization});if(this.cfg.debug&&console.log("[Twinalyze][FLOW] sessionCreateRes:",r),r&&r.success===!1){this._disabled=!0,this.sessionReady=!1;return}this.sessionReady=!0,this._initIndexId(),this._flushPendingEvents()}finally{this._booting=!1}}}_request(e,i){let t=this.cfg.responseTimeoutMs,s=[],n=this.cfg.responseSuffix;s.push(`${e}${n}`),s.push(`${e}Res`),s.push(`${e}Response`),s.push(e);let r=[...new Set(s)];return new Promise(o=>{if(!this.socketReady)return o(null);let a=!1,d=()=>{r.forEach(p=>this.socket.off(p,l)),clearTimeout(u)},l=p=>{a||(a=!0,d(),o(p))};r.forEach(p=>this.socket.on(p,l));let u=setTimeout(()=>{a||(a=!0,d(),o(null))},t),h=this.cfg.encrypt?this._encrypt(i):i;this.socket.emit(e,h)})}_encrypt(e){return k.default.AES.encrypt(JSON.stringify(e),"3Xn8vQp2Lm9sAa7ZkT1yCw5eR0uH6dJ4").toString()}async _waitForRenderStable(){var i;await new Promise(t=>requestAnimationFrame(()=>requestAnimationFrame(t)));try{(i=document.fonts)!=null&&i.ready&&await document.fonts.ready}catch{}let e=Array.from(document.images||[]).filter(t=>!t.complete);await Promise.allSettled(e.map(t=>new Promise(s=>{t.addEventListener("load",s,{once:!0}),t.addEventListener("error",s,{once:!0})})))}async _captureViewportJpegBlob(){var n,r;await this._waitForRenderStable();let e=(((n=this.cfg.screenActivity)==null?void 0:n.capture)||"viewport")==="viewport",i=Math.min(2,window.devicePixelRatio||1),t=await(0,S.default)(document.documentElement,{useCORS:!0,allowTaint:!1,backgroundColor:"#ffffff",logging:!1,...e?{x:window.scrollX,y:window.scrollY,width:window.innerWidth,height:window.innerHeight}:{x:0,y:0,width:document.documentElement.scrollWidth,height:document.documentElement.scrollHeight},scale:i,onclone:o=>{let a=o.createElement("style");a.textContent=`
|
|
2
2
|
* { animation: none !important; transition: none !important; caret-color: transparent !important; }
|
|
3
|
-
`,o.head.appendChild(a)}}),s=((r=this.cfg.screenActivity)==null?void 0:r.maxWidth)??1280;if(t.width>s){let o=s/t.width,a=document.createElement("canvas");return a.width=Math.round(t.width*o),a.height=Math.round(t.height*o),a.getContext("2d").drawImage(t,0,0,a.width,a.height),await new Promise(d=>{var l;return a.toBlob(d,"image/jpeg",((l=this.cfg.screenActivity)==null?void 0:l.jpegQuality)??.7)})}return await new Promise(o=>{var a;return t.toBlob(o,"image/jpeg",((a=this.cfg.screenActivity)==null?void 0:a.jpegQuality)??.7)})}async _uploadScreenActivity({eventName:e,properties:i}){var n,r;if(!((n=this.cfg.screenActivity)!=null&&n.enabled)||!this.sessionReady)return;let t=Date.now(),s=((r=this.cfg.screenActivity)==null?void 0:r.throttleMs)??2e3;if(!(t-this._lastShotAt<s)&&!this._shotInFlight){this._shotInFlight=!0,this._lastShotAt=t;try{let o=await this._captureViewportJpegBlob();if(!o)return;let a=new FormData;a.append("apiKey",this.cfg.apiKey),a.append("organization",this.cfg.organization),a.append("screenName",document.title||"unknown"),a.append("identifier",JSON.stringify(i||{})),a.append("description",e||"event"),a.append("image",o,`shot_${Date.now()}.jpg`),await fetch(this.cfg.screenActivity.apiUrl,{method:"POST",body:a,keepalive:!0})}catch(o){this.cfg.debug&&console.log("[Twinalyze][SCREENSHOT] error:",(o==null?void 0:o.message)||o)}finally{this._shotInFlight=!1}}}_emitSessionUpdateOnce(){if(!this.sessionReady||this._sentSessionUpdate)return;this._sentSessionUpdate=!0;let e={apiKey:this.cfg.apiKey,deviceId:this.deviceId,deviceProperties:this._deviceProperties(),organization:this.cfg.organization},i=this.cfg.encrypt?this._encrypt(e):e;this.socket.emit("session|sessionUpdateApp",i),this.cfg.debug&&console.log("[Twinalyze] \u2705 sessionUpdate sent")}_emitUserUpdateOnce(){if(!this.sessionReady||this._sentUserUpdate)return;this._sentUserUpdate=!0;let e={apiKey:this.cfg.apiKey,deviceId:this.deviceId,properties:this._userProperties(),organization:this.cfg.organization},i=this.cfg.encrypt?this._encrypt(e):e;this.socket.emit("user|userUpdateApp",i),this.cfg.debug&&console.log("[Twinalyze] \u2705 userUpdate sent")}_emitEventAdd(e,i={},t="auto"){if(!this.sessionReady)return;this._indexKey==null&&this._initIndexId();let s=this._nextIndexId(),n={socketId:this.socket.id,date:new Date().toISOString(),eventType:t,uniqueSessionId:this.sessionId,deviceId:this.deviceId,eventName:e,properties:i,apiKey:this.cfg.apiKey,source:this.cfg.source,indexId:s,organization:this.cfg.organization};console.log("payload session|web|sessionEventAddApp",n);let r=this.cfg.encrypt?this._encrypt(n):n;this.socket.emit("session|web|sessionEventAddApp",r),this._uploadScreenActivity({eventName:e,properties:i}),this.cfg.debug&&console.log("[Twinalyze][EVENT_ADD]",e,i,s)}_flushPendingEvents(){if(!this.sessionReady||!this.pendingEvents.length)return;this.pendingEvents.splice(0,this.pendingEvents.length).forEach(i=>this._emitEventAdd(i.eventName,i.properties||{},i.eventType||"auto"))}_trackAuto(e,i={}){var t;if(!this._disabled){if((t=this.cfg)!=null&&t.debug&&console.log("[Twinalyze][AUTO]",e,i),!this.sessionReady){this.pendingEvents.push({eventName:e,properties:i,ts:Date.now(),eventType:"auto"});return}this._emitEventAdd(e,i,"auto")}}_setupEnhancedMeasurement(){let e=this.cfg.enhancedMeasurement||{};if(e.pageViews){let i=this._pageViewInfo();this._trackAuto("page_view",i);let t=()=>{this.scrollFired=!1;let r=this._pageViewInfo();this._trackAuto("page_view",r),this._fireSiteSearch()},s=history.pushState,n=history.replaceState;history.pushState=(...r)=>{s.apply(history,r),t()},history.replaceState=(...r)=>{n.apply(history,r),t()},window.addEventListener("popstate",t)}if(e.scrolls&&window.addEventListener("scroll",()=>{if(this.scrollFired)return;let i=document.documentElement,t=window.scrollY||i.scrollTop,s=i.scrollHeight-i.clientHeight;if(s<=0)return;let n=Math.round(t/s*100);n>=90&&(this.scrollFired=!0,this._trackAuto("scroll",{percent_scrolled:n,...this._pageContext()}))},{passive:!0}),e.outboundClicks&&document.addEventListener("click",i=>{var a,d,l;let t=this._getClickTarget(i);if(!t)return;let s=t.el,n=(s.tagName||"").toLowerCase(),r=this._getTextFromElement(s),o={click_type:t.kind,element_tag:n,element_text:r||"not_found",element_id:s.id||null,element_name:((a=s.getAttribute)==null?void 0:a.call(s,"name"))||null,element_role:((d=s.getAttribute)==null?void 0:d.call(s,"role"))||null,element_classes:s.className&&typeof s.className=="string"?s.className.slice(0,120):null,element_path:this._cssPath(s),...this._pageContext&&this._pageContext()||{}};if(t.kind==="link"){let u=s.getAttribute("href")||"";if(/^(javascript:|mailto:|tel:)/i.test(u))return;let h;try{h=new URL(s.href)}catch{return}o.link_url=h.href,o.link_domain=h.hostname,o.link_path=h.pathname,o.outbound=h.hostname!==location.hostname}t.kind==="button"&&(o.button_type=((l=s.getAttribute)==null?void 0:l.call(s,"type"))||null,o.disabled=!!s.disabled),this._trackAuto("click",o)},!0),e.siteSearch&&this._fireSiteSearch(),e.formInteractions){let i=new WeakSet;document.addEventListener("focusin",t=>{let s=t.target&&t.target.closest?t.target.closest("form"):null;!s||i.has(s)||(i.add(s),this._trackAuto("form_start",{form_id:s.id||void 0,form_name:s.getAttribute("name")||void 0,form_action:s.action||void 0,...this._pageContext()}))}),document.addEventListener("submit",t=>{let s=t.target;s&&this._trackAuto("form_submit",{form_id:s.id||void 0,form_name:s.getAttribute("name")||void 0,form_action:s.action||void 0,form_destination:location.href,...this._pageContext()})},!0)}if(e.fileDownloads&&e.fileDownloads.extensions){let i=e.fileDownloads.extensions.map(t=>String(t).toLowerCase());document.addEventListener("click",t=>{let s=t.target&&t.target.closest?t.target.closest("a"):null;if(!s||!s.href)return;let n=s.href.split("?")[0].toLowerCase(),r=n.split(".").pop()||"";i.includes(r)&&this._trackAuto("file_download",{link_url:s.href,file_extension:r,file_name:n.split("/").pop()})},!0)}}_fireSiteSearch(){let e=this.cfg.enhancedMeasurement||{};if(!e.siteSearch)return;let i=e.siteSearch.params||["q","s","search","query"],t=new URLSearchParams(location.search),s=i.find(r=>t.get(r)),n=s?t.get(s):null;n&&n.trim()&&this._trackAuto("view_search_results",{search_term:n.trim(),search_param:s,page_path:location.pathname,...this._pageContext()})}_getTextFromElement(e){var n,r;if(!e)return null;let i=(n=e.getAttribute)==null?void 0:n.call(e,"aria-label");if(i&&i.trim())return i.trim();let t=(r=e.getAttribute)==null?void 0:r.call(e,"title");if(t&&t.trim())return t.trim();let s=(e.innerText||e.textContent||"").trim();return s?s.length>80?s.slice(0,80):s:null}_cssPath(e){if(!e||!e.tagName)return null;let i=[],t=e,s=0;for(;t&&t.nodeType===1&&s<5;){let n=t.tagName.toLowerCase();if(t.id){n+=`#${t.id}`,i.unshift(n);break}let r=t.className&&typeof t.className=="string"?t.className.trim().split(/\s+/).slice(0,2).join("."):"";r&&(n+=`.${r}`),i.unshift(n),t=t.parentElement,s++}return i.join(" > ")}_getClickTarget(e){let i=typeof e.composedPath=="function"?e.composedPath():null,t=i&&i.length?i[0]:e.target,s=(d,l)=>d&&d.closest?d.closest(l):null,n=s(t,"a[href]");if(n)return{el:n,kind:"link"};let r=s(t,'button, input[type="button"], input[type="submit"], input[type="reset"], [role="button"]');if(r)return{el:r,kind:"button"};let o=s(t,'[role="menuitem"], [role="menuitemradio"], [role="menuitemcheckbox"], [role="option"]');if(o)return{el:o,kind:"dropdown_item"};let a=s(t,'[role="link"], [tabindex]:not([tabindex="-1"]), [aria-haspopup], [contenteditable="true"]');return a?{el:a,kind:"interactive"}:null}_deviceProperties(){var n,r,o,a,d,l,u,h;let i=new z(navigator.userAgent).getResult(),t=navigator.connection||navigator.mozConnection||navigator.webkitConnection,s=(r=(n=window.matchMedia)==null?void 0:n.call(window,"(prefers-color-scheme: dark)"))!=null&&r.matches?"dark":"light";return{device_type:((o=i.device)==null?void 0:o.type)||(matchMedia("(pointer:coarse)").matches?"mobile":"desktop"),os_name:((a=i.os)==null?void 0:a.name)||"not_found",os_version:((d=i.os)==null?void 0:d.version)||"not_found",browser_name:((l=i.browser)==null?void 0:l.name)||"not_found",browser_version:((u=i.browser)==null?void 0:u.version)||"not_found",sdk:"web",sdk_version:((h=this.cfg)==null?void 0:h.version)||"not_found",device_screen_mode:s,screen_w:String(screen.width),screen_h:String(screen.height),viewport_w:String(window.innerWidth),viewport_h:String(window.innerHeight),density:String(window.devicePixelRatio||1),color_depth:String(screen.colorDepth||"not_found"),device_language:navigator.language||"not_found",timezone:Intl.DateTimeFormat().resolvedOptions().timeZone||"not_found",cores:navigator.hardwareConcurrency!=null?String(navigator.hardwareConcurrency):"not_found",memory_gb:navigator.deviceMemory!=null?String(navigator.deviceMemory):"not_found",touch_points:navigator.maxTouchPoints!=null?String(navigator.maxTouchPoints):"0",is_data_on:navigator.onLine?"true":"false",connection_effective_type:(t==null?void 0:t.effectiveType)||"not_found",connection_downlink:(t==null?void 0:t.downlink)!=null?String(t.downlink):"not_found",connection_rtt:(t==null?void 0:t.rtt)!=null?String(t.rtt):"not_found",connection_save_data:(t==null?void 0:t.saveData)!=null?String(t.saveData):"not_found",user_agent:navigator.userAgent}}_userProperties(){let e=new URL(location.href),i=e.searchParams,t=n=>i.get(n),s="not_found";return{landing_url:e.href,referrer:[{utm_source:t("utm_source")||s},{utm_medium:t("utm_medium")||s},{utm_campaign:t("utm_campaign")||s},{utm_term:t("utm_term")||s},{utm_content:t("utm_content")||s}],gclid:t("gclid")||s,fbclid:t("fbclid")||s,msclkid:t("msclkid")||s,language_pref:navigator.language||s}}_getOrCreateDeviceId(){let e="twinalyze_device_id",i=localStorage.getItem(e);if(i)return i;let t=y();return localStorage.setItem(e,t),t}_pageContext(){let e="twinalyze_last_page_location",i=location.href,t=location.hostname,s=location.pathname,n=document.title,o=sessionStorage.getItem(e)||document.referrer||null,a="direct";if(o)try{a=new URL(o).hostname===t?"internal":"external"}catch{a="external"}return{page_domain:t,page_location:i,page_url:i,page_path:s,page_title:n,previous_page_location:o,previous_page_type:a}}_pageViewInfo(){let e="twinalyze_last_page_location",i="twinalyze_page_counter",t="twinalyze_last_counted_path",s=this._pageContext(),n=s.page_path,r=sessionStorage.getItem(t),o=parseInt(sessionStorage.getItem(i)||"0",10);return Number.isNaN(o)&&(o=0),r!==n?(o+=1,sessionStorage.setItem(i,String(o)),sessionStorage.setItem(t,n)):sessionStorage.setItem(i,String(o)),sessionStorage.setItem(e,s.page_location),{page_counter:o,...s}}},U=new _;0&&(module.exports={TwinalyzeAnalytics});
|
|
3
|
+
`,o.head.appendChild(a)}}),s=((r=this.cfg.screenActivity)==null?void 0:r.maxWidth)??1280;if(t.width>s){let o=s/t.width,a=document.createElement("canvas");return a.width=Math.round(t.width*o),a.height=Math.round(t.height*o),a.getContext("2d").drawImage(t,0,0,a.width,a.height),await new Promise(d=>{var l;return a.toBlob(d,"image/jpeg",((l=this.cfg.screenActivity)==null?void 0:l.jpegQuality)??.7)})}return await new Promise(o=>{var a;return t.toBlob(o,"image/jpeg",((a=this.cfg.screenActivity)==null?void 0:a.jpegQuality)??.7)})}async _uploadScreenActivity({eventName:e,properties:i}){var n,r;if(!((n=this.cfg.screenActivity)!=null&&n.enabled)||!this.sessionReady)return;let t=Date.now(),s=((r=this.cfg.screenActivity)==null?void 0:r.throttleMs)??2e3;if(!(t-this._lastShotAt<s)&&!this._shotInFlight){this._shotInFlight=!0,this._lastShotAt=t;try{let o=await this._captureViewportJpegBlob();if(!o)return;let a=new FormData;a.append("apiKey",this.cfg.apiKey),a.append("organization",this.cfg.organization),a.append("screenName",document.title||"unknown"),a.append("identifier",JSON.stringify(i||{})),a.append("description",e||"event"),a.append("image",o,`shot_${Date.now()}.jpg`),await fetch(this.cfg.screenActivity.apiUrl,{method:"POST",body:a,keepalive:!0})}catch(o){this.cfg.debug&&console.log("[Twinalyze][SCREENSHOT] error:",(o==null?void 0:o.message)||o)}finally{this._shotInFlight=!1}}}_emitSessionUpdateOnce(){if(!this.sessionReady||this._sentSessionUpdate)return;this._sentSessionUpdate=!0;let e={apiKey:this.cfg.apiKey,deviceId:this.deviceId,deviceProperties:this._deviceProperties(),organization:this.cfg.organization},i=this.cfg.encrypt?this._encrypt(e):e;this.socket.emit("session|sessionUpdateApp",i),this.cfg.debug&&console.log("[Twinalyze] \u2705 sessionUpdate sent")}_emitUserUpdateOnce(){if(!this.sessionReady||this._sentUserUpdate)return;this._sentUserUpdate=!0;let e={apiKey:this.cfg.apiKey,deviceId:this.deviceId,properties:this._userProperties(),organization:this.cfg.organization},i=this.cfg.encrypt?this._encrypt(e):e;this.socket.emit("user|userUpdateApp",i),this.cfg.debug&&console.log("[Twinalyze] \u2705 userUpdate sent")}_emitEventAdd(e,i={},t="auto"){if(!this.sessionReady)return;this._indexKey==null&&this._initIndexId();let s=this._nextIndexId(),n={socketId:this.socket.id,date:new Date().toISOString(),eventType:t,uniqueSessionId:this.sessionId,deviceId:this.deviceId,eventName:e,properties:i,apiKey:this.cfg.apiKey,source:this.cfg.source,indexId:s,organization:this.cfg.organization};console.log("payload session|web|sessionEventAddApp",n);let r=this.cfg.encrypt?this._encrypt(n):n;this.socket.emit("session|web|sessionEventAddApp",r),this._uploadScreenActivity({eventName:e,properties:i}),this.cfg.debug&&console.log("[Twinalyze][EVENT_ADD]",e,i,s)}_flushPendingEvents(){if(!this.sessionReady||!this.pendingEvents.length)return;this.pendingEvents.splice(0,this.pendingEvents.length).forEach(i=>this._emitEventAdd(i.eventName,i.properties||{},i.eventType||"auto"))}_trackAuto(e,i={}){var t;if(!this._disabled){if((t=this.cfg)!=null&&t.debug&&console.log("[Twinalyze][AUTO]",e,i),!this.sessionReady){this.pendingEvents.push({eventName:e,properties:i,ts:Date.now(),eventType:"auto"});return}this._emitEventAdd(e,i,"auto")}}_setupEnhancedMeasurement(){let e=this.cfg.enhancedMeasurement||{};if(e.pageViews){let i=this._pageViewInfo();this._trackAuto("page_view",i);let t=()=>{this.scrollFired=!1;let r=this._pageViewInfo();this._trackAuto("page_view",r),this._fireSiteSearch()},s=history.pushState,n=history.replaceState;history.pushState=(...r)=>{s.apply(history,r),t()},history.replaceState=(...r)=>{n.apply(history,r),t()},window.addEventListener("popstate",t)}if(e.scrolls&&window.addEventListener("scroll",()=>{if(this.scrollFired)return;let i=document.documentElement,t=window.scrollY||i.scrollTop,s=i.scrollHeight-i.clientHeight;if(s<=0)return;let n=Math.round(t/s*100);n>=90&&(this.scrollFired=!0,this._trackAuto("scroll",{percent_scrolled:n,...this._pageContext()}))},{passive:!0}),e.outboundClicks&&document.addEventListener("click",i=>{var a,d,l;let t=this._getClickTarget(i);if(!t)return;let s=t.el,n=(s.tagName||"").toLowerCase(),r=this._getTextFromElement(s),o={click_type:t.kind,element_tag:n,element_text:r||"not_found",element_id:s.id||null,element_name:((a=s.getAttribute)==null?void 0:a.call(s,"name"))||null,element_role:((d=s.getAttribute)==null?void 0:d.call(s,"role"))||null,element_classes:s.className&&typeof s.className=="string"?s.className.slice(0,120):null,element_path:this._cssPath(s),...this._pageContext&&this._pageContext()||{}};if(t.kind==="link"){let u=s.getAttribute("href")||"";if(/^(javascript:|mailto:|tel:)/i.test(u))return;let h;try{h=new URL(s.href)}catch{return}o.link_url=h.href,o.link_domain=h.hostname,o.link_path=h.pathname,o.outbound=h.hostname!==location.hostname}t.kind==="button"&&(o.button_type=((l=s.getAttribute)==null?void 0:l.call(s,"type"))||null,o.disabled=!!s.disabled),this._trackAuto("click",o)},!0),e.siteSearch&&this._fireSiteSearch(),e.formInteractions){let i=new WeakSet;document.addEventListener("focusin",t=>{let s=t.target&&t.target.closest?t.target.closest("form"):null;!s||i.has(s)||(i.add(s),this._trackAuto("form_start",{form_id:s.id||void 0,form_name:s.getAttribute("name")||void 0,form_action:s.action||void 0,...this._pageContext()}))}),document.addEventListener("submit",t=>{let s=t.target;s&&this._trackAuto("form_submit",{form_id:s.id||void 0,form_name:s.getAttribute("name")||void 0,form_action:s.action||void 0,form_destination:location.href,...this._pageContext()})},!0)}if(e.fileDownloads&&e.fileDownloads.extensions){let i=e.fileDownloads.extensions.map(t=>String(t).toLowerCase());document.addEventListener("click",t=>{let s=t.target&&t.target.closest?t.target.closest("a"):null;if(!s||!s.href)return;let n=s.href.split("?")[0].toLowerCase(),r=n.split(".").pop()||"";i.includes(r)&&this._trackAuto("file_download",{link_url:s.href,file_extension:r,file_name:n.split("/").pop()})},!0)}}_fireSiteSearch(){let e=this.cfg.enhancedMeasurement||{};if(!e.siteSearch)return;let i=e.siteSearch.params||["q","s","search","query"],t=new URLSearchParams(location.search),s=i.find(r=>t.get(r)),n=s?t.get(s):null;n&&n.trim()&&this._trackAuto("view_search_results",{search_term:n.trim(),search_param:s,page_path:location.pathname,...this._pageContext()})}_getTextFromElement(e){var n,r;if(!e)return null;let i=(n=e.getAttribute)==null?void 0:n.call(e,"aria-label");if(i&&i.trim())return i.trim();let t=(r=e.getAttribute)==null?void 0:r.call(e,"title");if(t&&t.trim())return t.trim();let s=(e.innerText||e.textContent||"").trim();return s?s.length>80?s.slice(0,80):s:null}_cssPath(e){if(!e||!e.tagName)return null;let i=[],t=e,s=0;for(;t&&t.nodeType===1&&s<5;){let n=t.tagName.toLowerCase();if(t.id){n+=`#${t.id}`,i.unshift(n);break}let r=t.className&&typeof t.className=="string"?t.className.trim().split(/\s+/).slice(0,2).join("."):"";r&&(n+=`.${r}`),i.unshift(n),t=t.parentElement,s++}return i.join(" > ")}_getClickTarget(e){let i=typeof e.composedPath=="function"?e.composedPath():null,t=i&&i.length?i[0]:e.target,s=(d,l)=>d&&d.closest?d.closest(l):null,n=s(t,"a[href]");if(n)return{el:n,kind:"link"};let r=s(t,'button, input[type="button"], input[type="submit"], input[type="reset"], [role="button"]');if(r)return{el:r,kind:"button"};let o=s(t,'[role="menuitem"], [role="menuitemradio"], [role="menuitemcheckbox"], [role="option"]');if(o)return{el:o,kind:"dropdown_item"};let a=s(t,'[role="link"], [tabindex]:not([tabindex="-1"]), [aria-haspopup], [contenteditable="true"]');return a?{el:a,kind:"interactive"}:null}_deviceProperties(){var n,r,o,a,d,l,u,h;let i=new R(navigator.userAgent).getResult(),t=navigator.connection||navigator.mozConnection||navigator.webkitConnection,s=(r=(n=window.matchMedia)==null?void 0:n.call(window,"(prefers-color-scheme: dark)"))!=null&&r.matches?"dark":"light";return{device_type:((o=i.device)==null?void 0:o.type)||(matchMedia("(pointer:coarse)").matches?"mobile":"desktop"),os_name:((a=i.os)==null?void 0:a.name)||"not_found",os_version:((d=i.os)==null?void 0:d.version)||"not_found",browser_name:((l=i.browser)==null?void 0:l.name)||"not_found",browser_version:((u=i.browser)==null?void 0:u.version)||"not_found",sdk:"web",sdk_version:((h=this.cfg)==null?void 0:h.version)||"not_found",device_screen_mode:s,screen_w:String(screen.width),screen_h:String(screen.height),viewport_w:String(window.innerWidth),viewport_h:String(window.innerHeight),density:String(window.devicePixelRatio||1),color_depth:String(screen.colorDepth||"not_found"),device_language:navigator.language||"not_found",timezone:Intl.DateTimeFormat().resolvedOptions().timeZone||"not_found",cores:navigator.hardwareConcurrency!=null?String(navigator.hardwareConcurrency):"not_found",memory_gb:navigator.deviceMemory!=null?String(navigator.deviceMemory):"not_found",touch_points:navigator.maxTouchPoints!=null?String(navigator.maxTouchPoints):"0",is_data_on:navigator.onLine?"true":"false",connection_effective_type:(t==null?void 0:t.effectiveType)||"not_found",connection_downlink:(t==null?void 0:t.downlink)!=null?String(t.downlink):"not_found",connection_rtt:(t==null?void 0:t.rtt)!=null?String(t.rtt):"not_found",connection_save_data:(t==null?void 0:t.saveData)!=null?String(t.saveData):"not_found",user_agent:navigator.userAgent}}_userProperties(){let e=new URL(location.href),i=e.searchParams,t=n=>i.get(n),s="not_found";return{landing_url:e.href,referrer:[{utm_source:t("utm_source")||s},{utm_medium:t("utm_medium")||s},{utm_campaign:t("utm_campaign")||s},{utm_term:t("utm_term")||s},{utm_content:t("utm_content")||s}],gclid:t("gclid")||s,fbclid:t("fbclid")||s,msclkid:t("msclkid")||s,language_pref:navigator.language||s}}_getOrCreateDeviceId(){let e="twinalyze_device_id",i=localStorage.getItem(e);if(i)return i;let t=y();return localStorage.setItem(e,t),t}_pageContext(){let e="twinalyze_last_page_location",i=location.href,t=location.hostname,s=location.pathname,n=document.title,o=sessionStorage.getItem(e)||document.referrer||null,a="direct";if(o)try{a=new URL(o).hostname===t?"internal":"external"}catch{a="external"}return{page_domain:t,page_location:i,page_url:i,page_path:s,page_title:n,previous_page_location:o,previous_page_type:a}}_pageViewInfo(){let e="twinalyze_last_page_location",i="twinalyze_page_counter",t="twinalyze_last_counted_path",s=this._pageContext(),n=s.page_path,r=sessionStorage.getItem(t),o=parseInt(sessionStorage.getItem(i)||"0",10);return Number.isNaN(o)&&(o=0),r!==n?(o+=1,sessionStorage.setItem(i,String(o)),sessionStorage.setItem(t,n)):sessionStorage.setItem(i,String(o)),sessionStorage.setItem(e,s.page_location),{page_counter:o,...s}}},w=new _,K=["init","track"],b=Object.fromEntries(K.map(c=>[c,w[c].bind(w)])),P=b;0&&(module.exports={TwinalyzeAnalytics});
|
|
4
4
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.js"],"sourcesContent":["import { io } from \"socket.io-client\";\nimport CryptoJS from \"crypto-js\";\nimport html2canvas from \"html2canvas\";\n// import UAParser from \"ua-parser-js\";\nimport * as UAParserPkg from \"ua-parser-js\";\nconst UAParser = UAParserPkg.UAParser;\n\nfunction uuid() {\n if (typeof crypto !== \"undefined\" && crypto.randomUUID) return crypto.randomUUID();\n return Math.random().toString(16).slice(2) + Date.now().toString(16);\n}\n\nclass TwinalyzeAnalyticsImpl {\n constructor() {\n this.cfg = null;\n\n this.socket = null;\n this.socketReady = false;\n\n this.deviceId = null;\n this.sessionId = null; // uniqueSessionId (client generated)\n\n this.sessionReady = false; // sessionCreate done\n this._booting = false; // handshake in progress\n this._disabled = false; // STOP state\n\n this.pendingEvents = []; // queued until sessionReady\n this.scrollFired = false;\n\n this._initialized = false;\n\n this._sentSessionUpdate = false; // one-time\n this._sentUserUpdate = false; // one-time\n\n this.indexId = 0;\n this._indexKey = null;\n\n this._lastShotAt = 0;\n this._shotInFlight = false;\n }\n\n // -------------------------\n // PUBLIC API\n // -------------------------\n init(cfg) {\n if (typeof window === \"undefined\") return;\n if (this._initialized) return; // strict-mode safe\n this._initialized = true;\n\n const organization = cfg.organization || cfg.orgId;\n\n const DEFAULT_SCREEN_ACTIVITY = {\n enabled: false,\n apiUrl: cfg.screenActivity?.apiUrl ?? `${cfg.socketUrl}/api/app/screenActivity/screenActivityDetails_post`,\n captureOn: [\"page_view\", \"click\", \"form_start\", \"form_submit\", \"scroll\", \"view_search_results\", \"custom_event\"],\n throttleMs: 2000,\n jpegQuality: 0.7,\n };\n\n // support: screenshots: true OR screenActivity: true OR screenActivity: { ... }\n const sa =\n typeof cfg.screenActivity === \"object\"\n ? { ...DEFAULT_SCREEN_ACTIVITY, ...cfg.screenActivity }\n : { ...DEFAULT_SCREEN_ACTIVITY };\n\n this.cfg = {\n apiKey: cfg.apiKey,\n organization,\n socketUrl: \"http://192.168.1.26:3000\",\n\n debug: true,\n\n // encryption like Android (AES key = socket.id)\n encrypt: true, // default true\n\n // session id from client side\n persistSession: cfg.persistSession !== false, // default true (sessionStorage)\n sessionKey: \"twinalyze_session_id\",\n\n // server response event naming\n // we listen to multiple possible response events to be safe:\n // `${event}:res`, `${event}Res`, `${event}Response`, `${event}`\n responseSuffix: cfg.responseSuffix || \":res\",\n responseTimeoutMs: cfg.responseTimeoutMs || 15000,\n\n // eventAdd fields\n source: cfg.source || \"web\",\n indexId: cfg.indexId ?? null,\n\n // auto tracking options\n enhancedMeasurement: {\n pageViews: true,\n scrolls: true,\n outboundClicks: true,\n siteSearch: { params: [\"q\", \"s\", \"search\", \"query\"] },\n formInteractions: true,\n fileDownloads: { extensions: [\"pdf\", \"zip\", \"apk\", \"doc\", \"docx\", \"xls\", \"xlsx\", \"ppt\", \"pptx\"] },\n ...(cfg.enhancedMeasurement || {}),\n },\n apiBaseUrl: cfg.apiBaseUrl || cfg.endpoint || cfg.socketUrl, // http base for REST APIs\n screenActivity: sa,\n // screenActivity: {\n // enabled: cfg.screenActivity?.enabled ?? true,\n // apiUrl: cfg.screenActivity?.apiUrl ?? `${cfg.socketUrl}/api/app/screenActivity/screenActivityDetails_post`,\n // jpegQuality: cfg.screenActivity?.jpegQuality ?? 0.7,\n // throttleMs: cfg.screenActivity?.throttleMs ?? 2000, // don't screenshot every click instantly\n // capture: cfg.screenActivity?.capture ?? \"viewport\", // \"viewport\" | \"fullpage\"\n // maxWidth: cfg.screenActivity?.maxWidth ?? 1280, // reduce size\n // },\n };\n\n if (!this.cfg.apiKey || !this.cfg.organization) {\n console.log(\"[Twinalyze] ❌ Missing apiKey / organization / socketUrl\");\n return;\n }\n\n this.deviceId = this._getOrCreateDeviceId();\n\n if (this.cfg.persistSession) {\n this.sessionId = sessionStorage.getItem(this.cfg.sessionKey) || null;\n }\n\n this._connectSocket();\n this._setupEnhancedMeasurement();\n }\n\n // custom event (web dev only uses this)\n track(eventName, properties = {}) {\n if (this._disabled) return;\n\n if (this.cfg?.debug) console.log(\"[Twinalyze][CUSTOM]\", eventName, properties);\n\n if (!this.sessionReady) {\n this.pendingEvents.push({ eventName, properties: { ...this._pageContext(), ...properties }, ts: Date.now(), eventType: \"custom\" });\n return;\n }\n\n this._emitEventAdd(eventName, { ...this._pageContext(), ...properties }, \"custom\");\n }\n\n // -------------------------\n // SOCKET\n // -------------------------\n _connectSocket() {\n if (this.socket) return;\n\n const options = ({\n transports: [\"websocket\"], // Force WebSocket transport\n reconnection: true,\n reconnectionAttempts: Infinity, // Keep reconnecting indefinitely\n reconnectionDelay: 2000, // 2 seconds delay between reconnection attempts\n reconnectionDelayMax: 10000, // Max reconnection delay (10 seconds)\n timeout: 20000, // Connection timeout (20 seconds)\n path: \"/socket.io/\",\n auth: {\n \"x-api-key\": this.cfg.apiKey,\n \"x-organization-id\": this.cfg.organization,\n \"x-app-version-name\": this.cfg.appVersionName || \"not_found\",\n \"x-analytics-sdk-version\": this.cfg.analyticsSdkVersion || this.cfg.version || \"1.0.0\",\n \"x-ad-analytics-sdk-version\": this.cfg.adAnalyticsSdkVersion || \"not_found\",\n \"x-app-package-name\": this.cfg.appPackageName || location.hostname,\n \"x-platform\": \"web\",\n \"x-sdk-version\": this.cfg.version || \"1.0.0\",\n },\n extraHeaders: {\n \"Origin\": this.cfg.socketUrl,\n \"x-api-key\": this.cfg.apiKey,\n \"x-organization-id\": this.cfg.organization,\n \"x-app-version-name\": this.cfg.appVersionName || \"not_found\",\n \"x-analytics-sdk-version\": this.cfg.analyticsSdkVersion || this.cfg.version || \"1.0.0\",\n \"x-ad-analytics-sdk-version\": this.cfg.adAnalyticsSdkVersion || \"not_found\",\n \"x-app-package-name\": this.cfg.appPackageName || location.hostname,\n \"x-platform\": \"web\",\n \"x-sdk-version\": this.cfg.version || \"1.0.0\",\n },\n methods: [\"GET\", \"POST\"],\n })\n\n this.socket = io(this.cfg.socketUrl, options);\n\n this.socket.on(\"connect\", async () => {\n this.socketReady = true;\n\n if (this.cfg.debug) {\n console.log(\"[Twinalyze][SOCKET] ✅ connected CHANGED NEW 1\", {\n id: this.socket.id,\n transport: this.socket.io.engine.transport.name,\n });\n }\n\n await this._startFlow();\n });\n\n this.socket.on(\"disconnect\", (reason) => {\n this.socketReady = false;\n\n // on reconnect, flow should run again (sessionCreate + one-time updates)\n this.sessionReady = false;\n this._booting = false;\n this._sentSessionUpdate = false;\n this._sentUserUpdate = false;\n this._resetIndexId();\n\n if (this.cfg.debug) console.log(\"[Twinalyze][SOCKET] ❌ disconnected:\", reason);\n });\n\n this.socket.on(\"connect_error\", (err) => {\n this.socketReady = false;\n if (this.cfg.debug) console.log(\"[Twinalyze][SOCKET] ❌ connect_error:\", err?.message || err);\n });\n\n // Listening to sdk_upgrade_warning\n this.socket.on(\"sdk_upgrade_warning\", (data) => {\n if (data && data.message) {\n console.log(\"SDK Upgrade Warning:\", data.message);\n\n // Show the upgrade message\n alert(data.message);\n\n // Optionally, check for updateUrl and redirect\n if (data.updateUrl) {\n const proceedToUpdate = confirm(\"A new SDK version is available. Do you want to upgrade now?\");\n\n if (proceedToUpdate) {\n window.location.href = data.updateUrl; // Redirect to the update page\n }\n }\n } else {\n console.error(\"Received invalid data in sdk_upgrade_warning event:\", data);\n }\n });\n\n // optional incoming debug\n // this.socket.onAny((event, ...args) => {\n // if (this.cfg.debug) console.log(\"[Twinalyze][IN]\", event, args);\n // });\n }\n\n _initIndexId() {\n // call only when sessionId is available\n this._indexKey = `twinalyze_event_index_${this.sessionId}`;\n\n // always start from 0 for new session\n // if you want resume on refresh within same session, load from sessionStorage\n const stored = sessionStorage.getItem(this._indexKey);\n this.indexId = stored ? parseInt(stored, 10) : 0;\n if (Number.isNaN(this.indexId)) this.indexId = 0;\n }\n\n _nextIndexId() {\n const id = this.indexId;\n this.indexId += 1;\n\n if (this._indexKey) {\n sessionStorage.setItem(this._indexKey, String(this.indexId));\n }\n return id; // ✅ return current id then increment\n }\n\n _resetIndexId() {\n this.indexId = 0;\n if (this._indexKey) sessionStorage.removeItem(this._indexKey);\n this._indexKey = null;\n }\n\n _normalizeHttpBase(url) {\n return String(url || \"\")\n .replace(/^ws:\\/\\//, \"http://\")\n .replace(/^wss:\\/\\//, \"https://\")\n .replace(/\\/$/, \"\");\n }\n\n // -------------------------\n // YOUR EXACT FLOW\n // -------------------------\n async _startFlow() {\n if (this._disabled) return;\n if (!this.socketReady) return;\n if (this._booting) return;\n\n this._booting = true;\n\n try {\n // 1) user|userCheckApp { apiKey, organization }\n const checkRes = await this._request(\"user|userCheckApp\", {\n apiKey: this.cfg.apiKey,\n organization: this.cfg.organization,\n });\n\n if (this.cfg.debug) console.log(\"[Twinalyze][FLOW] userCheckRes:\", checkRes);\n\n // STOP condition:\n // \"if success true and status 0 then next else STOP\"\n const ok =\n checkRes &&\n checkRes.success === true &&\n (checkRes.status === 0 || checkRes.status === \"0\");\n\n const serverScreenshot = !!checkRes?.debugScreenshotCapture;\n this.cfg.screenActivity.enabled = serverScreenshot;\n\n if (!ok) {\n this._disabled = true;\n this.sessionReady = false;\n this.cfg.screenActivity.enabled = false;\n if (this.cfg.debug) console.log(\"[Twinalyze][FLOW] 🛑 STOP (userCheck failed)\");\n return;\n }\n\n // 2) user|userExistsApp { apiKey, deviceId, organization }\n const existsRes = await this._request(\"user|userExistsApp\", {\n apiKey: this.cfg.apiKey,\n deviceId: this.deviceId,\n organization: this.cfg.organization,\n });\n\n if (this.cfg.debug) console.log(\"[Twinalyze][FLOW] userExistsRes:\", existsRes);\n\n // your diagram says: if success===true => user exists\n const userExists = !!(existsRes && (existsRes.success === true) && (existsRes.userRegister === true));\n\n // 3) if user not exists => user|web|userCreateApp { apiKey, deviceId, properties, organization }\n if (!userExists) {\n const createRes = await this._request(\"user|web|userCreateApp\", {\n apiKey: this.cfg.apiKey,\n deviceId: this.deviceId,\n properties: this._userProperties(),\n organization: this.cfg.organization,\n });\n\n if (this.cfg.debug) console.log(\"[Twinalyze][FLOW] userCreateRes:\", createRes);\n\n // if backend returns explicit failure, stop\n if (createRes && createRes.success === false) {\n this._disabled = true;\n this.sessionReady = false;\n if (this.cfg.debug) console.log(\"[Twinalyze][FLOW] 🛑 STOP (userCreate failed)\");\n return;\n }\n }\n\n // 4) create session id from client (your requirement)\n if (!this.sessionId) {\n this.sessionId = `sess_${uuid()}`;\n if (this.cfg.persistSession) {\n sessionStorage.setItem(this.cfg.sessionKey, this.sessionId);\n }\n }\n\n // 5) session|web|sessionCreateApp\n // { apiKey, deviceId, socketId, uniqueSessionId, deviceProperties, organization }\n const sessionRes = await this._request(\"session|web|sessionCreateApp\", {\n apiKey: this.cfg.apiKey,\n deviceId: this.deviceId,\n socketId: this.socket.id,\n uniqueSessionId: this.sessionId,\n deviceProperties: this._deviceProperties(),\n organization: this.cfg.organization,\n });\n\n if (this.cfg.debug) console.log(\"[Twinalyze][FLOW] sessionCreateRes:\", sessionRes);\n\n if (sessionRes && sessionRes.success === false) {\n this._disabled = true;\n this.sessionReady = false;\n return;\n }\n\n // Mark ready even if backend doesn't return anything useful\n this.sessionReady = true;\n this._initIndexId();\n\n // // 6) one-time updates (exact payloads)\n // this._emitSessionUpdateOnce();\n // this._emitUserUpdateOnce();\n\n // 7) flush queued auto/custom events\n this._flushPendingEvents();\n } finally {\n this._booting = false;\n }\n }\n\n // -------------------------\n // REQUEST/RESPONSE (NO ACK)\n // waits for server response event using \"on/once\"\n // -------------------------\n _request(eventName, payloadObj) {\n const timeoutMs = this.cfg.responseTimeoutMs;\n\n const candidates = [];\n const suffix = this.cfg.responseSuffix;\n\n // Try multiple common response event names (so you don’t have to guess)\n candidates.push(`${eventName}${suffix}`); // user|userCheckApp:res\n candidates.push(`${eventName}Res`); // user|userCheckAppRes\n candidates.push(`${eventName}Response`); // user|userCheckAppResponse\n candidates.push(eventName); // same event name as response (some backends do this)\n\n // unique\n const resEvents = [...new Set(candidates)];\n\n return new Promise((resolve) => {\n if (!this.socketReady) return resolve(null);\n\n let done = false;\n\n const cleanup = () => {\n resEvents.forEach((ev) => this.socket.off(ev, onRes));\n clearTimeout(timer);\n };\n\n const onRes = (res) => {\n if (done) return;\n done = true;\n cleanup();\n resolve(res);\n };\n\n // IMPORTANT: register listeners before emit (so we don't miss fast responses)\n resEvents.forEach((ev) => this.socket.on(ev, onRes));\n\n const timer = setTimeout(() => {\n if (done) return;\n done = true;\n cleanup();\n resolve(null);\n }, timeoutMs);\n\n const sendData = this.cfg.encrypt ? this._encrypt(payloadObj) : payloadObj;\n this.socket.emit(eventName, sendData);\n });\n }\n\n _encrypt(obj) {\n const CRYPTOJS_SECRET_KEY = \"3Xn8vQp2Lm9sAa7ZkT1yCw5eR0uH6dJ4\"\n // AES key = socket.id (matches your Android/backend style)\n return CryptoJS.AES.encrypt(JSON.stringify(obj), CRYPTOJS_SECRET_KEY).toString();\n // return CryptoJS.AES.encrypt(JSON.stringify(obj), this.socket.id).toString();\n }\n\n async _waitForRenderStable() {\n // 2 frames\n await new Promise(r => requestAnimationFrame(() => requestAnimationFrame(r)));\n\n // fonts\n try { if (document.fonts?.ready) await document.fonts.ready; } catch { }\n\n // images\n const imgs = Array.from(document.images || []).filter(img => !img.complete);\n await Promise.allSettled(\n imgs.map(img => new Promise(res => {\n img.addEventListener(\"load\", res, { once: true });\n img.addEventListener(\"error\", res, { once: true });\n }))\n );\n }\n\n async _captureViewportJpegBlob() {\n await this._waitForRenderStable();\n\n const isViewport = (this.cfg.screenActivity?.capture || \"viewport\") === \"viewport\";\n const scale = Math.min(2, window.devicePixelRatio || 1);\n\n const canvas = await html2canvas(document.documentElement, {\n useCORS: true,\n allowTaint: false,\n backgroundColor: \"#ffffff\",\n logging: false,\n\n ...(isViewport\n ? { x: window.scrollX, y: window.scrollY, width: window.innerWidth, height: window.innerHeight }\n : { x: 0, y: 0, width: document.documentElement.scrollWidth, height: document.documentElement.scrollHeight }\n ),\n\n scale,\n onclone: (doc) => {\n const style = doc.createElement(\"style\");\n style.textContent = `\n * { animation: none !important; transition: none !important; caret-color: transparent !important; }\n `;\n doc.head.appendChild(style);\n },\n });\n\n // downscale big screenshots (optional but helpful)\n const maxW = this.cfg.screenActivity?.maxWidth ?? 1280;\n if (canvas.width > maxW) {\n const ratio = maxW / canvas.width;\n const c2 = document.createElement(\"canvas\");\n c2.width = Math.round(canvas.width * ratio);\n c2.height = Math.round(canvas.height * ratio);\n c2.getContext(\"2d\").drawImage(canvas, 0, 0, c2.width, c2.height);\n return await new Promise((resolve) =>\n c2.toBlob(resolve, \"image/jpeg\", this.cfg.screenActivity?.jpegQuality ?? 0.7)\n );\n }\n\n return await new Promise((resolve) =>\n canvas.toBlob(resolve, \"image/jpeg\", this.cfg.screenActivity?.jpegQuality ?? 0.7)\n );\n }\n\n async _uploadScreenActivity({ eventName, properties }) {\n if (!this.cfg.screenActivity?.enabled) return;\n if (!this.sessionReady) return;\n\n const now = Date.now();\n const throttleMs = this.cfg.screenActivity?.throttleMs ?? 2000;\n if (now - this._lastShotAt < throttleMs) return;\n if (this._shotInFlight) return;\n\n this._shotInFlight = true;\n this._lastShotAt = now;\n\n try {\n const blob = await this._captureViewportJpegBlob();\n if (!blob) return;\n\n const fd = new FormData();\n fd.append(\"apiKey\", this.cfg.apiKey);\n fd.append(\"organization\", this.cfg.organization);\n fd.append(\"screenName\", document.title || \"unknown\");\n fd.append(\"identifier\", JSON.stringify(properties || {})); // ✅ you asked: identifier = properties object\n fd.append(\"description\", eventName || \"event\");\n fd.append(\"image\", blob, `shot_${Date.now()}.jpg`);\n\n await fetch(this.cfg.screenActivity.apiUrl, {\n method: \"POST\",\n body: fd,\n keepalive: true,\n });\n } catch (e) {\n if (this.cfg.debug) console.log(\"[Twinalyze][SCREENSHOT] error:\", e?.message || e);\n } finally {\n this._shotInFlight = false;\n }\n }\n\n // -------------------------\n // EMITS (exact payloads you gave)\n // -------------------------\n _emitSessionUpdateOnce() {\n if (!this.sessionReady) return;\n if (this._sentSessionUpdate) return;\n this._sentSessionUpdate = true;\n\n const payload = {\n apiKey: this.cfg.apiKey,\n deviceId: this.deviceId,\n deviceProperties: this._deviceProperties(),\n organization: this.cfg.organization,\n };\n\n const sendData = this.cfg.encrypt ? this._encrypt(payload) : payload;\n this.socket.emit(\"session|sessionUpdateApp\", sendData);\n\n if (this.cfg.debug) console.log(\"[Twinalyze] ✅ sessionUpdate sent\");\n }\n\n _emitUserUpdateOnce() {\n if (!this.sessionReady) return;\n if (this._sentUserUpdate) return;\n this._sentUserUpdate = true;\n\n const payload = {\n apiKey: this.cfg.apiKey,\n deviceId: this.deviceId,\n properties: this._userProperties(),\n organization: this.cfg.organization,\n };\n\n const sendData = this.cfg.encrypt ? this._encrypt(payload) : payload;\n this.socket.emit(\"user|userUpdateApp\", sendData);\n\n if (this.cfg.debug) console.log(\"[Twinalyze] ✅ userUpdate sent\");\n }\n\n _emitEventAdd(eventName, properties = {}, eventType = \"auto\") {\n if (!this.sessionReady) return;\n\n // if session just got ready and index not set\n if (this._indexKey == null) this._initIndexId();\n const indexId = this._nextIndexId();\n\n const payload = {\n socketId: this.socket.id,\n date: new Date().toISOString(),\n eventType: eventType,\n uniqueSessionId: this.sessionId,\n deviceId: this.deviceId,\n eventName,\n properties: properties,\n apiKey: this.cfg.apiKey,\n source: this.cfg.source,\n // ✅ auto index\n indexId,\n organization: this.cfg.organization,\n };\n\n console.log(\"payload session|web|sessionEventAddApp\", payload);\n\n const sendData = this.cfg.encrypt ? this._encrypt(payload) : payload;\n this.socket.emit(\"session|web|sessionEventAddApp\", sendData);\n\n // this._maybeUploadScreenActivity(eventName, properties, indexId).catch(() => { });\n this._uploadScreenActivity({ eventName, properties });\n\n if (this.cfg.debug) console.log(\"[Twinalyze][EVENT_ADD]\", eventName, properties, indexId);\n }\n\n _flushPendingEvents() {\n if (!this.sessionReady) return;\n if (!this.pendingEvents.length) return;\n\n const items = this.pendingEvents.splice(0, this.pendingEvents.length);\n items.forEach((e) => this._emitEventAdd(e.eventName, e.properties || {}, e.eventType || \"auto\"));\n }\n\n // -------------------------\n // AUTO EVENTS (GA-style)\n // -------------------------\n _trackAuto(name, props = {}) {\n if (this._disabled) return;\n if (this.cfg?.debug) console.log(\"[Twinalyze][AUTO]\", name, props);\n\n if (!this.sessionReady) {\n this.pendingEvents.push({ eventName: name, properties: props, ts: Date.now(), eventType: \"auto\" });\n return;\n }\n\n this._emitEventAdd(name, props, \"auto\");\n }\n\n _setupEnhancedMeasurement() {\n const em = this.cfg.enhancedMeasurement || {};\n\n // page_view (normal + SPA)\n if (em.pageViews) {\n const pv = this._pageViewInfo();\n this._trackAuto(\"page_view\", pv);\n\n const fire = () => {\n this.scrollFired = false;\n const pv2 = this._pageViewInfo();\n this._trackAuto(\"page_view\", pv2);\n this._fireSiteSearch();\n };\n\n const _push = history.pushState;\n const _replace = history.replaceState;\n\n history.pushState = (...args) => { _push.apply(history, args); fire(); };\n history.replaceState = (...args) => { _replace.apply(history, args); fire(); };\n window.addEventListener(\"popstate\", fire);\n }\n\n // scroll (90%)\n if (em.scrolls) {\n window.addEventListener(\"scroll\", () => {\n if (this.scrollFired) return;\n\n const doc = document.documentElement;\n const scrollTop = window.scrollY || doc.scrollTop;\n const scrollHeight = doc.scrollHeight - doc.clientHeight;\n if (scrollHeight <= 0) return;\n\n const percent = Math.round((scrollTop / scrollHeight) * 100);\n if (percent >= 90) {\n this.scrollFired = true;\n this._trackAuto(\"scroll\", { percent_scrolled: percent, ...this._pageContext() });\n }\n }, { passive: true });\n }\n\n // // outbound clicks\n // if (em.outboundClicks) {\n // document.addEventListener(\"click\", (e) => {\n // const a = e.target && e.target.closest ? e.target.closest(\"a\") : null;\n // if (!a || !a.href) return;\n // if (/^(javascript:|mailto:|tel:)/i.test(a.href)) return;\n\n // let url;\n // try { url = new URL(a.href); } catch { return; }\n // if (url.hostname !== location.hostname) {\n // console.log(\"click\", { link_url: url.href, link_domain: url.hostname, outbound: true });\n // this._trackAuto(\"click\", { link_url: url.href, link_domain: url.hostname, outbound: true });\n // }\n // }, true);\n // }\n\n // clicks (links + buttons + dropdown items + generic interactive)\n if (em.outboundClicks) {\n document.addEventListener(\"click\", (e) => {\n const hit = this._getClickTarget(e);\n if (!hit) return;\n\n const el = hit.el;\n\n const tag = (el.tagName || \"\").toLowerCase();\n const text = this._getTextFromElement(el);\n\n const props = {\n click_type: hit.kind, // link | button | dropdown_item | interactive\n element_tag: tag,\n element_text: text || \"not_found\", // ✅ innerText/aria-label/title\n element_id: el.id || null,\n element_name: el.getAttribute?.(\"name\") || null,\n element_role: el.getAttribute?.(\"role\") || null,\n element_classes:\n (el.className && typeof el.className === \"string\") ? el.className.slice(0, 120) : null,\n element_path: this._cssPath(el),\n ...((this._pageContext && this._pageContext()) || {}), // if you added _pageContext()\n };\n\n // If link, enrich with URL + outbound\n if (hit.kind === \"link\") {\n const href = el.getAttribute(\"href\") || \"\";\n if (/^(javascript:|mailto:|tel:)/i.test(href)) return;\n\n let url;\n try { url = new URL(el.href); } catch { return; }\n\n props.link_url = url.href;\n props.link_domain = url.hostname;\n props.link_path = url.pathname;\n props.outbound = url.hostname !== location.hostname;\n }\n\n // Button details\n if (hit.kind === \"button\") {\n props.button_type = el.getAttribute?.(\"type\") || null;\n props.disabled = !!el.disabled;\n }\n\n this._trackAuto(\"click\", props);\n }, true);\n\n }\n\n // site search\n if (em.siteSearch) this._fireSiteSearch();\n\n // form interactions\n if (em.formInteractions) {\n const started = new WeakSet();\n\n document.addEventListener(\"focusin\", (e) => {\n const form = e.target && e.target.closest ? e.target.closest(\"form\") : null;\n if (!form || started.has(form)) return;\n started.add(form);\n\n this._trackAuto(\"form_start\", {\n form_id: form.id || undefined,\n form_name: form.getAttribute(\"name\") || undefined,\n form_action: form.action || undefined,\n ...this._pageContext(),\n });\n });\n\n document.addEventListener(\"submit\", (e) => {\n const form = e.target;\n if (!form) return;\n\n this._trackAuto(\"form_submit\", {\n form_id: form.id || undefined,\n form_name: form.getAttribute(\"name\") || undefined,\n form_action: form.action || undefined,\n form_destination: location.href,\n ...this._pageContext(),\n });\n }, true);\n }\n\n // file downloads\n if (em.fileDownloads && em.fileDownloads.extensions) {\n const exts = em.fileDownloads.extensions.map((x) => String(x).toLowerCase());\n\n document.addEventListener(\"click\", (e) => {\n const a = e.target && e.target.closest ? e.target.closest(\"a\") : null;\n if (!a || !a.href) return;\n\n const clean = a.href.split(\"?\")[0].toLowerCase();\n const ext = clean.split(\".\").pop() || \"\";\n if (!exts.includes(ext)) return;\n\n this._trackAuto(\"file_download\", {\n link_url: a.href,\n file_extension: ext,\n file_name: clean.split(\"/\").pop(),\n });\n }, true);\n }\n }\n\n _fireSiteSearch() {\n const em = this.cfg.enhancedMeasurement || {};\n if (!em.siteSearch) return;\n\n const params = em.siteSearch.params || [\"q\", \"s\", \"search\", \"query\"];\n const usp = new URLSearchParams(location.search);\n const key = params.find((k) => usp.get(k));\n const term = key ? usp.get(key) : null;\n\n if (term && term.trim()) {\n this._trackAuto(\"view_search_results\", {\n search_term: term.trim(),\n search_param: key,\n page_path: location.pathname,\n ...this._pageContext()\n });\n }\n }\n\n _getTextFromElement(el) {\n if (!el) return null;\n\n // common places (MUI/AntD often store label in aria-label)\n const aria = el.getAttribute?.(\"aria-label\");\n if (aria && aria.trim()) return aria.trim();\n\n const title = el.getAttribute?.(\"title\");\n if (title && title.trim()) return title.trim();\n\n // visible text\n const txt = (el.innerText || el.textContent || \"\").trim();\n if (!txt) return null;\n\n // limit length (avoid huge HTML)\n return txt.length > 80 ? txt.slice(0, 80) : txt;\n }\n\n _cssPath(el) {\n if (!el || !el.tagName) return null;\n const parts = [];\n let node = el;\n let depth = 0;\n while (node && node.nodeType === 1 && depth < 5) {\n let part = node.tagName.toLowerCase();\n if (node.id) {\n part += `#${node.id}`;\n parts.unshift(part);\n break;\n }\n const cls = (node.className && typeof node.className === \"string\")\n ? node.className.trim().split(/\\s+/).slice(0, 2).join(\".\")\n : \"\";\n if (cls) part += `.${cls}`;\n parts.unshift(part);\n node = node.parentElement;\n depth++;\n }\n return parts.join(\" > \");\n }\n\n _getClickTarget(e) {\n // Shadow DOM safe path\n const path = typeof e.composedPath === \"function\" ? e.composedPath() : null;\n const start = (path && path.length ? path[0] : e.target);\n\n const closest = (el, sel) => (el && el.closest ? el.closest(sel) : null);\n\n // 1) Links\n const a = closest(start, 'a[href]');\n if (a) return { el: a, kind: \"link\" };\n\n // 2) Buttons + button-like\n const btn = closest(\n start,\n 'button, input[type=\"button\"], input[type=\"submit\"], input[type=\"reset\"], [role=\"button\"]'\n );\n if (btn) return { el: btn, kind: \"button\" };\n\n // 3) Menu / dropdown item (generic roles)\n const menuItem = closest(\n start,\n '[role=\"menuitem\"], [role=\"menuitemradio\"], [role=\"menuitemcheckbox\"], [role=\"option\"]'\n );\n if (menuItem) return { el: menuItem, kind: \"dropdown_item\" };\n\n // 4) Generic \"interactive\" fallback:\n // - role link\n // - tabindex (focusable)\n // - aria-haspopup (opens menu)\n // - contenteditable\n const interactive = closest(\n start,\n '[role=\"link\"], [tabindex]:not([tabindex=\"-1\"]), [aria-haspopup], [contenteditable=\"true\"]'\n );\n if (interactive) return { el: interactive, kind: \"interactive\" };\n\n return null;\n }\n\n\n // -------------------------\n // PROPERTIES (AUTO)\n // -------------------------\n // _deviceProperties() {\n // return {\n // ua: navigator.userAgent,\n // lang: navigator.language,\n // tz: Intl.DateTimeFormat().resolvedOptions().timeZone,\n // screen: { w: screen.width, h: screen.height },\n // viewport: { w: window.innerWidth, h: window.innerHeight },\n // dpr: window.devicePixelRatio || 1,\n // };\n // }\n\n _deviceProperties() {\n const parser = new UAParser(navigator.userAgent);\n const ua = parser.getResult();\n\n const conn = navigator.connection || navigator.mozConnection || navigator.webkitConnection;\n\n const screenMode =\n window.matchMedia?.(\"(prefers-color-scheme: dark)\")?.matches ? \"dark\" : \"light\";\n\n return {\n // device/browser\n device_type: ua.device?.type || (matchMedia(\"(pointer:coarse)\").matches ? \"mobile\" : \"desktop\"),\n os_name: ua.os?.name || \"not_found\",\n os_version: ua.os?.version || \"not_found\",\n browser_name: ua.browser?.name || \"not_found\",\n browser_version: ua.browser?.version || \"not_found\",\n\n // app/sdk\n sdk: \"web\",\n sdk_version: this.cfg?.version || \"not_found\",\n\n // screen\n device_screen_mode: screenMode,\n screen_w: String(screen.width),\n screen_h: String(screen.height),\n viewport_w: String(window.innerWidth),\n viewport_h: String(window.innerHeight),\n density: String(window.devicePixelRatio || 1),\n color_depth: String(screen.colorDepth || \"not_found\"),\n\n // system-ish\n device_language: navigator.language || \"not_found\",\n timezone: Intl.DateTimeFormat().resolvedOptions().timeZone || \"not_found\",\n cores: navigator.hardwareConcurrency != null ? String(navigator.hardwareConcurrency) : \"not_found\",\n memory_gb: navigator.deviceMemory != null ? String(navigator.deviceMemory) : \"not_found\",\n touch_points: navigator.maxTouchPoints != null ? String(navigator.maxTouchPoints) : \"0\",\n\n // network (best-effort)\n is_data_on: navigator.onLine ? \"true\" : \"false\",\n connection_effective_type: conn?.effectiveType || \"not_found\",\n connection_downlink: conn?.downlink != null ? String(conn.downlink) : \"not_found\",\n connection_rtt: conn?.rtt != null ? String(conn.rtt) : \"not_found\",\n connection_save_data: conn?.saveData != null ? String(conn.saveData) : \"not_found\",\n\n // raw\n user_agent: navigator.userAgent,\n };\n }\n\n _userProperties() {\n const url = new URL(location.href);\n const usp = url.searchParams;\n\n const get = (k) => usp.get(k);\n const nf = \"not_found\";\n\n return {\n // acquisition\n landing_url: url.href,\n referrer: [\n {\n utm_source: get(\"utm_source\") || nf,\n },\n {\n utm_medium: get(\"utm_medium\") || nf,\n },\n {\n utm_campaign: get(\"utm_campaign\") || nf,\n },\n {\n utm_term: get(\"utm_term\") || nf,\n },\n {\n utm_content: get(\"utm_content\") || nf,\n }\n ],\n\n // ads click ids\n gclid: get(\"gclid\") || nf,\n fbclid: get(\"fbclid\") || nf,\n msclkid: get(\"msclkid\") || nf,\n\n // user preferences\n language_pref: navigator.language || nf,\n\n // identity (optional, only if you have it)\n // user_id: this.userId || nf,\n // email: this.email || nf,\n // plan: \"free\" / \"pro\" etc.\n };\n }\n\n\n // -------------------------\n // IDs\n // -------------------------\n _getOrCreateDeviceId() {\n const key = \"twinalyze_device_id\";\n const old = localStorage.getItem(key);\n if (old) return old;\n\n const id = uuid();\n // const id = `dev_${uuid()}`;\n localStorage.setItem(key, id);\n return id;\n }\n\n _pageContext() {\n const KEY_LAST = \"twinalyze_last_page_location\";\n\n const pageLocation = location.href;\n const pageDomain = location.hostname;\n const pagePath = location.pathname;\n const pageTitle = document.title;\n\n const storedPrev = sessionStorage.getItem(KEY_LAST);\n const prevLocation = storedPrev || (document.referrer || null);\n\n let prevType = \"direct\";\n if (prevLocation) {\n try {\n const prevHost = new URL(prevLocation).hostname;\n prevType = prevHost === pageDomain ? \"internal\" : \"external\";\n } catch {\n prevType = \"external\";\n }\n }\n\n return {\n page_domain: pageDomain,\n page_location: pageLocation,\n page_url: pageLocation,\n page_path: pagePath,\n page_title: pageTitle,\n previous_page_location: prevLocation,\n previous_page_type: prevType,\n };\n }\n\n // _pageViewInfo() {\n // const KEY_LAST = \"twinalyze_last_page_location\";\n // const KEY_COUNT = \"twinalyze_page_counter\";\n\n // const ctx = this._pageContext();\n\n // const prevCount = parseInt(sessionStorage.getItem(KEY_COUNT) || \"0\", 10);\n // const pageCounter = (Number.isNaN(prevCount) ? 0 : prevCount) + 1;\n\n // sessionStorage.setItem(KEY_COUNT, String(pageCounter));\n // sessionStorage.setItem(KEY_LAST, ctx.page_location);\n\n // return {\n // page_counter: pageCounter,\n // ...ctx,\n // };\n // }\n\n _pageViewInfo() {\n const KEY_LAST = \"twinalyze_last_page_location\";\n const KEY_COUNT = \"twinalyze_page_counter\";\n const KEY_LAST_COUNTED = \"twinalyze_last_counted_path\"; // ✅ new\n\n const ctx = this._pageContext(); // uses current location + previous page\n\n const currentKey = ctx.page_path; // ✅ count only on path change\n // If you want count only on full URL change, use: const currentKey = ctx.page_location;\n\n const lastCounted = sessionStorage.getItem(KEY_LAST_COUNTED);\n\n // If same page refreshed -> DO NOT increment\n let pageCounter = parseInt(sessionStorage.getItem(KEY_COUNT) || \"0\", 10);\n if (Number.isNaN(pageCounter)) pageCounter = 0;\n\n if (lastCounted !== currentKey) {\n pageCounter += 1;\n sessionStorage.setItem(KEY_COUNT, String(pageCounter));\n sessionStorage.setItem(KEY_LAST_COUNTED, currentKey);\n } else {\n // keep same counter\n sessionStorage.setItem(KEY_COUNT, String(pageCounter));\n }\n\n // update last page location (for prev page logic)\n sessionStorage.setItem(KEY_LAST, ctx.page_location);\n\n return {\n page_counter: pageCounter,\n ...ctx,\n };\n }\n}\n\nexport const TwinalyzeAnalytics = new TwinalyzeAnalyticsImpl();\n"],"mappings":"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","names":["index_exports","__export","TwinalyzeAnalytics","__toCommonJS","import_socket","import_crypto_js","import_html2canvas","UAParserPkg","UAParser","uuid","TwinalyzeAnalyticsImpl","cfg","_a","organization","DEFAULT_SCREEN_ACTIVITY","sa","eventName","properties","options","reason","err","data","stored","id","url","checkRes","ok","serverScreenshot","existsRes","createRes","sessionRes","payloadObj","timeoutMs","candidates","suffix","resEvents","resolve","done","cleanup","ev","onRes","timer","res","sendData","obj","CryptoJS","r","imgs","img","_b","isViewport","scale","canvas","html2canvas","doc","style","maxW","ratio","c2","now","throttleMs","blob","fd","e","payload","eventType","indexId","name","props","em","pv","fire","pv2","_push","_replace","args","scrollTop","scrollHeight","percent","_c","hit","el","tag","text","href","started","form","exts","x","a","clean","ext","params","usp","key","k","term","aria","title","txt","parts","node","depth","part","cls","path","start","closest","sel","btn","menuItem","interactive","_d","_e","_f","_g","_h","ua","conn","screenMode","get","nf","old","KEY_LAST","pageLocation","pageDomain","pagePath","pageTitle","prevLocation","prevType","KEY_COUNT","KEY_LAST_COUNTED","ctx","currentKey","lastCounted","pageCounter"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.js"],"sourcesContent":["import { io } from \"socket.io-client\";\nimport CryptoJS from \"crypto-js\";\nimport html2canvas from \"html2canvas\";\n// import UAParser from \"ua-parser-js\";\nimport * as UAParserPkg from \"ua-parser-js\";\nconst UAParser = UAParserPkg.UAParser;\n\nfunction uuid() {\n if (typeof crypto !== \"undefined\" && crypto.randomUUID) return crypto.randomUUID();\n return Math.random().toString(16).slice(2) + Date.now().toString(16);\n}\n\nclass TwinalyzeAnalyticsImpl {\n constructor() {\n this.cfg = null;\n\n this.socket = null;\n this.socketReady = false;\n\n this.deviceId = null;\n this.sessionId = null; // uniqueSessionId (client generated)\n\n this.sessionReady = false; // sessionCreate done\n this._booting = false; // handshake in progress\n this._disabled = false; // STOP state\n\n this.pendingEvents = []; // queued until sessionReady\n this.scrollFired = false;\n\n this._initialized = false;\n\n this._sentSessionUpdate = false; // one-time\n this._sentUserUpdate = false; // one-time\n\n this.indexId = 0;\n this._indexKey = null;\n\n this._lastShotAt = 0;\n this._shotInFlight = false;\n }\n\n // -------------------------\n // PUBLIC API\n // -------------------------\n init(cfg) {\n if (typeof window === \"undefined\") return;\n if (this._initialized) return; // strict-mode safe\n this._initialized = true;\n\n const organization = cfg.organization || cfg.orgId;\n\n const DEFAULT_SCREEN_ACTIVITY = {\n enabled: false,\n apiUrl: cfg.screenActivity?.apiUrl ?? `${cfg.socketUrl}/api/app/screenActivity/screenActivityDetails_post`,\n captureOn: [\"page_view\", \"click\", \"form_start\", \"form_submit\", \"scroll\", \"view_search_results\", \"custom_event\"],\n throttleMs: 2000,\n jpegQuality: 0.7,\n };\n\n // support: screenshots: true OR screenActivity: true OR screenActivity: { ... }\n const sa =\n typeof cfg.screenActivity === \"object\"\n ? { ...DEFAULT_SCREEN_ACTIVITY, ...cfg.screenActivity }\n : { ...DEFAULT_SCREEN_ACTIVITY };\n\n this.cfg = {\n apiKey: cfg.apiKey,\n organization,\n socketUrl: \"https://api.twinalyze.com\",\n // socketUrl: \"http://192.168.1.26:3000\",\n\n debug: true,\n\n // encryption like Android (AES key = socket.id)\n encrypt: true, // default true\n\n // session id from client side\n persistSession: cfg.persistSession !== false, // default true (sessionStorage)\n sessionKey: \"twinalyze_session_id\",\n\n // server response event naming\n // we listen to multiple possible response events to be safe:\n // `${event}:res`, `${event}Res`, `${event}Response`, `${event}`\n responseSuffix: cfg.responseSuffix || \":res\",\n responseTimeoutMs: cfg.responseTimeoutMs || 15000,\n\n // eventAdd fields\n source: cfg.source || \"web\",\n indexId: cfg.indexId ?? null,\n\n // auto tracking options\n enhancedMeasurement: {\n pageViews: true,\n scrolls: true,\n outboundClicks: true,\n siteSearch: { params: [\"q\", \"s\", \"search\", \"query\"] },\n formInteractions: true,\n fileDownloads: { extensions: [\"pdf\", \"zip\", \"apk\", \"doc\", \"docx\", \"xls\", \"xlsx\", \"ppt\", \"pptx\"] },\n ...(cfg.enhancedMeasurement || {}),\n },\n apiBaseUrl: cfg.apiBaseUrl || cfg.endpoint || cfg.socketUrl, // http base for REST APIs\n screenActivity: sa,\n // screenActivity: {\n // enabled: cfg.screenActivity?.enabled ?? true,\n // apiUrl: cfg.screenActivity?.apiUrl ?? `${cfg.socketUrl}/api/app/screenActivity/screenActivityDetails_post`,\n // jpegQuality: cfg.screenActivity?.jpegQuality ?? 0.7,\n // throttleMs: cfg.screenActivity?.throttleMs ?? 2000, // don't screenshot every click instantly\n // capture: cfg.screenActivity?.capture ?? \"viewport\", // \"viewport\" | \"fullpage\"\n // maxWidth: cfg.screenActivity?.maxWidth ?? 1280, // reduce size\n // },\n };\n\n if (!this.cfg.apiKey || !this.cfg.organization) {\n console.log(\"[Twinalyze] ❌ Missing apiKey / organization / socketUrl\");\n return;\n }\n\n this.deviceId = this._getOrCreateDeviceId();\n\n if (this.cfg.persistSession) {\n this.sessionId = sessionStorage.getItem(this.cfg.sessionKey) || null;\n }\n\n this._connectSocket();\n this._setupEnhancedMeasurement();\n }\n\n // custom event (web dev only uses this)\n track(eventName, properties = {}) {\n if (this._disabled) return;\n\n if (this.cfg?.debug) console.log(\"[Twinalyze][CUSTOM]\", eventName, properties);\n\n if (!this.sessionReady) {\n this.pendingEvents.push({ eventName, properties: { ...this._pageContext(), ...properties }, ts: Date.now(), eventType: \"custom\" });\n return;\n }\n\n this._emitEventAdd(eventName, { ...this._pageContext(), ...properties }, \"custom\");\n }\n\n // -------------------------\n // SOCKET\n // -------------------------\n _connectSocket() {\n if (this.socket) return;\n\n const options = ({\n transports: [\"websocket\"], // Force WebSocket transport\n reconnection: true,\n reconnectionAttempts: Infinity, // Keep reconnecting indefinitely\n reconnectionDelay: 2000, // 2 seconds delay between reconnection attempts\n reconnectionDelayMax: 10000, // Max reconnection delay (10 seconds)\n timeout: 20000, // Connection timeout (20 seconds)\n path: \"/socket.io/\",\n auth: {\n \"x-api-key\": this.cfg.apiKey,\n \"x-organization-id\": this.cfg.organization,\n \"x-app-version-name\": this.cfg.appVersionName || \"not_found\",\n \"x-analytics-sdk-version\": this.cfg.analyticsSdkVersion || this.cfg.version || \"1.0.0\",\n \"x-ad-analytics-sdk-version\": this.cfg.adAnalyticsSdkVersion || \"not_found\",\n \"x-app-package-name\": this.cfg.appPackageName || location.hostname,\n \"x-platform\": \"web\",\n \"x-sdk-version\": this.cfg.version || \"1.0.0\",\n },\n extraHeaders: {\n \"Origin\": this.cfg.socketUrl,\n \"x-api-key\": this.cfg.apiKey,\n \"x-organization-id\": this.cfg.organization,\n \"x-app-version-name\": this.cfg.appVersionName || \"not_found\",\n \"x-analytics-sdk-version\": this.cfg.analyticsSdkVersion || this.cfg.version || \"1.0.0\",\n \"x-ad-analytics-sdk-version\": this.cfg.adAnalyticsSdkVersion || \"not_found\",\n \"x-app-package-name\": this.cfg.appPackageName || location.hostname,\n \"x-platform\": \"web\",\n \"x-sdk-version\": this.cfg.version || \"1.0.0\",\n },\n methods: [\"GET\", \"POST\"],\n })\n\n this.socket = io(this.cfg.socketUrl, options);\n\n this.socket.on(\"connect\", async () => {\n this.socketReady = true;\n\n if (this.cfg.debug) {\n console.log(\"[Twinalyze][SOCKET] ✅ connected CHANGED NEW 1\", {\n id: this.socket.id,\n transport: this.socket.io.engine.transport.name,\n });\n }\n\n await this._startFlow();\n });\n\n this.socket.on(\"disconnect\", (reason) => {\n this.socketReady = false;\n\n // on reconnect, flow should run again (sessionCreate + one-time updates)\n this.sessionReady = false;\n this._booting = false;\n this._sentSessionUpdate = false;\n this._sentUserUpdate = false;\n this._resetIndexId();\n\n if (this.cfg.debug) console.log(\"[Twinalyze][SOCKET] ❌ disconnected:\", reason);\n });\n\n this.socket.on(\"connect_error\", (err) => {\n this.socketReady = false;\n if (this.cfg.debug) console.log(\"[Twinalyze][SOCKET] ❌ connect_error:\", err?.message || err);\n });\n\n // Listening to sdk_upgrade_warning\n this.socket.on(\"sdk_upgrade_warning\", (data) => {\n if (data && data.message) {\n console.log(\"SDK Upgrade Warning:\", data.message);\n\n // Show the upgrade message\n alert(data.message);\n\n // Optionally, check for updateUrl and redirect\n if (data.updateUrl) {\n const proceedToUpdate = confirm(\"A new SDK version is available. Do you want to upgrade now?\");\n\n if (proceedToUpdate) {\n window.location.href = data.updateUrl; // Redirect to the update page\n }\n }\n } else {\n console.error(\"Received invalid data in sdk_upgrade_warning event:\", data);\n }\n });\n\n // optional incoming debug\n // this.socket.onAny((event, ...args) => {\n // if (this.cfg.debug) console.log(\"[Twinalyze][IN]\", event, args);\n // });\n }\n\n _initIndexId() {\n // call only when sessionId is available\n this._indexKey = `twinalyze_event_index_${this.sessionId}`;\n\n // always start from 0 for new session\n // if you want resume on refresh within same session, load from sessionStorage\n const stored = sessionStorage.getItem(this._indexKey);\n this.indexId = stored ? parseInt(stored, 10) : 0;\n if (Number.isNaN(this.indexId)) this.indexId = 0;\n }\n\n _nextIndexId() {\n const id = this.indexId;\n this.indexId += 1;\n\n if (this._indexKey) {\n sessionStorage.setItem(this._indexKey, String(this.indexId));\n }\n return id; // ✅ return current id then increment\n }\n\n _resetIndexId() {\n this.indexId = 0;\n if (this._indexKey) sessionStorage.removeItem(this._indexKey);\n this._indexKey = null;\n }\n\n _normalizeHttpBase(url) {\n return String(url || \"\")\n .replace(/^ws:\\/\\//, \"http://\")\n .replace(/^wss:\\/\\//, \"https://\")\n .replace(/\\/$/, \"\");\n }\n\n // -------------------------\n // YOUR EXACT FLOW\n // -------------------------\n async _startFlow() {\n if (this._disabled) return;\n if (!this.socketReady) return;\n if (this._booting) return;\n\n this._booting = true;\n\n try {\n // 1) user|userCheckApp { apiKey, organization }\n const checkRes = await this._request(\"user|userCheckApp\", {\n apiKey: this.cfg.apiKey,\n organization: this.cfg.organization,\n });\n\n if (this.cfg.debug) console.log(\"[Twinalyze][FLOW] userCheckRes:\", checkRes);\n\n // STOP condition:\n // \"if success true and status 0 then next else STOP\"\n const ok =\n checkRes &&\n checkRes.success === true &&\n (checkRes.status === 0 || checkRes.status === \"0\");\n\n const serverScreenshot = !!checkRes?.debugScreenshotCapture;\n this.cfg.screenActivity.enabled = serverScreenshot;\n\n if (!ok) {\n this._disabled = true;\n this.sessionReady = false;\n this.cfg.screenActivity.enabled = false;\n if (this.cfg.debug) console.log(\"[Twinalyze][FLOW] 🛑 STOP (userCheck failed)\");\n return;\n }\n\n // 2) user|userExistsApp { apiKey, deviceId, organization }\n const existsRes = await this._request(\"user|userExistsApp\", {\n apiKey: this.cfg.apiKey,\n deviceId: this.deviceId,\n organization: this.cfg.organization,\n });\n\n if (this.cfg.debug) console.log(\"[Twinalyze][FLOW] userExistsRes:\", existsRes);\n\n // your diagram says: if success===true => user exists\n const userExists = !!(existsRes && (existsRes.success === true) && (existsRes.userRegister === true));\n\n // 3) if user not exists => user|web|userCreateApp { apiKey, deviceId, properties, organization }\n if (!userExists) {\n const createRes = await this._request(\"user|web|userCreateApp\", {\n apiKey: this.cfg.apiKey,\n deviceId: this.deviceId,\n properties: this._userProperties(),\n organization: this.cfg.organization,\n });\n\n if (this.cfg.debug) console.log(\"[Twinalyze][FLOW] userCreateRes:\", createRes);\n\n // if backend returns explicit failure, stop\n if (createRes && createRes.success === false) {\n this._disabled = true;\n this.sessionReady = false;\n if (this.cfg.debug) console.log(\"[Twinalyze][FLOW] 🛑 STOP (userCreate failed)\");\n return;\n }\n }\n\n // 4) create session id from client (your requirement)\n if (!this.sessionId) {\n this.sessionId = `sess_${uuid()}`;\n if (this.cfg.persistSession) {\n sessionStorage.setItem(this.cfg.sessionKey, this.sessionId);\n }\n }\n\n // 5) session|web|sessionCreateApp\n // { apiKey, deviceId, socketId, uniqueSessionId, deviceProperties, organization }\n const sessionRes = await this._request(\"session|web|sessionCreateApp\", {\n apiKey: this.cfg.apiKey,\n deviceId: this.deviceId,\n socketId: this.socket.id,\n uniqueSessionId: this.sessionId,\n deviceProperties: this._deviceProperties(),\n organization: this.cfg.organization,\n });\n\n if (this.cfg.debug) console.log(\"[Twinalyze][FLOW] sessionCreateRes:\", sessionRes);\n\n if (sessionRes && sessionRes.success === false) {\n this._disabled = true;\n this.sessionReady = false;\n return;\n }\n\n // Mark ready even if backend doesn't return anything useful\n this.sessionReady = true;\n this._initIndexId();\n\n // // 6) one-time updates (exact payloads)\n // this._emitSessionUpdateOnce();\n // this._emitUserUpdateOnce();\n\n // 7) flush queued auto/custom events\n this._flushPendingEvents();\n } finally {\n this._booting = false;\n }\n }\n\n // -------------------------\n // REQUEST/RESPONSE (NO ACK)\n // waits for server response event using \"on/once\"\n // -------------------------\n _request(eventName, payloadObj) {\n const timeoutMs = this.cfg.responseTimeoutMs;\n\n const candidates = [];\n const suffix = this.cfg.responseSuffix;\n\n // Try multiple common response event names (so you don’t have to guess)\n candidates.push(`${eventName}${suffix}`); // user|userCheckApp:res\n candidates.push(`${eventName}Res`); // user|userCheckAppRes\n candidates.push(`${eventName}Response`); // user|userCheckAppResponse\n candidates.push(eventName); // same event name as response (some backends do this)\n\n // unique\n const resEvents = [...new Set(candidates)];\n\n return new Promise((resolve) => {\n if (!this.socketReady) return resolve(null);\n\n let done = false;\n\n const cleanup = () => {\n resEvents.forEach((ev) => this.socket.off(ev, onRes));\n clearTimeout(timer);\n };\n\n const onRes = (res) => {\n if (done) return;\n done = true;\n cleanup();\n resolve(res);\n };\n\n // IMPORTANT: register listeners before emit (so we don't miss fast responses)\n resEvents.forEach((ev) => this.socket.on(ev, onRes));\n\n const timer = setTimeout(() => {\n if (done) return;\n done = true;\n cleanup();\n resolve(null);\n }, timeoutMs);\n\n const sendData = this.cfg.encrypt ? this._encrypt(payloadObj) : payloadObj;\n this.socket.emit(eventName, sendData);\n });\n }\n\n _encrypt(obj) {\n const CRYPTOJS_SECRET_KEY = \"3Xn8vQp2Lm9sAa7ZkT1yCw5eR0uH6dJ4\"\n // AES key = socket.id (matches your Android/backend style)\n return CryptoJS.AES.encrypt(JSON.stringify(obj), CRYPTOJS_SECRET_KEY).toString();\n // return CryptoJS.AES.encrypt(JSON.stringify(obj), this.socket.id).toString();\n }\n\n async _waitForRenderStable() {\n // 2 frames\n await new Promise(r => requestAnimationFrame(() => requestAnimationFrame(r)));\n\n // fonts\n try { if (document.fonts?.ready) await document.fonts.ready; } catch { }\n\n // images\n const imgs = Array.from(document.images || []).filter(img => !img.complete);\n await Promise.allSettled(\n imgs.map(img => new Promise(res => {\n img.addEventListener(\"load\", res, { once: true });\n img.addEventListener(\"error\", res, { once: true });\n }))\n );\n }\n\n async _captureViewportJpegBlob() {\n await this._waitForRenderStable();\n\n const isViewport = (this.cfg.screenActivity?.capture || \"viewport\") === \"viewport\";\n const scale = Math.min(2, window.devicePixelRatio || 1);\n\n const canvas = await html2canvas(document.documentElement, {\n useCORS: true,\n allowTaint: false,\n backgroundColor: \"#ffffff\",\n logging: false,\n\n ...(isViewport\n ? { x: window.scrollX, y: window.scrollY, width: window.innerWidth, height: window.innerHeight }\n : { x: 0, y: 0, width: document.documentElement.scrollWidth, height: document.documentElement.scrollHeight }\n ),\n\n scale,\n onclone: (doc) => {\n const style = doc.createElement(\"style\");\n style.textContent = `\n * { animation: none !important; transition: none !important; caret-color: transparent !important; }\n `;\n doc.head.appendChild(style);\n },\n });\n\n // downscale big screenshots (optional but helpful)\n const maxW = this.cfg.screenActivity?.maxWidth ?? 1280;\n if (canvas.width > maxW) {\n const ratio = maxW / canvas.width;\n const c2 = document.createElement(\"canvas\");\n c2.width = Math.round(canvas.width * ratio);\n c2.height = Math.round(canvas.height * ratio);\n c2.getContext(\"2d\").drawImage(canvas, 0, 0, c2.width, c2.height);\n return await new Promise((resolve) =>\n c2.toBlob(resolve, \"image/jpeg\", this.cfg.screenActivity?.jpegQuality ?? 0.7)\n );\n }\n\n return await new Promise((resolve) =>\n canvas.toBlob(resolve, \"image/jpeg\", this.cfg.screenActivity?.jpegQuality ?? 0.7)\n );\n }\n\n async _uploadScreenActivity({ eventName, properties }) {\n if (!this.cfg.screenActivity?.enabled) return;\n if (!this.sessionReady) return;\n\n const now = Date.now();\n const throttleMs = this.cfg.screenActivity?.throttleMs ?? 2000;\n if (now - this._lastShotAt < throttleMs) return;\n if (this._shotInFlight) return;\n\n this._shotInFlight = true;\n this._lastShotAt = now;\n\n try {\n const blob = await this._captureViewportJpegBlob();\n if (!blob) return;\n\n const fd = new FormData();\n fd.append(\"apiKey\", this.cfg.apiKey);\n fd.append(\"organization\", this.cfg.organization);\n fd.append(\"screenName\", document.title || \"unknown\");\n fd.append(\"identifier\", JSON.stringify(properties || {})); // ✅ you asked: identifier = properties object\n fd.append(\"description\", eventName || \"event\");\n fd.append(\"image\", blob, `shot_${Date.now()}.jpg`);\n\n await fetch(this.cfg.screenActivity.apiUrl, {\n method: \"POST\",\n body: fd,\n keepalive: true,\n });\n } catch (e) {\n if (this.cfg.debug) console.log(\"[Twinalyze][SCREENSHOT] error:\", e?.message || e);\n } finally {\n this._shotInFlight = false;\n }\n }\n\n // -------------------------\n // EMITS (exact payloads you gave)\n // -------------------------\n _emitSessionUpdateOnce() {\n if (!this.sessionReady) return;\n if (this._sentSessionUpdate) return;\n this._sentSessionUpdate = true;\n\n const payload = {\n apiKey: this.cfg.apiKey,\n deviceId: this.deviceId,\n deviceProperties: this._deviceProperties(),\n organization: this.cfg.organization,\n };\n\n const sendData = this.cfg.encrypt ? this._encrypt(payload) : payload;\n this.socket.emit(\"session|sessionUpdateApp\", sendData);\n\n if (this.cfg.debug) console.log(\"[Twinalyze] ✅ sessionUpdate sent\");\n }\n\n _emitUserUpdateOnce() {\n if (!this.sessionReady) return;\n if (this._sentUserUpdate) return;\n this._sentUserUpdate = true;\n\n const payload = {\n apiKey: this.cfg.apiKey,\n deviceId: this.deviceId,\n properties: this._userProperties(),\n organization: this.cfg.organization,\n };\n\n const sendData = this.cfg.encrypt ? this._encrypt(payload) : payload;\n this.socket.emit(\"user|userUpdateApp\", sendData);\n\n if (this.cfg.debug) console.log(\"[Twinalyze] ✅ userUpdate sent\");\n }\n\n _emitEventAdd(eventName, properties = {}, eventType = \"auto\") {\n if (!this.sessionReady) return;\n\n // if session just got ready and index not set\n if (this._indexKey == null) this._initIndexId();\n const indexId = this._nextIndexId();\n\n const payload = {\n socketId: this.socket.id,\n date: new Date().toISOString(),\n eventType: eventType,\n uniqueSessionId: this.sessionId,\n deviceId: this.deviceId,\n eventName,\n properties: properties,\n apiKey: this.cfg.apiKey,\n source: this.cfg.source,\n // ✅ auto index\n indexId,\n organization: this.cfg.organization,\n };\n\n console.log(\"payload session|web|sessionEventAddApp\", payload);\n\n const sendData = this.cfg.encrypt ? this._encrypt(payload) : payload;\n this.socket.emit(\"session|web|sessionEventAddApp\", sendData);\n\n // this._maybeUploadScreenActivity(eventName, properties, indexId).catch(() => { });\n this._uploadScreenActivity({ eventName, properties });\n\n if (this.cfg.debug) console.log(\"[Twinalyze][EVENT_ADD]\", eventName, properties, indexId);\n }\n\n _flushPendingEvents() {\n if (!this.sessionReady) return;\n if (!this.pendingEvents.length) return;\n\n const items = this.pendingEvents.splice(0, this.pendingEvents.length);\n items.forEach((e) => this._emitEventAdd(e.eventName, e.properties || {}, e.eventType || \"auto\"));\n }\n\n // -------------------------\n // AUTO EVENTS (GA-style)\n // -------------------------\n _trackAuto(name, props = {}) {\n if (this._disabled) return;\n if (this.cfg?.debug) console.log(\"[Twinalyze][AUTO]\", name, props);\n\n if (!this.sessionReady) {\n this.pendingEvents.push({ eventName: name, properties: props, ts: Date.now(), eventType: \"auto\" });\n return;\n }\n\n this._emitEventAdd(name, props, \"auto\");\n }\n\n _setupEnhancedMeasurement() {\n const em = this.cfg.enhancedMeasurement || {};\n\n // page_view (normal + SPA)\n if (em.pageViews) {\n const pv = this._pageViewInfo();\n this._trackAuto(\"page_view\", pv);\n\n const fire = () => {\n this.scrollFired = false;\n const pv2 = this._pageViewInfo();\n this._trackAuto(\"page_view\", pv2);\n this._fireSiteSearch();\n };\n\n const _push = history.pushState;\n const _replace = history.replaceState;\n\n history.pushState = (...args) => { _push.apply(history, args); fire(); };\n history.replaceState = (...args) => { _replace.apply(history, args); fire(); };\n window.addEventListener(\"popstate\", fire);\n }\n\n // scroll (90%)\n if (em.scrolls) {\n window.addEventListener(\"scroll\", () => {\n if (this.scrollFired) return;\n\n const doc = document.documentElement;\n const scrollTop = window.scrollY || doc.scrollTop;\n const scrollHeight = doc.scrollHeight - doc.clientHeight;\n if (scrollHeight <= 0) return;\n\n const percent = Math.round((scrollTop / scrollHeight) * 100);\n if (percent >= 90) {\n this.scrollFired = true;\n this._trackAuto(\"scroll\", { percent_scrolled: percent, ...this._pageContext() });\n }\n }, { passive: true });\n }\n\n // // outbound clicks\n // if (em.outboundClicks) {\n // document.addEventListener(\"click\", (e) => {\n // const a = e.target && e.target.closest ? e.target.closest(\"a\") : null;\n // if (!a || !a.href) return;\n // if (/^(javascript:|mailto:|tel:)/i.test(a.href)) return;\n\n // let url;\n // try { url = new URL(a.href); } catch { return; }\n // if (url.hostname !== location.hostname) {\n // console.log(\"click\", { link_url: url.href, link_domain: url.hostname, outbound: true });\n // this._trackAuto(\"click\", { link_url: url.href, link_domain: url.hostname, outbound: true });\n // }\n // }, true);\n // }\n\n // clicks (links + buttons + dropdown items + generic interactive)\n if (em.outboundClicks) {\n document.addEventListener(\"click\", (e) => {\n const hit = this._getClickTarget(e);\n if (!hit) return;\n\n const el = hit.el;\n\n const tag = (el.tagName || \"\").toLowerCase();\n const text = this._getTextFromElement(el);\n\n const props = {\n click_type: hit.kind, // link | button | dropdown_item | interactive\n element_tag: tag,\n element_text: text || \"not_found\", // ✅ innerText/aria-label/title\n element_id: el.id || null,\n element_name: el.getAttribute?.(\"name\") || null,\n element_role: el.getAttribute?.(\"role\") || null,\n element_classes:\n (el.className && typeof el.className === \"string\") ? el.className.slice(0, 120) : null,\n element_path: this._cssPath(el),\n ...((this._pageContext && this._pageContext()) || {}), // if you added _pageContext()\n };\n\n // If link, enrich with URL + outbound\n if (hit.kind === \"link\") {\n const href = el.getAttribute(\"href\") || \"\";\n if (/^(javascript:|mailto:|tel:)/i.test(href)) return;\n\n let url;\n try { url = new URL(el.href); } catch { return; }\n\n props.link_url = url.href;\n props.link_domain = url.hostname;\n props.link_path = url.pathname;\n props.outbound = url.hostname !== location.hostname;\n }\n\n // Button details\n if (hit.kind === \"button\") {\n props.button_type = el.getAttribute?.(\"type\") || null;\n props.disabled = !!el.disabled;\n }\n\n this._trackAuto(\"click\", props);\n }, true);\n\n }\n\n // site search\n if (em.siteSearch) this._fireSiteSearch();\n\n // form interactions\n if (em.formInteractions) {\n const started = new WeakSet();\n\n document.addEventListener(\"focusin\", (e) => {\n const form = e.target && e.target.closest ? e.target.closest(\"form\") : null;\n if (!form || started.has(form)) return;\n started.add(form);\n\n this._trackAuto(\"form_start\", {\n form_id: form.id || undefined,\n form_name: form.getAttribute(\"name\") || undefined,\n form_action: form.action || undefined,\n ...this._pageContext(),\n });\n });\n\n document.addEventListener(\"submit\", (e) => {\n const form = e.target;\n if (!form) return;\n\n this._trackAuto(\"form_submit\", {\n form_id: form.id || undefined,\n form_name: form.getAttribute(\"name\") || undefined,\n form_action: form.action || undefined,\n form_destination: location.href,\n ...this._pageContext(),\n });\n }, true);\n }\n\n // file downloads\n if (em.fileDownloads && em.fileDownloads.extensions) {\n const exts = em.fileDownloads.extensions.map((x) => String(x).toLowerCase());\n\n document.addEventListener(\"click\", (e) => {\n const a = e.target && e.target.closest ? e.target.closest(\"a\") : null;\n if (!a || !a.href) return;\n\n const clean = a.href.split(\"?\")[0].toLowerCase();\n const ext = clean.split(\".\").pop() || \"\";\n if (!exts.includes(ext)) return;\n\n this._trackAuto(\"file_download\", {\n link_url: a.href,\n file_extension: ext,\n file_name: clean.split(\"/\").pop(),\n });\n }, true);\n }\n }\n\n _fireSiteSearch() {\n const em = this.cfg.enhancedMeasurement || {};\n if (!em.siteSearch) return;\n\n const params = em.siteSearch.params || [\"q\", \"s\", \"search\", \"query\"];\n const usp = new URLSearchParams(location.search);\n const key = params.find((k) => usp.get(k));\n const term = key ? usp.get(key) : null;\n\n if (term && term.trim()) {\n this._trackAuto(\"view_search_results\", {\n search_term: term.trim(),\n search_param: key,\n page_path: location.pathname,\n ...this._pageContext()\n });\n }\n }\n\n _getTextFromElement(el) {\n if (!el) return null;\n\n // common places (MUI/AntD often store label in aria-label)\n const aria = el.getAttribute?.(\"aria-label\");\n if (aria && aria.trim()) return aria.trim();\n\n const title = el.getAttribute?.(\"title\");\n if (title && title.trim()) return title.trim();\n\n // visible text\n const txt = (el.innerText || el.textContent || \"\").trim();\n if (!txt) return null;\n\n // limit length (avoid huge HTML)\n return txt.length > 80 ? txt.slice(0, 80) : txt;\n }\n\n _cssPath(el) {\n if (!el || !el.tagName) return null;\n const parts = [];\n let node = el;\n let depth = 0;\n while (node && node.nodeType === 1 && depth < 5) {\n let part = node.tagName.toLowerCase();\n if (node.id) {\n part += `#${node.id}`;\n parts.unshift(part);\n break;\n }\n const cls = (node.className && typeof node.className === \"string\")\n ? node.className.trim().split(/\\s+/).slice(0, 2).join(\".\")\n : \"\";\n if (cls) part += `.${cls}`;\n parts.unshift(part);\n node = node.parentElement;\n depth++;\n }\n return parts.join(\" > \");\n }\n\n _getClickTarget(e) {\n // Shadow DOM safe path\n const path = typeof e.composedPath === \"function\" ? e.composedPath() : null;\n const start = (path && path.length ? path[0] : e.target);\n\n const closest = (el, sel) => (el && el.closest ? el.closest(sel) : null);\n\n // 1) Links\n const a = closest(start, 'a[href]');\n if (a) return { el: a, kind: \"link\" };\n\n // 2) Buttons + button-like\n const btn = closest(\n start,\n 'button, input[type=\"button\"], input[type=\"submit\"], input[type=\"reset\"], [role=\"button\"]'\n );\n if (btn) return { el: btn, kind: \"button\" };\n\n // 3) Menu / dropdown item (generic roles)\n const menuItem = closest(\n start,\n '[role=\"menuitem\"], [role=\"menuitemradio\"], [role=\"menuitemcheckbox\"], [role=\"option\"]'\n );\n if (menuItem) return { el: menuItem, kind: \"dropdown_item\" };\n\n // 4) Generic \"interactive\" fallback:\n // - role link\n // - tabindex (focusable)\n // - aria-haspopup (opens menu)\n // - contenteditable\n const interactive = closest(\n start,\n '[role=\"link\"], [tabindex]:not([tabindex=\"-1\"]), [aria-haspopup], [contenteditable=\"true\"]'\n );\n if (interactive) return { el: interactive, kind: \"interactive\" };\n\n return null;\n }\n\n\n // -------------------------\n // PROPERTIES (AUTO)\n // -------------------------\n // _deviceProperties() {\n // return {\n // ua: navigator.userAgent,\n // lang: navigator.language,\n // tz: Intl.DateTimeFormat().resolvedOptions().timeZone,\n // screen: { w: screen.width, h: screen.height },\n // viewport: { w: window.innerWidth, h: window.innerHeight },\n // dpr: window.devicePixelRatio || 1,\n // };\n // }\n\n _deviceProperties() {\n const parser = new UAParser(navigator.userAgent);\n const ua = parser.getResult();\n\n const conn = navigator.connection || navigator.mozConnection || navigator.webkitConnection;\n\n const screenMode =\n window.matchMedia?.(\"(prefers-color-scheme: dark)\")?.matches ? \"dark\" : \"light\";\n\n return {\n // device/browser\n device_type: ua.device?.type || (matchMedia(\"(pointer:coarse)\").matches ? \"mobile\" : \"desktop\"),\n os_name: ua.os?.name || \"not_found\",\n os_version: ua.os?.version || \"not_found\",\n browser_name: ua.browser?.name || \"not_found\",\n browser_version: ua.browser?.version || \"not_found\",\n\n // app/sdk\n sdk: \"web\",\n sdk_version: this.cfg?.version || \"not_found\",\n\n // screen\n device_screen_mode: screenMode,\n screen_w: String(screen.width),\n screen_h: String(screen.height),\n viewport_w: String(window.innerWidth),\n viewport_h: String(window.innerHeight),\n density: String(window.devicePixelRatio || 1),\n color_depth: String(screen.colorDepth || \"not_found\"),\n\n // system-ish\n device_language: navigator.language || \"not_found\",\n timezone: Intl.DateTimeFormat().resolvedOptions().timeZone || \"not_found\",\n cores: navigator.hardwareConcurrency != null ? String(navigator.hardwareConcurrency) : \"not_found\",\n memory_gb: navigator.deviceMemory != null ? String(navigator.deviceMemory) : \"not_found\",\n touch_points: navigator.maxTouchPoints != null ? String(navigator.maxTouchPoints) : \"0\",\n\n // network (best-effort)\n is_data_on: navigator.onLine ? \"true\" : \"false\",\n connection_effective_type: conn?.effectiveType || \"not_found\",\n connection_downlink: conn?.downlink != null ? String(conn.downlink) : \"not_found\",\n connection_rtt: conn?.rtt != null ? String(conn.rtt) : \"not_found\",\n connection_save_data: conn?.saveData != null ? String(conn.saveData) : \"not_found\",\n\n // raw\n user_agent: navigator.userAgent,\n };\n }\n\n _userProperties() {\n const url = new URL(location.href);\n const usp = url.searchParams;\n\n const get = (k) => usp.get(k);\n const nf = \"not_found\";\n\n return {\n // acquisition\n landing_url: url.href,\n referrer: [\n {\n utm_source: get(\"utm_source\") || nf,\n },\n {\n utm_medium: get(\"utm_medium\") || nf,\n },\n {\n utm_campaign: get(\"utm_campaign\") || nf,\n },\n {\n utm_term: get(\"utm_term\") || nf,\n },\n {\n utm_content: get(\"utm_content\") || nf,\n }\n ],\n\n // ads click ids\n gclid: get(\"gclid\") || nf,\n fbclid: get(\"fbclid\") || nf,\n msclkid: get(\"msclkid\") || nf,\n\n // user preferences\n language_pref: navigator.language || nf,\n\n // identity (optional, only if you have it)\n // user_id: this.userId || nf,\n // email: this.email || nf,\n // plan: \"free\" / \"pro\" etc.\n };\n }\n\n\n // -------------------------\n // IDs\n // -------------------------\n _getOrCreateDeviceId() {\n const key = \"twinalyze_device_id\";\n const old = localStorage.getItem(key);\n if (old) return old;\n\n const id = uuid();\n // const id = `dev_${uuid()}`;\n localStorage.setItem(key, id);\n return id;\n }\n\n _pageContext() {\n const KEY_LAST = \"twinalyze_last_page_location\";\n\n const pageLocation = location.href;\n const pageDomain = location.hostname;\n const pagePath = location.pathname;\n const pageTitle = document.title;\n\n const storedPrev = sessionStorage.getItem(KEY_LAST);\n const prevLocation = storedPrev || (document.referrer || null);\n\n let prevType = \"direct\";\n if (prevLocation) {\n try {\n const prevHost = new URL(prevLocation).hostname;\n prevType = prevHost === pageDomain ? \"internal\" : \"external\";\n } catch {\n prevType = \"external\";\n }\n }\n\n return {\n page_domain: pageDomain,\n page_location: pageLocation,\n page_url: pageLocation,\n page_path: pagePath,\n page_title: pageTitle,\n previous_page_location: prevLocation,\n previous_page_type: prevType,\n };\n }\n\n // _pageViewInfo() {\n // const KEY_LAST = \"twinalyze_last_page_location\";\n // const KEY_COUNT = \"twinalyze_page_counter\";\n\n // const ctx = this._pageContext();\n\n // const prevCount = parseInt(sessionStorage.getItem(KEY_COUNT) || \"0\", 10);\n // const pageCounter = (Number.isNaN(prevCount) ? 0 : prevCount) + 1;\n\n // sessionStorage.setItem(KEY_COUNT, String(pageCounter));\n // sessionStorage.setItem(KEY_LAST, ctx.page_location);\n\n // return {\n // page_counter: pageCounter,\n // ...ctx,\n // };\n // }\n\n _pageViewInfo() {\n const KEY_LAST = \"twinalyze_last_page_location\";\n const KEY_COUNT = \"twinalyze_page_counter\";\n const KEY_LAST_COUNTED = \"twinalyze_last_counted_path\"; // ✅ new\n\n const ctx = this._pageContext(); // uses current location + previous page\n\n const currentKey = ctx.page_path; // ✅ count only on path change\n // If you want count only on full URL change, use: const currentKey = ctx.page_location;\n\n const lastCounted = sessionStorage.getItem(KEY_LAST_COUNTED);\n\n // If same page refreshed -> DO NOT increment\n let pageCounter = parseInt(sessionStorage.getItem(KEY_COUNT) || \"0\", 10);\n if (Number.isNaN(pageCounter)) pageCounter = 0;\n\n if (lastCounted !== currentKey) {\n pageCounter += 1;\n sessionStorage.setItem(KEY_COUNT, String(pageCounter));\n sessionStorage.setItem(KEY_LAST_COUNTED, currentKey);\n } else {\n // keep same counter\n sessionStorage.setItem(KEY_COUNT, String(pageCounter));\n }\n\n // update last page location (for prev page logic)\n sessionStorage.setItem(KEY_LAST, ctx.page_location);\n\n return {\n page_counter: pageCounter,\n ...ctx,\n };\n }\n}\n\nconst _instance = new TwinalyzeAnalyticsImpl();\n\nconst PUBLIC_METHODS = [\n \"init\",\n \"track\"\n];\n\nexport const TwinalyzeAnalytics = Object.fromEntries(\n PUBLIC_METHODS.map((m) => [m, _instance[m].bind(_instance)])\n);\n\nexport default TwinalyzeAnalytics;"],"mappings":"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","names":["index_exports","__export","TwinalyzeAnalytics","index_default","__toCommonJS","import_socket","import_crypto_js","import_html2canvas","UAParserPkg","UAParser","uuid","TwinalyzeAnalyticsImpl","cfg","_a","organization","DEFAULT_SCREEN_ACTIVITY","sa","eventName","properties","options","reason","err","data","stored","id","url","checkRes","ok","serverScreenshot","existsRes","createRes","sessionRes","payloadObj","timeoutMs","candidates","suffix","resEvents","resolve","done","cleanup","ev","onRes","timer","res","sendData","obj","CryptoJS","r","imgs","img","_b","isViewport","scale","canvas","html2canvas","doc","style","maxW","ratio","c2","now","throttleMs","blob","fd","e","payload","eventType","indexId","name","props","em","pv","fire","pv2","_push","_replace","args","scrollTop","scrollHeight","percent","_c","hit","el","tag","text","href","started","form","exts","x","a","clean","ext","params","usp","key","k","term","aria","title","txt","parts","node","depth","part","cls","path","start","closest","sel","btn","menuItem","interactive","_d","_e","_f","_g","_h","ua","conn","screenMode","get","nf","old","KEY_LAST","pageLocation","pageDomain","pagePath","pageTitle","prevLocation","prevType","KEY_COUNT","KEY_LAST_COUNTED","ctx","currentKey","lastCounted","pageCounter","_instance","PUBLIC_METHODS","m"]}
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{io as
|
|
1
|
+
import{io as y}from"socket.io-client";import w from"crypto-js";import v from"html2canvas";import*as m from"ua-parser-js";var k=m.UAParser;function f(){return typeof crypto<"u"&&crypto.randomUUID?crypto.randomUUID():Math.random().toString(16).slice(2)+Date.now().toString(16)}var g=class{constructor(){this.cfg=null,this.socket=null,this.socketReady=!1,this.deviceId=null,this.sessionId=null,this.sessionReady=!1,this._booting=!1,this._disabled=!1,this.pendingEvents=[],this.scrollFired=!1,this._initialized=!1,this._sentSessionUpdate=!1,this._sentUserUpdate=!1,this.indexId=0,this._indexKey=null,this._lastShotAt=0,this._shotInFlight=!1}init(t){var n;if(typeof window>"u"||this._initialized)return;this._initialized=!0;let i=t.organization||t.orgId,e={enabled:!1,apiUrl:((n=t.screenActivity)==null?void 0:n.apiUrl)??`${t.socketUrl}/api/app/screenActivity/screenActivityDetails_post`,captureOn:["page_view","click","form_start","form_submit","scroll","view_search_results","custom_event"],throttleMs:2e3,jpegQuality:.7},s=typeof t.screenActivity=="object"?{...e,...t.screenActivity}:{...e};if(this.cfg={apiKey:t.apiKey,organization:i,socketUrl:"https://api.twinalyze.com",debug:!0,encrypt:!0,persistSession:t.persistSession!==!1,sessionKey:"twinalyze_session_id",responseSuffix:t.responseSuffix||":res",responseTimeoutMs:t.responseTimeoutMs||15e3,source:t.source||"web",indexId:t.indexId??null,enhancedMeasurement:{pageViews:!0,scrolls:!0,outboundClicks:!0,siteSearch:{params:["q","s","search","query"]},formInteractions:!0,fileDownloads:{extensions:["pdf","zip","apk","doc","docx","xls","xlsx","ppt","pptx"]},...t.enhancedMeasurement||{}},apiBaseUrl:t.apiBaseUrl||t.endpoint||t.socketUrl,screenActivity:s},!this.cfg.apiKey||!this.cfg.organization){console.log("[Twinalyze] \u274C Missing apiKey / organization / socketUrl");return}this.deviceId=this._getOrCreateDeviceId(),this.cfg.persistSession&&(this.sessionId=sessionStorage.getItem(this.cfg.sessionKey)||null),this._connectSocket(),this._setupEnhancedMeasurement()}track(t,i={}){var e;if(!this._disabled){if((e=this.cfg)!=null&&e.debug&&console.log("[Twinalyze][CUSTOM]",t,i),!this.sessionReady){this.pendingEvents.push({eventName:t,properties:{...this._pageContext(),...i},ts:Date.now(),eventType:"custom"});return}this._emitEventAdd(t,{...this._pageContext(),...i},"custom")}}_connectSocket(){if(this.socket)return;let t={transports:["websocket"],reconnection:!0,reconnectionAttempts:1/0,reconnectionDelay:2e3,reconnectionDelayMax:1e4,timeout:2e4,path:"/socket.io/",auth:{"x-api-key":this.cfg.apiKey,"x-organization-id":this.cfg.organization,"x-app-version-name":this.cfg.appVersionName||"not_found","x-analytics-sdk-version":this.cfg.analyticsSdkVersion||this.cfg.version||"1.0.0","x-ad-analytics-sdk-version":this.cfg.adAnalyticsSdkVersion||"not_found","x-app-package-name":this.cfg.appPackageName||location.hostname,"x-platform":"web","x-sdk-version":this.cfg.version||"1.0.0"},extraHeaders:{Origin:this.cfg.socketUrl,"x-api-key":this.cfg.apiKey,"x-organization-id":this.cfg.organization,"x-app-version-name":this.cfg.appVersionName||"not_found","x-analytics-sdk-version":this.cfg.analyticsSdkVersion||this.cfg.version||"1.0.0","x-ad-analytics-sdk-version":this.cfg.adAnalyticsSdkVersion||"not_found","x-app-package-name":this.cfg.appPackageName||location.hostname,"x-platform":"web","x-sdk-version":this.cfg.version||"1.0.0"},methods:["GET","POST"]};this.socket=y(this.cfg.socketUrl,t),this.socket.on("connect",async()=>{this.socketReady=!0,this.cfg.debug&&console.log("[Twinalyze][SOCKET] \u2705 connected CHANGED NEW 1",{id:this.socket.id,transport:this.socket.io.engine.transport.name}),await this._startFlow()}),this.socket.on("disconnect",i=>{this.socketReady=!1,this.sessionReady=!1,this._booting=!1,this._sentSessionUpdate=!1,this._sentUserUpdate=!1,this._resetIndexId(),this.cfg.debug&&console.log("[Twinalyze][SOCKET] \u274C disconnected:",i)}),this.socket.on("connect_error",i=>{this.socketReady=!1,this.cfg.debug&&console.log("[Twinalyze][SOCKET] \u274C connect_error:",(i==null?void 0:i.message)||i)}),this.socket.on("sdk_upgrade_warning",i=>{i&&i.message?(console.log("SDK Upgrade Warning:",i.message),alert(i.message),i.updateUrl&&confirm("A new SDK version is available. Do you want to upgrade now?")&&(window.location.href=i.updateUrl)):console.error("Received invalid data in sdk_upgrade_warning event:",i)})}_initIndexId(){this._indexKey=`twinalyze_event_index_${this.sessionId}`;let t=sessionStorage.getItem(this._indexKey);this.indexId=t?parseInt(t,10):0,Number.isNaN(this.indexId)&&(this.indexId=0)}_nextIndexId(){let t=this.indexId;return this.indexId+=1,this._indexKey&&sessionStorage.setItem(this._indexKey,String(this.indexId)),t}_resetIndexId(){this.indexId=0,this._indexKey&&sessionStorage.removeItem(this._indexKey),this._indexKey=null}_normalizeHttpBase(t){return String(t||"").replace(/^ws:\/\//,"http://").replace(/^wss:\/\//,"https://").replace(/\/$/,"")}async _startFlow(){if(!this._disabled&&this.socketReady&&!this._booting){this._booting=!0;try{let t=await this._request("user|userCheckApp",{apiKey:this.cfg.apiKey,organization:this.cfg.organization});this.cfg.debug&&console.log("[Twinalyze][FLOW] userCheckRes:",t);let i=t&&t.success===!0&&(t.status===0||t.status==="0"),e=!!(t!=null&&t.debugScreenshotCapture);if(this.cfg.screenActivity.enabled=e,!i){this._disabled=!0,this.sessionReady=!1,this.cfg.screenActivity.enabled=!1,this.cfg.debug&&console.log("[Twinalyze][FLOW] \u{1F6D1} STOP (userCheck failed)");return}let s=await this._request("user|userExistsApp",{apiKey:this.cfg.apiKey,deviceId:this.deviceId,organization:this.cfg.organization});if(this.cfg.debug&&console.log("[Twinalyze][FLOW] userExistsRes:",s),!!!(s&&s.success===!0&&s.userRegister===!0)){let o=await this._request("user|web|userCreateApp",{apiKey:this.cfg.apiKey,deviceId:this.deviceId,properties:this._userProperties(),organization:this.cfg.organization});if(this.cfg.debug&&console.log("[Twinalyze][FLOW] userCreateRes:",o),o&&o.success===!1){this._disabled=!0,this.sessionReady=!1,this.cfg.debug&&console.log("[Twinalyze][FLOW] \u{1F6D1} STOP (userCreate failed)");return}}this.sessionId||(this.sessionId=`sess_${f()}`,this.cfg.persistSession&&sessionStorage.setItem(this.cfg.sessionKey,this.sessionId));let r=await this._request("session|web|sessionCreateApp",{apiKey:this.cfg.apiKey,deviceId:this.deviceId,socketId:this.socket.id,uniqueSessionId:this.sessionId,deviceProperties:this._deviceProperties(),organization:this.cfg.organization});if(this.cfg.debug&&console.log("[Twinalyze][FLOW] sessionCreateRes:",r),r&&r.success===!1){this._disabled=!0,this.sessionReady=!1;return}this.sessionReady=!0,this._initIndexId(),this._flushPendingEvents()}finally{this._booting=!1}}}_request(t,i){let e=this.cfg.responseTimeoutMs,s=[],n=this.cfg.responseSuffix;s.push(`${t}${n}`),s.push(`${t}Res`),s.push(`${t}Response`),s.push(t);let r=[...new Set(s)];return new Promise(o=>{if(!this.socketReady)return o(null);let a=!1,c=()=>{r.forEach(u=>this.socket.off(u,d)),clearTimeout(h)},d=u=>{a||(a=!0,c(),o(u))};r.forEach(u=>this.socket.on(u,d));let h=setTimeout(()=>{a||(a=!0,c(),o(null))},e),l=this.cfg.encrypt?this._encrypt(i):i;this.socket.emit(t,l)})}_encrypt(t){return w.AES.encrypt(JSON.stringify(t),"3Xn8vQp2Lm9sAa7ZkT1yCw5eR0uH6dJ4").toString()}async _waitForRenderStable(){var i;await new Promise(e=>requestAnimationFrame(()=>requestAnimationFrame(e)));try{(i=document.fonts)!=null&&i.ready&&await document.fonts.ready}catch{}let t=Array.from(document.images||[]).filter(e=>!e.complete);await Promise.allSettled(t.map(e=>new Promise(s=>{e.addEventListener("load",s,{once:!0}),e.addEventListener("error",s,{once:!0})})))}async _captureViewportJpegBlob(){var n,r;await this._waitForRenderStable();let t=(((n=this.cfg.screenActivity)==null?void 0:n.capture)||"viewport")==="viewport",i=Math.min(2,window.devicePixelRatio||1),e=await v(document.documentElement,{useCORS:!0,allowTaint:!1,backgroundColor:"#ffffff",logging:!1,...t?{x:window.scrollX,y:window.scrollY,width:window.innerWidth,height:window.innerHeight}:{x:0,y:0,width:document.documentElement.scrollWidth,height:document.documentElement.scrollHeight},scale:i,onclone:o=>{let a=o.createElement("style");a.textContent=`
|
|
2
2
|
* { animation: none !important; transition: none !important; caret-color: transparent !important; }
|
|
3
|
-
`,o.head.appendChild(a)}}),s=((r=this.cfg.screenActivity)==null?void 0:r.maxWidth)??1280;if(e.width>s){let o=s/e.width,a=document.createElement("canvas");return a.width=Math.round(e.width*o),a.height=Math.round(e.height*o),a.getContext("2d").drawImage(e,0,0,a.width,a.height),await new Promise(c=>{var d;return a.toBlob(c,"image/jpeg",((d=this.cfg.screenActivity)==null?void 0:d.jpegQuality)??.7)})}return await new Promise(o=>{var a;return e.toBlob(o,"image/jpeg",((a=this.cfg.screenActivity)==null?void 0:a.jpegQuality)??.7)})}async _uploadScreenActivity({eventName:t,properties:i}){var n,r;if(!((n=this.cfg.screenActivity)!=null&&n.enabled)||!this.sessionReady)return;let e=Date.now(),s=((r=this.cfg.screenActivity)==null?void 0:r.throttleMs)??2e3;if(!(e-this._lastShotAt<s)&&!this._shotInFlight){this._shotInFlight=!0,this._lastShotAt=e;try{let o=await this._captureViewportJpegBlob();if(!o)return;let a=new FormData;a.append("apiKey",this.cfg.apiKey),a.append("organization",this.cfg.organization),a.append("screenName",document.title||"unknown"),a.append("identifier",JSON.stringify(i||{})),a.append("description",t||"event"),a.append("image",o,`shot_${Date.now()}.jpg`),await fetch(this.cfg.screenActivity.apiUrl,{method:"POST",body:a,keepalive:!0})}catch(o){this.cfg.debug&&console.log("[Twinalyze][SCREENSHOT] error:",(o==null?void 0:o.message)||o)}finally{this._shotInFlight=!1}}}_emitSessionUpdateOnce(){if(!this.sessionReady||this._sentSessionUpdate)return;this._sentSessionUpdate=!0;let t={apiKey:this.cfg.apiKey,deviceId:this.deviceId,deviceProperties:this._deviceProperties(),organization:this.cfg.organization},i=this.cfg.encrypt?this._encrypt(t):t;this.socket.emit("session|sessionUpdateApp",i),this.cfg.debug&&console.log("[Twinalyze] \u2705 sessionUpdate sent")}_emitUserUpdateOnce(){if(!this.sessionReady||this._sentUserUpdate)return;this._sentUserUpdate=!0;let t={apiKey:this.cfg.apiKey,deviceId:this.deviceId,properties:this._userProperties(),organization:this.cfg.organization},i=this.cfg.encrypt?this._encrypt(t):t;this.socket.emit("user|userUpdateApp",i),this.cfg.debug&&console.log("[Twinalyze] \u2705 userUpdate sent")}_emitEventAdd(t,i={},e="auto"){if(!this.sessionReady)return;this._indexKey==null&&this._initIndexId();let s=this._nextIndexId(),n={socketId:this.socket.id,date:new Date().toISOString(),eventType:e,uniqueSessionId:this.sessionId,deviceId:this.deviceId,eventName:t,properties:i,apiKey:this.cfg.apiKey,source:this.cfg.source,indexId:s,organization:this.cfg.organization};console.log("payload session|web|sessionEventAddApp",n);let r=this.cfg.encrypt?this._encrypt(n):n;this.socket.emit("session|web|sessionEventAddApp",r),this._uploadScreenActivity({eventName:t,properties:i}),this.cfg.debug&&console.log("[Twinalyze][EVENT_ADD]",t,i,s)}_flushPendingEvents(){if(!this.sessionReady||!this.pendingEvents.length)return;this.pendingEvents.splice(0,this.pendingEvents.length).forEach(i=>this._emitEventAdd(i.eventName,i.properties||{},i.eventType||"auto"))}_trackAuto(t,i={}){var e;if(!this._disabled){if((e=this.cfg)!=null&&e.debug&&console.log("[Twinalyze][AUTO]",t,i),!this.sessionReady){this.pendingEvents.push({eventName:t,properties:i,ts:Date.now(),eventType:"auto"});return}this._emitEventAdd(t,i,"auto")}}_setupEnhancedMeasurement(){let t=this.cfg.enhancedMeasurement||{};if(t.pageViews){let i=this._pageViewInfo();this._trackAuto("page_view",i);let e=()=>{this.scrollFired=!1;let r=this._pageViewInfo();this._trackAuto("page_view",r),this._fireSiteSearch()},s=history.pushState,n=history.replaceState;history.pushState=(...r)=>{s.apply(history,r),e()},history.replaceState=(...r)=>{n.apply(history,r),e()},window.addEventListener("popstate",e)}if(t.scrolls&&window.addEventListener("scroll",()=>{if(this.scrollFired)return;let i=document.documentElement,e=window.scrollY||i.scrollTop,s=i.scrollHeight-i.clientHeight;if(s<=0)return;let n=Math.round(e/s*100);n>=90&&(this.scrollFired=!0,this._trackAuto("scroll",{percent_scrolled:n,...this._pageContext()}))},{passive:!0}),t.outboundClicks&&document.addEventListener("click",i=>{var a,c,d;let e=this._getClickTarget(i);if(!e)return;let s=e.el,n=(s.tagName||"").toLowerCase(),r=this._getTextFromElement(s),o={click_type:e.kind,element_tag:n,element_text:r||"not_found",element_id:s.id||null,element_name:((a=s.getAttribute)==null?void 0:a.call(s,"name"))||null,element_role:((c=s.getAttribute)==null?void 0:c.call(s,"role"))||null,element_classes:s.className&&typeof s.className=="string"?s.className.slice(0,120):null,element_path:this._cssPath(s),...this._pageContext&&this._pageContext()||{}};if(e.kind==="link"){let h=s.getAttribute("href")||"";if(/^(javascript:|mailto:|tel:)/i.test(h))return;let l;try{l=new URL(s.href)}catch{return}o.link_url=l.href,o.link_domain=l.hostname,o.link_path=l.pathname,o.outbound=l.hostname!==location.hostname}e.kind==="button"&&(o.button_type=((d=s.getAttribute)==null?void 0:d.call(s,"type"))||null,o.disabled=!!s.disabled),this._trackAuto("click",o)},!0),t.siteSearch&&this._fireSiteSearch(),t.formInteractions){let i=new WeakSet;document.addEventListener("focusin",e=>{let s=e.target&&e.target.closest?e.target.closest("form"):null;!s||i.has(s)||(i.add(s),this._trackAuto("form_start",{form_id:s.id||void 0,form_name:s.getAttribute("name")||void 0,form_action:s.action||void 0,...this._pageContext()}))}),document.addEventListener("submit",e=>{let s=e.target;s&&this._trackAuto("form_submit",{form_id:s.id||void 0,form_name:s.getAttribute("name")||void 0,form_action:s.action||void 0,form_destination:location.href,...this._pageContext()})},!0)}if(t.fileDownloads&&t.fileDownloads.extensions){let i=t.fileDownloads.extensions.map(e=>String(e).toLowerCase());document.addEventListener("click",e=>{let s=e.target&&e.target.closest?e.target.closest("a"):null;if(!s||!s.href)return;let n=s.href.split("?")[0].toLowerCase(),r=n.split(".").pop()||"";i.includes(r)&&this._trackAuto("file_download",{link_url:s.href,file_extension:r,file_name:n.split("/").pop()})},!0)}}_fireSiteSearch(){let t=this.cfg.enhancedMeasurement||{};if(!t.siteSearch)return;let i=t.siteSearch.params||["q","s","search","query"],e=new URLSearchParams(location.search),s=i.find(r=>e.get(r)),n=s?e.get(s):null;n&&n.trim()&&this._trackAuto("view_search_results",{search_term:n.trim(),search_param:s,page_path:location.pathname,...this._pageContext()})}_getTextFromElement(t){var n,r;if(!t)return null;let i=(n=t.getAttribute)==null?void 0:n.call(t,"aria-label");if(i&&i.trim())return i.trim();let e=(r=t.getAttribute)==null?void 0:r.call(t,"title");if(e&&e.trim())return e.trim();let s=(t.innerText||t.textContent||"").trim();return s?s.length>80?s.slice(0,80):s:null}_cssPath(t){if(!t||!t.tagName)return null;let i=[],e=t,s=0;for(;e&&e.nodeType===1&&s<5;){let n=e.tagName.toLowerCase();if(e.id){n+=`#${e.id}`,i.unshift(n);break}let r=e.className&&typeof e.className=="string"?e.className.trim().split(/\s+/).slice(0,2).join("."):"";r&&(n+=`.${r}`),i.unshift(n),e=e.parentElement,s++}return i.join(" > ")}_getClickTarget(t){let i=typeof t.composedPath=="function"?t.composedPath():null,e=i&&i.length?i[0]:t.target,s=(c,d)=>c&&c.closest?c.closest(d):null,n=s(e,"a[href]");if(n)return{el:n,kind:"link"};let r=s(e,'button, input[type="button"], input[type="submit"], input[type="reset"], [role="button"]');if(r)return{el:r,kind:"button"};let o=s(e,'[role="menuitem"], [role="menuitemradio"], [role="menuitemcheckbox"], [role="option"]');if(o)return{el:o,kind:"dropdown_item"};let a=s(e,'[role="link"], [tabindex]:not([tabindex="-1"]), [aria-haspopup], [contenteditable="true"]');return a?{el:a,kind:"interactive"}:null}_deviceProperties(){var n,r,o,a,c,d,h,l;let i=new w(navigator.userAgent).getResult(),e=navigator.connection||navigator.mozConnection||navigator.webkitConnection,s=(r=(n=window.matchMedia)==null?void 0:n.call(window,"(prefers-color-scheme: dark)"))!=null&&r.matches?"dark":"light";return{device_type:((o=i.device)==null?void 0:o.type)||(matchMedia("(pointer:coarse)").matches?"mobile":"desktop"),os_name:((a=i.os)==null?void 0:a.name)||"not_found",os_version:((c=i.os)==null?void 0:c.version)||"not_found",browser_name:((d=i.browser)==null?void 0:d.name)||"not_found",browser_version:((h=i.browser)==null?void 0:h.version)||"not_found",sdk:"web",sdk_version:((l=this.cfg)==null?void 0:l.version)||"not_found",device_screen_mode:s,screen_w:String(screen.width),screen_h:String(screen.height),viewport_w:String(window.innerWidth),viewport_h:String(window.innerHeight),density:String(window.devicePixelRatio||1),color_depth:String(screen.colorDepth||"not_found"),device_language:navigator.language||"not_found",timezone:Intl.DateTimeFormat().resolvedOptions().timeZone||"not_found",cores:navigator.hardwareConcurrency!=null?String(navigator.hardwareConcurrency):"not_found",memory_gb:navigator.deviceMemory!=null?String(navigator.deviceMemory):"not_found",touch_points:navigator.maxTouchPoints!=null?String(navigator.maxTouchPoints):"0",is_data_on:navigator.onLine?"true":"false",connection_effective_type:(e==null?void 0:e.effectiveType)||"not_found",connection_downlink:(e==null?void 0:e.downlink)!=null?String(e.downlink):"not_found",connection_rtt:(e==null?void 0:e.rtt)!=null?String(e.rtt):"not_found",connection_save_data:(e==null?void 0:e.saveData)!=null?String(e.saveData):"not_found",user_agent:navigator.userAgent}}_userProperties(){let t=new URL(location.href),i=t.searchParams,e=n=>i.get(n),s="not_found";return{landing_url:t.href,referrer:[{utm_source:e("utm_source")||s},{utm_medium:e("utm_medium")||s},{utm_campaign:e("utm_campaign")||s},{utm_term:e("utm_term")||s},{utm_content:e("utm_content")||s}],gclid:e("gclid")||s,fbclid:e("fbclid")||s,msclkid:e("msclkid")||s,language_pref:navigator.language||s}}_getOrCreateDeviceId(){let t="twinalyze_device_id",i=localStorage.getItem(t);if(i)return i;let e=g();return localStorage.setItem(t,e),e}_pageContext(){let t="twinalyze_last_page_location",i=location.href,e=location.hostname,s=location.pathname,n=document.title,o=sessionStorage.getItem(t)||document.referrer||null,a="direct";if(o)try{a=new URL(o).hostname===e?"internal":"external"}catch{a="external"}return{page_domain:e,page_location:i,page_url:i,page_path:s,page_title:n,previous_page_location:o,previous_page_type:a}}_pageViewInfo(){let t="twinalyze_last_page_location",i="twinalyze_page_counter",e="twinalyze_last_counted_path",s=this._pageContext(),n=s.page_path,r=sessionStorage.getItem(e),o=parseInt(sessionStorage.getItem(i)||"0",10);return Number.isNaN(o)&&(o=0),r!==n?(o+=1,sessionStorage.setItem(i,String(o)),sessionStorage.setItem(e,n)):sessionStorage.setItem(i,String(o)),sessionStorage.setItem(t,s.page_location),{page_counter:o,...s}}},b=new p;export{b as TwinalyzeAnalytics};
|
|
3
|
+
`,o.head.appendChild(a)}}),s=((r=this.cfg.screenActivity)==null?void 0:r.maxWidth)??1280;if(e.width>s){let o=s/e.width,a=document.createElement("canvas");return a.width=Math.round(e.width*o),a.height=Math.round(e.height*o),a.getContext("2d").drawImage(e,0,0,a.width,a.height),await new Promise(c=>{var d;return a.toBlob(c,"image/jpeg",((d=this.cfg.screenActivity)==null?void 0:d.jpegQuality)??.7)})}return await new Promise(o=>{var a;return e.toBlob(o,"image/jpeg",((a=this.cfg.screenActivity)==null?void 0:a.jpegQuality)??.7)})}async _uploadScreenActivity({eventName:t,properties:i}){var n,r;if(!((n=this.cfg.screenActivity)!=null&&n.enabled)||!this.sessionReady)return;let e=Date.now(),s=((r=this.cfg.screenActivity)==null?void 0:r.throttleMs)??2e3;if(!(e-this._lastShotAt<s)&&!this._shotInFlight){this._shotInFlight=!0,this._lastShotAt=e;try{let o=await this._captureViewportJpegBlob();if(!o)return;let a=new FormData;a.append("apiKey",this.cfg.apiKey),a.append("organization",this.cfg.organization),a.append("screenName",document.title||"unknown"),a.append("identifier",JSON.stringify(i||{})),a.append("description",t||"event"),a.append("image",o,`shot_${Date.now()}.jpg`),await fetch(this.cfg.screenActivity.apiUrl,{method:"POST",body:a,keepalive:!0})}catch(o){this.cfg.debug&&console.log("[Twinalyze][SCREENSHOT] error:",(o==null?void 0:o.message)||o)}finally{this._shotInFlight=!1}}}_emitSessionUpdateOnce(){if(!this.sessionReady||this._sentSessionUpdate)return;this._sentSessionUpdate=!0;let t={apiKey:this.cfg.apiKey,deviceId:this.deviceId,deviceProperties:this._deviceProperties(),organization:this.cfg.organization},i=this.cfg.encrypt?this._encrypt(t):t;this.socket.emit("session|sessionUpdateApp",i),this.cfg.debug&&console.log("[Twinalyze] \u2705 sessionUpdate sent")}_emitUserUpdateOnce(){if(!this.sessionReady||this._sentUserUpdate)return;this._sentUserUpdate=!0;let t={apiKey:this.cfg.apiKey,deviceId:this.deviceId,properties:this._userProperties(),organization:this.cfg.organization},i=this.cfg.encrypt?this._encrypt(t):t;this.socket.emit("user|userUpdateApp",i),this.cfg.debug&&console.log("[Twinalyze] \u2705 userUpdate sent")}_emitEventAdd(t,i={},e="auto"){if(!this.sessionReady)return;this._indexKey==null&&this._initIndexId();let s=this._nextIndexId(),n={socketId:this.socket.id,date:new Date().toISOString(),eventType:e,uniqueSessionId:this.sessionId,deviceId:this.deviceId,eventName:t,properties:i,apiKey:this.cfg.apiKey,source:this.cfg.source,indexId:s,organization:this.cfg.organization};console.log("payload session|web|sessionEventAddApp",n);let r=this.cfg.encrypt?this._encrypt(n):n;this.socket.emit("session|web|sessionEventAddApp",r),this._uploadScreenActivity({eventName:t,properties:i}),this.cfg.debug&&console.log("[Twinalyze][EVENT_ADD]",t,i,s)}_flushPendingEvents(){if(!this.sessionReady||!this.pendingEvents.length)return;this.pendingEvents.splice(0,this.pendingEvents.length).forEach(i=>this._emitEventAdd(i.eventName,i.properties||{},i.eventType||"auto"))}_trackAuto(t,i={}){var e;if(!this._disabled){if((e=this.cfg)!=null&&e.debug&&console.log("[Twinalyze][AUTO]",t,i),!this.sessionReady){this.pendingEvents.push({eventName:t,properties:i,ts:Date.now(),eventType:"auto"});return}this._emitEventAdd(t,i,"auto")}}_setupEnhancedMeasurement(){let t=this.cfg.enhancedMeasurement||{};if(t.pageViews){let i=this._pageViewInfo();this._trackAuto("page_view",i);let e=()=>{this.scrollFired=!1;let r=this._pageViewInfo();this._trackAuto("page_view",r),this._fireSiteSearch()},s=history.pushState,n=history.replaceState;history.pushState=(...r)=>{s.apply(history,r),e()},history.replaceState=(...r)=>{n.apply(history,r),e()},window.addEventListener("popstate",e)}if(t.scrolls&&window.addEventListener("scroll",()=>{if(this.scrollFired)return;let i=document.documentElement,e=window.scrollY||i.scrollTop,s=i.scrollHeight-i.clientHeight;if(s<=0)return;let n=Math.round(e/s*100);n>=90&&(this.scrollFired=!0,this._trackAuto("scroll",{percent_scrolled:n,...this._pageContext()}))},{passive:!0}),t.outboundClicks&&document.addEventListener("click",i=>{var a,c,d;let e=this._getClickTarget(i);if(!e)return;let s=e.el,n=(s.tagName||"").toLowerCase(),r=this._getTextFromElement(s),o={click_type:e.kind,element_tag:n,element_text:r||"not_found",element_id:s.id||null,element_name:((a=s.getAttribute)==null?void 0:a.call(s,"name"))||null,element_role:((c=s.getAttribute)==null?void 0:c.call(s,"role"))||null,element_classes:s.className&&typeof s.className=="string"?s.className.slice(0,120):null,element_path:this._cssPath(s),...this._pageContext&&this._pageContext()||{}};if(e.kind==="link"){let h=s.getAttribute("href")||"";if(/^(javascript:|mailto:|tel:)/i.test(h))return;let l;try{l=new URL(s.href)}catch{return}o.link_url=l.href,o.link_domain=l.hostname,o.link_path=l.pathname,o.outbound=l.hostname!==location.hostname}e.kind==="button"&&(o.button_type=((d=s.getAttribute)==null?void 0:d.call(s,"type"))||null,o.disabled=!!s.disabled),this._trackAuto("click",o)},!0),t.siteSearch&&this._fireSiteSearch(),t.formInteractions){let i=new WeakSet;document.addEventListener("focusin",e=>{let s=e.target&&e.target.closest?e.target.closest("form"):null;!s||i.has(s)||(i.add(s),this._trackAuto("form_start",{form_id:s.id||void 0,form_name:s.getAttribute("name")||void 0,form_action:s.action||void 0,...this._pageContext()}))}),document.addEventListener("submit",e=>{let s=e.target;s&&this._trackAuto("form_submit",{form_id:s.id||void 0,form_name:s.getAttribute("name")||void 0,form_action:s.action||void 0,form_destination:location.href,...this._pageContext()})},!0)}if(t.fileDownloads&&t.fileDownloads.extensions){let i=t.fileDownloads.extensions.map(e=>String(e).toLowerCase());document.addEventListener("click",e=>{let s=e.target&&e.target.closest?e.target.closest("a"):null;if(!s||!s.href)return;let n=s.href.split("?")[0].toLowerCase(),r=n.split(".").pop()||"";i.includes(r)&&this._trackAuto("file_download",{link_url:s.href,file_extension:r,file_name:n.split("/").pop()})},!0)}}_fireSiteSearch(){let t=this.cfg.enhancedMeasurement||{};if(!t.siteSearch)return;let i=t.siteSearch.params||["q","s","search","query"],e=new URLSearchParams(location.search),s=i.find(r=>e.get(r)),n=s?e.get(s):null;n&&n.trim()&&this._trackAuto("view_search_results",{search_term:n.trim(),search_param:s,page_path:location.pathname,...this._pageContext()})}_getTextFromElement(t){var n,r;if(!t)return null;let i=(n=t.getAttribute)==null?void 0:n.call(t,"aria-label");if(i&&i.trim())return i.trim();let e=(r=t.getAttribute)==null?void 0:r.call(t,"title");if(e&&e.trim())return e.trim();let s=(t.innerText||t.textContent||"").trim();return s?s.length>80?s.slice(0,80):s:null}_cssPath(t){if(!t||!t.tagName)return null;let i=[],e=t,s=0;for(;e&&e.nodeType===1&&s<5;){let n=e.tagName.toLowerCase();if(e.id){n+=`#${e.id}`,i.unshift(n);break}let r=e.className&&typeof e.className=="string"?e.className.trim().split(/\s+/).slice(0,2).join("."):"";r&&(n+=`.${r}`),i.unshift(n),e=e.parentElement,s++}return i.join(" > ")}_getClickTarget(t){let i=typeof t.composedPath=="function"?t.composedPath():null,e=i&&i.length?i[0]:t.target,s=(c,d)=>c&&c.closest?c.closest(d):null,n=s(e,"a[href]");if(n)return{el:n,kind:"link"};let r=s(e,'button, input[type="button"], input[type="submit"], input[type="reset"], [role="button"]');if(r)return{el:r,kind:"button"};let o=s(e,'[role="menuitem"], [role="menuitemradio"], [role="menuitemcheckbox"], [role="option"]');if(o)return{el:o,kind:"dropdown_item"};let a=s(e,'[role="link"], [tabindex]:not([tabindex="-1"]), [aria-haspopup], [contenteditable="true"]');return a?{el:a,kind:"interactive"}:null}_deviceProperties(){var n,r,o,a,c,d,h,l;let i=new k(navigator.userAgent).getResult(),e=navigator.connection||navigator.mozConnection||navigator.webkitConnection,s=(r=(n=window.matchMedia)==null?void 0:n.call(window,"(prefers-color-scheme: dark)"))!=null&&r.matches?"dark":"light";return{device_type:((o=i.device)==null?void 0:o.type)||(matchMedia("(pointer:coarse)").matches?"mobile":"desktop"),os_name:((a=i.os)==null?void 0:a.name)||"not_found",os_version:((c=i.os)==null?void 0:c.version)||"not_found",browser_name:((d=i.browser)==null?void 0:d.name)||"not_found",browser_version:((h=i.browser)==null?void 0:h.version)||"not_found",sdk:"web",sdk_version:((l=this.cfg)==null?void 0:l.version)||"not_found",device_screen_mode:s,screen_w:String(screen.width),screen_h:String(screen.height),viewport_w:String(window.innerWidth),viewport_h:String(window.innerHeight),density:String(window.devicePixelRatio||1),color_depth:String(screen.colorDepth||"not_found"),device_language:navigator.language||"not_found",timezone:Intl.DateTimeFormat().resolvedOptions().timeZone||"not_found",cores:navigator.hardwareConcurrency!=null?String(navigator.hardwareConcurrency):"not_found",memory_gb:navigator.deviceMemory!=null?String(navigator.deviceMemory):"not_found",touch_points:navigator.maxTouchPoints!=null?String(navigator.maxTouchPoints):"0",is_data_on:navigator.onLine?"true":"false",connection_effective_type:(e==null?void 0:e.effectiveType)||"not_found",connection_downlink:(e==null?void 0:e.downlink)!=null?String(e.downlink):"not_found",connection_rtt:(e==null?void 0:e.rtt)!=null?String(e.rtt):"not_found",connection_save_data:(e==null?void 0:e.saveData)!=null?String(e.saveData):"not_found",user_agent:navigator.userAgent}}_userProperties(){let t=new URL(location.href),i=t.searchParams,e=n=>i.get(n),s="not_found";return{landing_url:t.href,referrer:[{utm_source:e("utm_source")||s},{utm_medium:e("utm_medium")||s},{utm_campaign:e("utm_campaign")||s},{utm_term:e("utm_term")||s},{utm_content:e("utm_content")||s}],gclid:e("gclid")||s,fbclid:e("fbclid")||s,msclkid:e("msclkid")||s,language_pref:navigator.language||s}}_getOrCreateDeviceId(){let t="twinalyze_device_id",i=localStorage.getItem(t);if(i)return i;let e=f();return localStorage.setItem(t,e),e}_pageContext(){let t="twinalyze_last_page_location",i=location.href,e=location.hostname,s=location.pathname,n=document.title,o=sessionStorage.getItem(t)||document.referrer||null,a="direct";if(o)try{a=new URL(o).hostname===e?"internal":"external"}catch{a="external"}return{page_domain:e,page_location:i,page_url:i,page_path:s,page_title:n,previous_page_location:o,previous_page_type:a}}_pageViewInfo(){let t="twinalyze_last_page_location",i="twinalyze_page_counter",e="twinalyze_last_counted_path",s=this._pageContext(),n=s.page_path,r=sessionStorage.getItem(e),o=parseInt(sessionStorage.getItem(i)||"0",10);return Number.isNaN(o)&&(o=0),r!==n?(o+=1,sessionStorage.setItem(i,String(o)),sessionStorage.setItem(e,n)):sessionStorage.setItem(i,String(o)),sessionStorage.setItem(t,s.page_location),{page_counter:o,...s}}},_=new g,S=["init","track"],x=Object.fromEntries(S.map(p=>[p,_[p].bind(_)])),E=x;export{x as TwinalyzeAnalytics,E as default};
|
|
4
4
|
//# sourceMappingURL=index.mjs.map
|