@kryptonhq/analytics 0.1.0 → 0.1.1

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 CHANGED
@@ -16,6 +16,8 @@ const krypton = new Krypton({
16
16
  endpoint: "https://ingest.yourdomain.com",
17
17
  autoPageview: true,
18
18
  heatmap: true,
19
+ consentRequired: true,
20
+ showConsentBanner: true,
19
21
  });
20
22
 
21
23
  krypton.track("signup_clicked", { plan: "pro" });
@@ -33,7 +35,9 @@ Use jsDelivr:
33
35
  apiKey: "kapi_xxx",
34
36
  endpoint: "https://ingest.yourdomain.com",
35
37
  autoPageview: true,
36
- heatmap: true
38
+ heatmap: true,
39
+ consentRequired: true,
40
+ showConsentBanner: true
37
41
  });
38
42
 
39
43
  krypton.track("page_loaded");
@@ -43,6 +47,35 @@ Use jsDelivr:
43
47
 
44
48
  The CDN build exposes a global: `window.KryptonAnalytics`.
45
49
 
50
+ ## Consent (default deny + categories)
51
+
52
+ The SDK supports category-level consent:
53
+
54
+ - `analytics`
55
+ - `heatmaps`
56
+ - `geo` (browser geolocation, if granted by user)
57
+
58
+ Example:
59
+
60
+ ```ts
61
+ import { Krypton } from "@kryptonhq/analytics";
62
+
63
+ const krypton = new Krypton({
64
+ apiKey: "kapi_xxx",
65
+ endpoint: "https://ingest.yourdomain.com",
66
+ heatmap: true,
67
+ consentRequired: true, // default deny
68
+ showConsentBanner: true, // built-in popup
69
+ });
70
+
71
+ // Optional manual control
72
+ krypton.setConsent({
73
+ analytics: true,
74
+ heatmaps: false,
75
+ geo: false,
76
+ });
77
+ ```
78
+
46
79
  ### GA-style bootstrap snippet (single paste)
47
80
 
48
81
  ```html
@@ -1 +1,21 @@
1
- "use strict";var KryptonAnalytics=(()=>{var Z=Object.defineProperty;var ke=Object.getOwnPropertyDescriptor;var Se=Object.getOwnPropertyNames;var be=Object.prototype.hasOwnProperty;var Ce=(e,t)=>{for(var r in t)Z(e,r,{get:t[r],enumerable:!0})},Ie=(e,t,r,i)=>{if(t&&typeof t=="object"||typeof t=="function")for(let n of Se(t))!be.call(e,n)&&n!==r&&Z(e,n,{get:()=>t[n],enumerable:!(i=ke(t,n))||i.enumerable});return e};var Te=e=>Ie(Z({},"__esModule",{value:!0}),e);var at={};Ce(at,{Krypton:()=>K,default:()=>nt,init:()=>ye});var g;(function(e){e[e.Document=0]="Document",e[e.DocumentType=1]="DocumentType",e[e.Element=2]="Element",e[e.Text=3]="Text",e[e.CDATA=4]="CDATA",e[e.Comment=5]="Comment"})(g||(g={}));function xe(e){return e.nodeType===e.ELEMENT_NODE}function Ee(e){var t=e?.host;return t?.shadowRoot===e}function ee(e){return Object.prototype.toString.call(e)==="[object ShadowRoot]"}function Le(e){return e.includes(" background-clip: text;")&&!e.includes(" -webkit-background-clip: text;")&&(e=e.replace(" background-clip: text;"," -webkit-background-clip: text; background-clip: text;")),e}function te(e){try{var t=e.rules||e.cssRules;return t?Le(Array.from(t).map(Ne).join("")):null}catch{return null}}function Ne(e){var t=e.cssText;if(_e(e))try{t=te(e.styleSheet)||t}catch{}return t}function _e(e){return"styleSheet"in e}var Re=(function(){function e(){this.idNodeMap=new Map,this.nodeMetaMap=new WeakMap}return e.prototype.getId=function(t){var r;if(!t)return-1;var i=(r=this.getMeta(t))===null||r===void 0?void 0:r.id;return i??-1},e.prototype.getNode=function(t){return this.idNodeMap.get(t)||null},e.prototype.getIds=function(){return Array.from(this.idNodeMap.keys())},e.prototype.getMeta=function(t){return this.nodeMetaMap.get(t)||null},e.prototype.removeNodeFromMap=function(t){var r=this,i=this.getId(t);this.idNodeMap.delete(i),t.childNodes&&t.childNodes.forEach(function(n){return r.removeNodeFromMap(n)})},e.prototype.has=function(t){return this.idNodeMap.has(t)},e.prototype.hasNode=function(t){return this.nodeMetaMap.has(t)},e.prototype.add=function(t,r){var i=r.id;this.idNodeMap.set(i,t),this.nodeMetaMap.set(t,r)},e.prototype.replace=function(t,r){var i=this.getNode(t);if(i){var n=this.nodeMetaMap.get(i);n&&this.nodeMetaMap.set(r,n)}this.idNodeMap.set(t,r)},e.prototype.reset=function(){this.idNodeMap=new Map,this.nodeMetaMap=new WeakMap},e})();function Oe(e){var t=e.maskInputOptions,r=e.tagName,i=e.type,n=e.value,a=e.maskInputFn,c=n||"";return(t[r.toLowerCase()]||t[i])&&(a?c=a(c):c="*".repeat(c.length)),c}var ue="__rrweb_original__";function Me(e){var t=e.getContext("2d");if(!t)return!0;for(var r=50,i=0;i<e.width;i+=r)for(var n=0;n<e.height;n+=r){var a=t.getImageData,c=ue in a?a[ue]:a,l=new Uint32Array(c.call(t,i,n,Math.min(r,e.width-i),Math.min(r,e.height-n)).data.buffer);if(l.some(function(o){return o!==0}))return!1}return!0}var De=1,Ae=new RegExp("[^a-z0-9-_:]"),le=-2;function Fe(){return De++}function Pe(e){if(e instanceof HTMLFormElement)return"form";var t=e.tagName.toLowerCase().trim();return Ae.test(t)?"div":t}function Ue(e){return e.cssRules?Array.from(e.cssRules).map(function(t){return t.cssText||""}).join(""):""}function He(e){var t="";return e.indexOf("//")>-1?t=e.split("/").slice(0,3).join("/"):t=e.split("/")[0],t=t.split("?")[0],t}var z,fe,We=/url\((?:(')([^']*)'|(")(.*?)"|([^)]*))\)/gm,Ge=/^(?!www\.|(?:http|ftp)s?:\/\/|[A-Za-z]:\\|\/\/|#).*/,ze=/^(data:)([^,]*),(.*)/i;function $(e,t){return(e||"").replace(We,function(r,i,n,a,c,l){var o=n||c||l,f=i||a||"";if(!o)return r;if(!Ge.test(o)||ze.test(o))return"url(".concat(f).concat(o).concat(f,")");if(o[0]==="/")return"url(".concat(f).concat(He(t)+o).concat(f,")");var u=t.split("/"),p=o.split("/");u.pop();for(var b=0,w=p;b<w.length;b++){var k=w[b];k!=="."&&(k===".."?u.pop():u.push(k))}return"url(".concat(f).concat(u.join("/")).concat(f,")")})}var je=/^[^ \t\n\r\u000c]+/,Ke=/^[, \t\n\r\u000c]+/;function Be(e,t){if(t.trim()==="")return t;var r=0;function i(f){var u,p=f.exec(t.substring(r));return p?(u=p[0],r+=u.length,u):""}for(var n=[];i(Ke),!(r>=t.length);){var a=i(je);if(a.slice(-1)===",")a=j(e,a.substring(0,a.length-1)),n.push(a);else{var c="";a=j(e,a);for(var l=!1;;){var o=t.charAt(r);if(o===""){n.push((a+c).trim());break}else if(l)o===")"&&(l=!1);else if(o===","){r+=1,n.push((a+c).trim());break}else o==="("&&(l=!0);c+=o,r+=1}}}return n.join(", ")}function j(e,t){if(!t||t.trim()==="")return t;var r=e.createElement("a");return r.href=t,r.href}function Qe(e){return!!(e.tagName==="svg"||e.ownerSVGElement)}function ie(){var e=document.createElement("a");return e.href="",e.href}function $e(e,t,r,i){return r==="src"||r==="href"&&i&&!(t==="use"&&i[0]==="#")||r==="xlink:href"&&i&&i[0]!=="#"||r==="background"&&i&&(t==="table"||t==="td"||t==="th")?j(e,i):r==="srcset"&&i?Be(e,i):r==="style"&&i?$(i,ie()):t==="object"&&r==="data"&&i?j(e,i):i}function qe(e,t,r){if(typeof t=="string"){if(e.classList.contains(t))return!0}else for(var i=e.classList.length;i--;){var n=e.classList[i];if(t.test(n))return!0}return r?e.matches(r):!1}function re(e,t,r){if(!e)return!1;if(e.nodeType!==e.ELEMENT_NODE)return r?re(e.parentNode,t,r):!1;for(var i=e.classList.length;i--;){var n=e.classList[i];if(t.test(n))return!0}return r?re(e.parentNode,t,r):!1}function Je(e,t,r){var i=e.nodeType===e.ELEMENT_NODE?e:e.parentElement;if(i===null)return!1;if(typeof t=="string"){if(i.classList.contains(t)||i.closest(".".concat(t)))return!0}else if(re(i,t,!0))return!0;return!!(r&&(i.matches(r)||i.closest(r)))}function Ye(e,t,r){var i=e.contentWindow;if(i){var n=!1,a;try{a=i.document.readyState}catch{return}if(a!=="complete"){var c=setTimeout(function(){n||(t(),n=!0)},r);e.addEventListener("load",function(){clearTimeout(c),n=!0,t()});return}var l="about:blank";if(i.location.href!==l||e.src===l||e.src==="")return setTimeout(t,0),e.addEventListener("load",t);e.addEventListener("load",t)}}function Ve(e,t,r){var i=!1,n;try{n=e.sheet}catch{return}if(!n){var a=setTimeout(function(){i||(t(),i=!0)},r);e.addEventListener("load",function(){clearTimeout(a),i=!0,t()})}}function Xe(e,t){var r=t.doc,i=t.mirror,n=t.blockClass,a=t.blockSelector,c=t.maskTextClass,l=t.maskTextSelector,o=t.inlineStylesheet,f=t.maskInputOptions,u=f===void 0?{}:f,p=t.maskTextFn,b=t.maskInputFn,w=t.dataURLOptions,k=w===void 0?{}:w,I=t.inlineImages,T=t.recordCanvas,x=t.keepIframeSrcFn,h=t.newlyAddedElement,s=h===void 0?!1:h,y=Ze(r,i);switch(e.nodeType){case e.DOCUMENT_NODE:return e.compatMode!=="CSS1Compat"?{type:g.Document,childNodes:[],compatMode:e.compatMode}:{type:g.Document,childNodes:[]};case e.DOCUMENT_TYPE_NODE:return{type:g.DocumentType,name:e.name,publicId:e.publicId,systemId:e.systemId,rootId:y};case e.ELEMENT_NODE:return tt(e,{doc:r,blockClass:n,blockSelector:a,inlineStylesheet:o,maskInputOptions:u,maskInputFn:b,dataURLOptions:k,inlineImages:I,recordCanvas:T,keepIframeSrcFn:x,newlyAddedElement:s,rootId:y});case e.TEXT_NODE:return et(e,{maskTextClass:c,maskTextSelector:l,maskTextFn:p,rootId:y});case e.CDATA_SECTION_NODE:return{type:g.CDATA,textContent:"",rootId:y};case e.COMMENT_NODE:return{type:g.Comment,textContent:e.textContent||"",rootId:y};default:return!1}}function Ze(e,t){if(t.hasNode(e)){var r=t.getId(e);return r===1?void 0:r}}function et(e,t){var r,i=t.maskTextClass,n=t.maskTextSelector,a=t.maskTextFn,c=t.rootId,l=e.parentNode&&e.parentNode.tagName,o=e.textContent,f=l==="STYLE"?!0:void 0,u=l==="SCRIPT"?!0:void 0;if(f&&o){try{e.nextSibling||e.previousSibling||!((r=e.parentNode.sheet)===null||r===void 0)&&r.cssRules&&(o=Ue(e.parentNode.sheet))}catch(p){console.warn("Cannot get CSS styles from text's parentNode. Error: ".concat(p),e)}o=$(o,ie())}return u&&(o="SCRIPT_PLACEHOLDER"),!f&&!u&&o&&Je(e,i,n)&&(o=a?a(o):o.replace(/[\S]/g,"*")),{type:g.Text,textContent:o||"",isStyle:f,rootId:c}}function tt(e,t){for(var r=t.doc,i=t.blockClass,n=t.blockSelector,a=t.inlineStylesheet,c=t.maskInputOptions,l=c===void 0?{}:c,o=t.maskInputFn,f=t.dataURLOptions,u=f===void 0?{}:f,p=t.inlineImages,b=t.recordCanvas,w=t.keepIframeSrcFn,k=t.newlyAddedElement,I=k===void 0?!1:k,T=t.rootId,x=qe(e,i,n),h=Pe(e),s={},y=e.attributes.length,M=0;M<y;M++){var E=e.attributes[M];s[E.name]=$e(r,h,E.name,E.value)}if(h==="link"&&a){var C=Array.from(r.styleSheets).find(function(N){return N.href===e.href}),m=null;C&&(m=te(C)),m&&(delete s.rel,delete s.href,s._cssText=$(m,C.href))}if(h==="style"&&e.sheet&&!(e.innerText||e.textContent||"").trim().length){var m=te(e.sheet);m&&(s._cssText=$(m,ie()))}if(h==="input"||h==="textarea"||h==="select"){var P=e.value,_=e.checked;s.type!=="radio"&&s.type!=="checkbox"&&s.type!=="submit"&&s.type!=="button"&&P?s.value=Oe({type:s.type,tagName:h,value:P,maskInputOptions:l,maskInputFn:o}):_&&(s.checked=_)}if(h==="option"&&(e.selected&&!l.select?s.selected=!0:delete s.selected),h==="canvas"&&b){if(e.__context==="2d")Me(e)||(s.rr_dataURL=e.toDataURL(u.type,u.quality));else if(!("__context"in e)){var L=e.toDataURL(u.type,u.quality),D=document.createElement("canvas");D.width=e.width,D.height=e.height;var A=D.toDataURL(u.type,u.quality);L!==A&&(s.rr_dataURL=L)}}if(h==="img"&&p){z||(z=r.createElement("canvas"),fe=z.getContext("2d"));var S=e,R=S.crossOrigin;S.crossOrigin="anonymous";var F=function(){try{z.width=S.naturalWidth,z.height=S.naturalHeight,fe.drawImage(S,0,0),s.rr_dataURL=z.toDataURL(u.type,u.quality)}catch(N){console.warn("Cannot inline img src=".concat(S.currentSrc,"! Error: ").concat(N))}R?s.crossOrigin=R:S.removeAttribute("crossorigin")};S.complete&&S.naturalWidth!==0?F():S.onload=F}if((h==="audio"||h==="video")&&(s.rr_mediaState=e.paused?"paused":"played",s.rr_mediaCurrentTime=e.currentTime),I||(e.scrollLeft&&(s.rr_scrollLeft=e.scrollLeft),e.scrollTop&&(s.rr_scrollTop=e.scrollTop)),x){var U=e.getBoundingClientRect(),H=U.width,O=U.height;s={class:s.class,rr_width:"".concat(H,"px"),rr_height:"".concat(O,"px")}}return h==="iframe"&&!w(s.src)&&(e.contentDocument||(s.rr_src=s.src),delete s.src),{type:g.Element,tagName:h,attributes:s,childNodes:[],isSVG:Qe(e)||void 0,needBlock:x,rootId:T}}function d(e){return e===void 0?"":e.toLowerCase()}function rt(e,t){if(t.comment&&e.type===g.Comment)return!0;if(e.type===g.Element){if(t.script&&(e.tagName==="script"||e.tagName==="link"&&e.attributes.rel==="preload"&&e.attributes.as==="script"||e.tagName==="link"&&e.attributes.rel==="prefetch"&&typeof e.attributes.href=="string"&&e.attributes.href.endsWith(".js")))return!0;if(t.headFavicon&&(e.tagName==="link"&&e.attributes.rel==="shortcut icon"||e.tagName==="meta"&&(d(e.attributes.name).match(/^msapplication-tile(image|color)$/)||d(e.attributes.name)==="application-name"||d(e.attributes.rel)==="icon"||d(e.attributes.rel)==="apple-touch-icon"||d(e.attributes.rel)==="shortcut icon")))return!0;if(e.tagName==="meta"){if(t.headMetaDescKeywords&&d(e.attributes.name).match(/^description|keywords$/))return!0;if(t.headMetaSocial&&(d(e.attributes.property).match(/^(og|twitter|fb):/)||d(e.attributes.name).match(/^(og|twitter):/)||d(e.attributes.name)==="pinterest"))return!0;if(t.headMetaRobots&&(d(e.attributes.name)==="robots"||d(e.attributes.name)==="googlebot"||d(e.attributes.name)==="bingbot"))return!0;if(t.headMetaHttpEquiv&&e.attributes["http-equiv"]!==void 0)return!0;if(t.headMetaAuthorship&&(d(e.attributes.name)==="author"||d(e.attributes.name)==="generator"||d(e.attributes.name)==="framework"||d(e.attributes.name)==="publisher"||d(e.attributes.name)==="progid"||d(e.attributes.property).match(/^article:/)||d(e.attributes.property).match(/^product:/)))return!0;if(t.headMetaVerification&&(d(e.attributes.name)==="google-site-verification"||d(e.attributes.name)==="yandex-verification"||d(e.attributes.name)==="csrf-token"||d(e.attributes.name)==="p:domain_verify"||d(e.attributes.name)==="verify-v1"||d(e.attributes.name)==="verification"||d(e.attributes.name)==="shopify-checkout-api-token"))return!0}}return!1}function Q(e,t){var r=t.doc,i=t.mirror,n=t.blockClass,a=t.blockSelector,c=t.maskTextClass,l=t.maskTextSelector,o=t.skipChild,f=o===void 0?!1:o,u=t.inlineStylesheet,p=u===void 0?!0:u,b=t.maskInputOptions,w=b===void 0?{}:b,k=t.maskTextFn,I=t.maskInputFn,T=t.slimDOMOptions,x=t.dataURLOptions,h=x===void 0?{}:x,s=t.inlineImages,y=s===void 0?!1:s,M=t.recordCanvas,E=M===void 0?!1:M,C=t.onSerialize,m=t.onIframeLoad,P=t.iframeLoadTimeout,_=P===void 0?5e3:P,L=t.onStylesheetLoad,D=t.stylesheetLoadTimeout,A=D===void 0?5e3:D,S=t.keepIframeSrcFn,R=S===void 0?function(){return!1}:S,F=t.newlyAddedElement,U=F===void 0?!1:F,H=t.preserveWhiteSpace,O=H===void 0?!0:H,N=Xe(e,{doc:r,mirror:i,blockClass:n,blockSelector:a,maskTextClass:c,maskTextSelector:l,inlineStylesheet:p,maskInputOptions:w,maskTextFn:k,maskInputFn:I,dataURLOptions:h,inlineImages:y,recordCanvas:E,keepIframeSrcFn:R,newlyAddedElement:U});if(!N)return console.warn(e,"not serialized"),null;var B;i.hasNode(e)?B=i.getId(e):rt(N,T)||!O&&N.type===g.Text&&!N.isStyle&&!N.textContent.replace(/^\s+|\s+$/gm,"").length?B=le:B=Fe();var v=Object.assign(N,{id:B});if(i.add(e,v),B===le)return null;C&&C(e);var J=!f;if(v.type===g.Element){J=J&&!v.needBlock,delete v.needBlock;var ne=e.shadowRoot;ne&&ee(ne)&&(v.isShadowHost=!0)}if((v.type===g.Document||v.type===g.Element)&&J){T.headWhitespace&&v.type===g.Element&&v.tagName==="head"&&(O=!1);for(var ae={doc:r,mirror:i,blockClass:n,blockSelector:a,maskTextClass:c,maskTextSelector:l,skipChild:f,inlineStylesheet:p,maskInputOptions:w,maskTextFn:k,maskInputFn:I,slimDOMOptions:T,dataURLOptions:h,inlineImages:y,recordCanvas:E,preserveWhiteSpace:O,onSerialize:C,onIframeLoad:m,iframeLoadTimeout:_,onStylesheetLoad:L,stylesheetLoadTimeout:A,keepIframeSrcFn:R},Y=0,oe=Array.from(e.childNodes);Y<oe.length;Y++){var V=oe[Y],W=Q(V,ae);W&&v.childNodes.push(W)}if(xe(e)&&e.shadowRoot)for(var X=0,se=Array.from(e.shadowRoot.childNodes);X<se.length;X++){var V=se[X],W=Q(V,ae);W&&(ee(e.shadowRoot)&&(W.isShadow=!0),v.childNodes.push(W))}}return e.parentNode&&Ee(e.parentNode)&&ee(e.parentNode)&&(v.isShadow=!0),v.type===g.Element&&v.tagName==="iframe"&&Ye(e,function(){var G=e.contentDocument;if(G&&m){var ce=Q(G,{doc:G,mirror:i,blockClass:n,blockSelector:a,maskTextClass:c,maskTextSelector:l,skipChild:!1,inlineStylesheet:p,maskInputOptions:w,maskTextFn:k,maskInputFn:I,slimDOMOptions:T,dataURLOptions:h,inlineImages:y,recordCanvas:E,preserveWhiteSpace:O,onSerialize:C,onIframeLoad:m,iframeLoadTimeout:_,onStylesheetLoad:L,stylesheetLoadTimeout:A,keepIframeSrcFn:R});ce&&m(e,ce)}},_),v.type===g.Element&&v.tagName==="link"&&v.attributes.rel==="stylesheet"&&Ve(e,function(){if(L){var G=Q(e,{doc:r,mirror:i,blockClass:n,blockSelector:a,maskTextClass:c,maskTextSelector:l,skipChild:!1,inlineStylesheet:p,maskInputOptions:w,maskTextFn:k,maskInputFn:I,slimDOMOptions:T,dataURLOptions:h,inlineImages:y,recordCanvas:E,preserveWhiteSpace:O,onSerialize:C,onIframeLoad:m,iframeLoadTimeout:_,onStylesheetLoad:L,stylesheetLoadTimeout:A,keepIframeSrcFn:R});G&&L(e,G)}},A),v}function de(e,t){var r=t||{},i=r.mirror,n=i===void 0?new Re:i,a=r.blockClass,c=a===void 0?"rr-block":a,l=r.blockSelector,o=l===void 0?null:l,f=r.maskTextClass,u=f===void 0?"rr-mask":f,p=r.maskTextSelector,b=p===void 0?null:p,w=r.inlineStylesheet,k=w===void 0?!0:w,I=r.inlineImages,T=I===void 0?!1:I,x=r.recordCanvas,h=x===void 0?!1:x,s=r.maskAllInputs,y=s===void 0?!1:s,M=r.maskTextFn,E=r.maskInputFn,C=r.slimDOM,m=C===void 0?!1:C,P=r.dataURLOptions,_=r.preserveWhiteSpace,L=r.onSerialize,D=r.onIframeLoad,A=r.iframeLoadTimeout,S=r.onStylesheetLoad,R=r.stylesheetLoadTimeout,F=r.keepIframeSrcFn,U=F===void 0?function(){return!1}:F,H=y===!0?{color:!0,date:!0,"datetime-local":!0,email:!0,month:!0,number:!0,range:!0,search:!0,tel:!0,text:!0,time:!0,url:!0,week:!0,textarea:!0,select:!0,password:!0}:y===!1?{password:!0}:y,O=m===!0||m==="all"?{script:!0,comment:!0,headFavicon:!0,headWhitespace:!0,headMetaDescKeywords:m==="all",headMetaSocial:!0,headMetaRobots:!0,headMetaHttpEquiv:!0,headMetaAuthorship:!0,headMetaVerification:!0}:m===!1?{}:m;return Q(e,{doc:e,mirror:n,blockClass:c,blockSelector:o,maskTextClass:u,maskTextSelector:b,skipChild:!1,inlineStylesheet:k,maskInputOptions:H,maskTextFn:M,maskInputFn:E,slimDOMOptions:O,dataURLOptions:P,inlineImages:T,recordCanvas:h,preserveWhiteSpace:_,onSerialize:L,onIframeLoad:D,iframeLoadTimeout:A,onStylesheetLoad:S,stylesheetLoadTimeout:R,keepIframeSrcFn:U,newlyAddedElement:!1})}var it=/([^\\]):hover/,st=new RegExp(it.source,"g");function q(){return typeof crypto<"u"&&crypto.randomUUID?crypto.randomUUID():"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,e=>{let t=Math.random()*16|0;return(e==="x"?t:t&3|8).toString(16)})}function he(){let e="_krypton_sid";if(typeof sessionStorage<"u"){let t=sessionStorage.getItem(e);return t||(t=q(),sessionStorage.setItem(e,t)),t}return q()}function pe(){let e="_krypton_did";if(typeof localStorage<"u"){let t=localStorage.getItem(e);return t||(t=q(),localStorage.setItem(e,t)),t}return q()}function me(){if(typeof window>"u")return{};let e=new URLSearchParams(window.location.search),t={};for(let r of["utm_source","utm_medium","utm_campaign","utm_term","utm_content"]){let i=e.get(r);i&&(t[r]=i)}return t}function ve(){if(typeof window>"u")return"unknown";let e=window.innerWidth;return e<768?"mobile":e<1024?"tablet":"desktop"}function ge(e){if(e.id)return`#${e.id}`;let t=[],r=e;for(;r&&r!==document.body;){let i=r.tagName.toLowerCase();if(r.id){t.unshift(`#${r.id}`);break}if(r.className&&typeof r.className=="string"){let n=r.className.trim().split(/\s+/).slice(0,2).join(".");n&&(i+=`.${n}`)}t.unshift(i),r=r.parentElement}return t.join(" > ")}var K=class{constructor(t){this.eventQueue=[];this.heatmapQueue=[];this.flushTimer=null;this.consentGiven=!1;this.initialized=!1;this.lastSnapshotUrl=null;this.serverConfig=null;this.configFetched=!1;this.config={autoPageview:!0,heatmap:!1,consentRequired:!1,flushInterval:5e3,batchSize:20,...t},this.distinctId=pe(),this.sessionId=he(),this.config.consentRequired||(this.consentGiven=!0),this.init()}init(){this.initialized||(this.initialized=!0,this.fetchConfig().then(()=>this.setupTracking()))}async fetchConfig(){if(typeof window>"u")return;let t=`_krypton_config_${this.config.apiKey}`,r=sessionStorage.getItem(t);if(r)try{let i=JSON.parse(r);if(i._ts&&Date.now()-i._ts<300*1e3){this.serverConfig=i,this.configFetched=!0;return}}catch{}try{let i=await fetch(`${this.config.endpoint}/api/v1/config?api_key=${encodeURIComponent(this.config.apiKey)}`);if(i.ok){let n=await i.json();this.serverConfig=n,this.configFetched=!0,sessionStorage.setItem(t,JSON.stringify({...n,_ts:Date.now()}))}}catch{}}setupTracking(){if(this.flushTimer=setInterval(()=>this.flush(),this.config.flushInterval),typeof window<"u"){window.addEventListener("beforeunload",()=>this.flush()),this.config.autoPageview&&this.consentGiven&&this.trackPageview();let t=this.config.heatmap&&(!this.configFetched||this.serverConfig?.heatmaps_enabled),r=history.pushState;history.pushState=(...i)=>{r.apply(history,i),this.config.autoPageview&&this.consentGiven&&setTimeout(()=>this.trackPageview(),0),t&&this.consentGiven&&setTimeout(()=>this.captureSnapshot(),0)},window.addEventListener("popstate",()=>{this.config.autoPageview&&this.consentGiven&&setTimeout(()=>this.trackPageview(),0),t&&this.consentGiven&&setTimeout(()=>this.captureSnapshot(),0)}),t&&this.consentGiven&&this.setupHeatmap()}}grantConsent(){this.consentGiven=!0,this.config.autoPageview&&this.trackPageview(),this.config.heatmap&&(!this.configFetched||this.serverConfig?.heatmaps_enabled)&&this.setupHeatmap()}revokeConsent(){this.consentGiven=!1,this.eventQueue=[],this.heatmapQueue=[]}identify(t){this.distinctId=t,typeof localStorage<"u"&&localStorage.setItem("_krypton_did",t)}trackPageview(t){this.consentGiven&&this.track("$pageview",{...t})}track(t,r){if(!this.consentGiven)return;let i=me(),n={project_id:this.config.apiKey,distinct_id:this.distinctId,event_name:t,timestamp:new Date().toISOString(),properties:r,page_url:typeof window<"u"?window.location.href:"",page_title:typeof document<"u"?document.title:"",referrer:typeof document<"u"?document.referrer:"",utm_source:i.utm_source||"",utm_medium:i.utm_medium||"",utm_campaign:i.utm_campaign||"",utm_term:i.utm_term||"",utm_content:i.utm_content||"",device_type:ve(),browser:this.getBrowser(),screen_width:typeof window<"u"?window.screen.width:0,screen_height:typeof window<"u"?window.screen.height:0,session_id:this.sessionId};this.eventQueue.push(n),this.eventQueue.length>=this.config.batchSize&&this.flush()}async flush(){await Promise.all([this.flushEvents(),this.flushHeatmapEvents()])}shutdown(){this.flushTimer&&(clearInterval(this.flushTimer),this.flushTimer=null),this.flush()}async flushEvents(){if(this.eventQueue.length===0)return;let t=this.eventQueue.splice(0,this.eventQueue.length),r={api_key:this.config.apiKey,events:t};try{await fetch(`${this.config.endpoint}/api/v1/ingest/batch`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(r),keepalive:!0})}catch{this.eventQueue.length<this.config.batchSize*5&&this.eventQueue.unshift(...t)}}async flushHeatmapEvents(){if(this.heatmapQueue.length===0)return;let t=this.heatmapQueue.splice(0,this.heatmapQueue.length),r={api_key:this.config.apiKey,events:t};try{await fetch(`${this.config.endpoint}/api/v1/heatmap`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(r),keepalive:!0})}catch{this.heatmapQueue.length<this.config.batchSize*5&&this.heatmapQueue.unshift(...t)}}captureSnapshot(){if(typeof window>"u"||typeof document>"u")return;let t=window.location.href;this.lastSnapshotUrl!==t&&(this.lastSnapshotUrl=t,setTimeout(()=>{try{let r=de(document,{maskAllInputs:!0,blockSelector:"[data-krypton-block]",inlineStylesheet:!0});if(!r)return;let i=JSON.stringify(r),n={api_key:this.config.apiKey,page_url:t,snapshot:i,viewport_width:window.innerWidth,viewport_height:window.innerHeight,page_width:document.documentElement.scrollWidth,page_height:document.documentElement.scrollHeight,timestamp:new Date().toISOString()};fetch(`${this.config.endpoint}/api/v1/snapshot`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(n),keepalive:!0}).catch(()=>{})}catch{}},1e3))}setupHeatmap(){if(typeof window>"u")return;this.captureSnapshot(),document.addEventListener("click",i=>{if(!this.consentGiven)return;let n=i.target;this.heatmapQueue.push({project_id:this.config.apiKey,distinct_id:this.distinctId,session_id:this.sessionId,timestamp:new Date().toISOString(),page_url:window.location.href,interaction_type:"click",x:i.clientX+window.scrollX,y:i.clientY+window.scrollY,viewport_width:window.innerWidth,viewport_height:window.innerHeight,page_width:document.documentElement.scrollWidth,page_height:document.documentElement.scrollHeight,selector:ge(n)})});let t=0,r=null;window.addEventListener("scroll",()=>{if(!this.consentGiven)return;let i=window.scrollY||document.documentElement.scrollTop,n=document.documentElement.scrollHeight-window.innerHeight,a=n>0?i/n*100:0;a>t&&(t=a),r&&clearTimeout(r),r=setTimeout(()=>{this.heatmapQueue.push({project_id:this.config.apiKey,distinct_id:this.distinctId,session_id:this.sessionId,timestamp:new Date().toISOString(),page_url:window.location.href,interaction_type:"scroll",scroll_depth:t,viewport_width:window.innerWidth,viewport_height:window.innerHeight,page_width:document.documentElement.scrollWidth,page_height:document.documentElement.scrollHeight})},500)})}getBrowser(){if(typeof navigator>"u")return"unknown";let t=navigator.userAgent;return t.includes("Firefox")?"Firefox":t.includes("Edg")?"Edge":t.includes("Chrome")?"Chrome":t.includes("Safari")?"Safari":"Other"}};function ye(e){return new K(e)}var we={Krypton:K,init:ye};typeof window<"u"&&(window.KryptonAnalytics=we);var nt=we;return Te(at);})();
1
+ "use strict";var KryptonAnalytics=(()=>{var Z=Object.defineProperty;var we=Object.getOwnPropertyDescriptor;var Se=Object.getOwnPropertyNames;var be=Object.prototype.hasOwnProperty;var Ce=(e,t)=>{for(var r in t)Z(e,r,{get:t[r],enumerable:!0})},xe=(e,t,r,i)=>{if(t&&typeof t=="object"||typeof t=="function")for(let n of Se(t))!be.call(e,n)&&n!==r&&Z(e,n,{get:()=>t[n],enumerable:!(i=we(t,n))||i.enumerable});return e};var Ie=e=>xe(Z({},"__esModule",{value:!0}),e);var at={};Ce(at,{Krypton:()=>q,default:()=>nt,init:()=>ye});var v;(function(e){e[e.Document=0]="Document",e[e.DocumentType=1]="DocumentType",e[e.Element=2]="Element",e[e.Text=3]="Text",e[e.CDATA=4]="CDATA",e[e.Comment=5]="Comment"})(v||(v={}));function Te(e){return e.nodeType===e.ELEMENT_NODE}function Ee(e){var t=e?.host;return t?.shadowRoot===e}function ee(e){return Object.prototype.toString.call(e)==="[object ShadowRoot]"}function Le(e){return e.includes(" background-clip: text;")&&!e.includes(" -webkit-background-clip: text;")&&(e=e.replace(" background-clip: text;"," -webkit-background-clip: text; background-clip: text;")),e}function te(e){try{var t=e.rules||e.cssRules;return t?Le(Array.from(t).map(Re).join("")):null}catch{return null}}function Re(e){var t=e.cssText;if(_e(e))try{t=te(e.styleSheet)||t}catch{}return t}function _e(e){return"styleSheet"in e}var Ne=(function(){function e(){this.idNodeMap=new Map,this.nodeMetaMap=new WeakMap}return e.prototype.getId=function(t){var r;if(!t)return-1;var i=(r=this.getMeta(t))===null||r===void 0?void 0:r.id;return i??-1},e.prototype.getNode=function(t){return this.idNodeMap.get(t)||null},e.prototype.getIds=function(){return Array.from(this.idNodeMap.keys())},e.prototype.getMeta=function(t){return this.nodeMetaMap.get(t)||null},e.prototype.removeNodeFromMap=function(t){var r=this,i=this.getId(t);this.idNodeMap.delete(i),t.childNodes&&t.childNodes.forEach(function(n){return r.removeNodeFromMap(n)})},e.prototype.has=function(t){return this.idNodeMap.has(t)},e.prototype.hasNode=function(t){return this.nodeMetaMap.has(t)},e.prototype.add=function(t,r){var i=r.id;this.idNodeMap.set(i,t),this.nodeMetaMap.set(t,r)},e.prototype.replace=function(t,r){var i=this.getNode(t);if(i){var n=this.nodeMetaMap.get(i);n&&this.nodeMetaMap.set(r,n)}this.idNodeMap.set(t,r)},e.prototype.reset=function(){this.idNodeMap=new Map,this.nodeMetaMap=new WeakMap},e})();function Oe(e){var t=e.maskInputOptions,r=e.tagName,i=e.type,n=e.value,a=e.maskInputFn,c=n||"";return(t[r.toLowerCase()]||t[i])&&(a?c=a(c):c="*".repeat(c.length)),c}var le="__rrweb_original__";function Me(e){var t=e.getContext("2d");if(!t)return!0;for(var r=50,i=0;i<e.width;i+=r)for(var n=0;n<e.height;n+=r){var a=t.getImageData,c=le in a?a[le]:a,u=new Uint32Array(c.call(t,i,n,Math.min(r,e.width-i),Math.min(r,e.height-n)).data.buffer);if(u.some(function(o){return o!==0}))return!1}return!0}var Ae=1,De=new RegExp("[^a-z0-9-_:]"),ue=-2;function Fe(){return Ae++}function Pe(e){if(e instanceof HTMLFormElement)return"form";var t=e.tagName.toLowerCase().trim();return De.test(t)?"div":t}function He(e){return e.cssRules?Array.from(e.cssRules).map(function(t){return t.cssText||""}).join(""):""}function Ue(e){var t="";return e.indexOf("//")>-1?t=e.split("/").slice(0,3).join("/"):t=e.split("/")[0],t=t.split("?")[0],t}var z,fe,We=/url\((?:(')([^']*)'|(")(.*?)"|([^)]*))\)/gm,Ke=/^(?!www\.|(?:http|ftp)s?:\/\/|[A-Za-z]:\\|\/\/|#).*/,Be=/^(data:)([^,]*),(.*)/i;function $(e,t){return(e||"").replace(We,function(r,i,n,a,c,u){var o=n||c||u,f=i||a||"";if(!o)return r;if(!Ke.test(o)||Be.test(o))return"url(".concat(f).concat(o).concat(f,")");if(o[0]==="/")return"url(".concat(f).concat(Ue(t)+o).concat(f,")");var l=t.split("/"),h=o.split("/");l.pop();for(var b=0,k=h;b<k.length;b++){var w=k[b];w!=="."&&(w===".."?l.pop():l.push(w))}return"url(".concat(f).concat(l.join("/")).concat(f,")")})}var ze=/^[^ \t\n\r\u000c]+/,je=/^[, \t\n\r\u000c]+/;function qe(e,t){if(t.trim()==="")return t;var r=0;function i(f){var l,h=f.exec(t.substring(r));return h?(l=h[0],r+=l.length,l):""}for(var n=[];i(je),!(r>=t.length);){var a=i(ze);if(a.slice(-1)===",")a=j(e,a.substring(0,a.length-1)),n.push(a);else{var c="";a=j(e,a);for(var u=!1;;){var o=t.charAt(r);if(o===""){n.push((a+c).trim());break}else if(u)o===")"&&(u=!1);else if(o===","){r+=1,n.push((a+c).trim());break}else o==="("&&(u=!0);c+=o,r+=1}}}return n.join(", ")}function j(e,t){if(!t||t.trim()==="")return t;var r=e.createElement("a");return r.href=t,r.href}function Ge(e){return!!(e.tagName==="svg"||e.ownerSVGElement)}function ie(){var e=document.createElement("a");return e.href="",e.href}function Qe(e,t,r,i){return r==="src"||r==="href"&&i&&!(t==="use"&&i[0]==="#")||r==="xlink:href"&&i&&i[0]!=="#"||r==="background"&&i&&(t==="table"||t==="td"||t==="th")?j(e,i):r==="srcset"&&i?qe(e,i):r==="style"&&i?$(i,ie()):t==="object"&&r==="data"&&i?j(e,i):i}function $e(e,t,r){if(typeof t=="string"){if(e.classList.contains(t))return!0}else for(var i=e.classList.length;i--;){var n=e.classList[i];if(t.test(n))return!0}return r?e.matches(r):!1}function re(e,t,r){if(!e)return!1;if(e.nodeType!==e.ELEMENT_NODE)return r?re(e.parentNode,t,r):!1;for(var i=e.classList.length;i--;){var n=e.classList[i];if(t.test(n))return!0}return r?re(e.parentNode,t,r):!1}function Je(e,t,r){var i=e.nodeType===e.ELEMENT_NODE?e:e.parentElement;if(i===null)return!1;if(typeof t=="string"){if(i.classList.contains(t)||i.closest(".".concat(t)))return!0}else if(re(i,t,!0))return!0;return!!(r&&(i.matches(r)||i.closest(r)))}function Ve(e,t,r){var i=e.contentWindow;if(i){var n=!1,a;try{a=i.document.readyState}catch{return}if(a!=="complete"){var c=setTimeout(function(){n||(t(),n=!0)},r);e.addEventListener("load",function(){clearTimeout(c),n=!0,t()});return}var u="about:blank";if(i.location.href!==u||e.src===u||e.src==="")return setTimeout(t,0),e.addEventListener("load",t);e.addEventListener("load",t)}}function Ye(e,t,r){var i=!1,n;try{n=e.sheet}catch{return}if(!n){var a=setTimeout(function(){i||(t(),i=!0)},r);e.addEventListener("load",function(){clearTimeout(a),i=!0,t()})}}function Xe(e,t){var r=t.doc,i=t.mirror,n=t.blockClass,a=t.blockSelector,c=t.maskTextClass,u=t.maskTextSelector,o=t.inlineStylesheet,f=t.maskInputOptions,l=f===void 0?{}:f,h=t.maskTextFn,b=t.maskInputFn,k=t.dataURLOptions,w=k===void 0?{}:k,x=t.inlineImages,I=t.recordCanvas,T=t.keepIframeSrcFn,p=t.newlyAddedElement,s=p===void 0?!1:p,y=Ze(r,i);switch(e.nodeType){case e.DOCUMENT_NODE:return e.compatMode!=="CSS1Compat"?{type:v.Document,childNodes:[],compatMode:e.compatMode}:{type:v.Document,childNodes:[]};case e.DOCUMENT_TYPE_NODE:return{type:v.DocumentType,name:e.name,publicId:e.publicId,systemId:e.systemId,rootId:y};case e.ELEMENT_NODE:return tt(e,{doc:r,blockClass:n,blockSelector:a,inlineStylesheet:o,maskInputOptions:l,maskInputFn:b,dataURLOptions:w,inlineImages:x,recordCanvas:I,keepIframeSrcFn:T,newlyAddedElement:s,rootId:y});case e.TEXT_NODE:return et(e,{maskTextClass:c,maskTextSelector:u,maskTextFn:h,rootId:y});case e.CDATA_SECTION_NODE:return{type:v.CDATA,textContent:"",rootId:y};case e.COMMENT_NODE:return{type:v.Comment,textContent:e.textContent||"",rootId:y};default:return!1}}function Ze(e,t){if(t.hasNode(e)){var r=t.getId(e);return r===1?void 0:r}}function et(e,t){var r,i=t.maskTextClass,n=t.maskTextSelector,a=t.maskTextFn,c=t.rootId,u=e.parentNode&&e.parentNode.tagName,o=e.textContent,f=u==="STYLE"?!0:void 0,l=u==="SCRIPT"?!0:void 0;if(f&&o){try{e.nextSibling||e.previousSibling||!((r=e.parentNode.sheet)===null||r===void 0)&&r.cssRules&&(o=He(e.parentNode.sheet))}catch(h){console.warn("Cannot get CSS styles from text's parentNode. Error: ".concat(h),e)}o=$(o,ie())}return l&&(o="SCRIPT_PLACEHOLDER"),!f&&!l&&o&&Je(e,i,n)&&(o=a?a(o):o.replace(/[\S]/g,"*")),{type:v.Text,textContent:o||"",isStyle:f,rootId:c}}function tt(e,t){for(var r=t.doc,i=t.blockClass,n=t.blockSelector,a=t.inlineStylesheet,c=t.maskInputOptions,u=c===void 0?{}:c,o=t.maskInputFn,f=t.dataURLOptions,l=f===void 0?{}:f,h=t.inlineImages,b=t.recordCanvas,k=t.keepIframeSrcFn,w=t.newlyAddedElement,x=w===void 0?!1:w,I=t.rootId,T=$e(e,i,n),p=Pe(e),s={},y=e.attributes.length,M=0;M<y;M++){var E=e.attributes[M];s[E.name]=Qe(r,p,E.name,E.value)}if(p==="link"&&a){var C=Array.from(r.styleSheets).find(function(R){return R.href===e.href}),m=null;C&&(m=te(C)),m&&(delete s.rel,delete s.href,s._cssText=$(m,C.href))}if(p==="style"&&e.sheet&&!(e.innerText||e.textContent||"").trim().length){var m=te(e.sheet);m&&(s._cssText=$(m,ie()))}if(p==="input"||p==="textarea"||p==="select"){var P=e.value,_=e.checked;s.type!=="radio"&&s.type!=="checkbox"&&s.type!=="submit"&&s.type!=="button"&&P?s.value=Oe({type:s.type,tagName:p,value:P,maskInputOptions:u,maskInputFn:o}):_&&(s.checked=_)}if(p==="option"&&(e.selected&&!u.select?s.selected=!0:delete s.selected),p==="canvas"&&b){if(e.__context==="2d")Me(e)||(s.rr_dataURL=e.toDataURL(l.type,l.quality));else if(!("__context"in e)){var L=e.toDataURL(l.type,l.quality),A=document.createElement("canvas");A.width=e.width,A.height=e.height;var D=A.toDataURL(l.type,l.quality);L!==D&&(s.rr_dataURL=L)}}if(p==="img"&&h){z||(z=r.createElement("canvas"),fe=z.getContext("2d"));var S=e,N=S.crossOrigin;S.crossOrigin="anonymous";var F=function(){try{z.width=S.naturalWidth,z.height=S.naturalHeight,fe.drawImage(S,0,0),s.rr_dataURL=z.toDataURL(l.type,l.quality)}catch(R){console.warn("Cannot inline img src=".concat(S.currentSrc,"! Error: ").concat(R))}N?s.crossOrigin=N:S.removeAttribute("crossorigin")};S.complete&&S.naturalWidth!==0?F():S.onload=F}if((p==="audio"||p==="video")&&(s.rr_mediaState=e.paused?"paused":"played",s.rr_mediaCurrentTime=e.currentTime),x||(e.scrollLeft&&(s.rr_scrollLeft=e.scrollLeft),e.scrollTop&&(s.rr_scrollTop=e.scrollTop)),T){var U=e.getBoundingClientRect(),W=U.width,O=U.height;s={class:s.class,rr_width:"".concat(W,"px"),rr_height:"".concat(O,"px")}}return p==="iframe"&&!k(s.src)&&(e.contentDocument||(s.rr_src=s.src),delete s.src),{type:v.Element,tagName:p,attributes:s,childNodes:[],isSVG:Ge(e)||void 0,needBlock:T,rootId:I}}function d(e){return e===void 0?"":e.toLowerCase()}function rt(e,t){if(t.comment&&e.type===v.Comment)return!0;if(e.type===v.Element){if(t.script&&(e.tagName==="script"||e.tagName==="link"&&e.attributes.rel==="preload"&&e.attributes.as==="script"||e.tagName==="link"&&e.attributes.rel==="prefetch"&&typeof e.attributes.href=="string"&&e.attributes.href.endsWith(".js")))return!0;if(t.headFavicon&&(e.tagName==="link"&&e.attributes.rel==="shortcut icon"||e.tagName==="meta"&&(d(e.attributes.name).match(/^msapplication-tile(image|color)$/)||d(e.attributes.name)==="application-name"||d(e.attributes.rel)==="icon"||d(e.attributes.rel)==="apple-touch-icon"||d(e.attributes.rel)==="shortcut icon")))return!0;if(e.tagName==="meta"){if(t.headMetaDescKeywords&&d(e.attributes.name).match(/^description|keywords$/))return!0;if(t.headMetaSocial&&(d(e.attributes.property).match(/^(og|twitter|fb):/)||d(e.attributes.name).match(/^(og|twitter):/)||d(e.attributes.name)==="pinterest"))return!0;if(t.headMetaRobots&&(d(e.attributes.name)==="robots"||d(e.attributes.name)==="googlebot"||d(e.attributes.name)==="bingbot"))return!0;if(t.headMetaHttpEquiv&&e.attributes["http-equiv"]!==void 0)return!0;if(t.headMetaAuthorship&&(d(e.attributes.name)==="author"||d(e.attributes.name)==="generator"||d(e.attributes.name)==="framework"||d(e.attributes.name)==="publisher"||d(e.attributes.name)==="progid"||d(e.attributes.property).match(/^article:/)||d(e.attributes.property).match(/^product:/)))return!0;if(t.headMetaVerification&&(d(e.attributes.name)==="google-site-verification"||d(e.attributes.name)==="yandex-verification"||d(e.attributes.name)==="csrf-token"||d(e.attributes.name)==="p:domain_verify"||d(e.attributes.name)==="verify-v1"||d(e.attributes.name)==="verification"||d(e.attributes.name)==="shopify-checkout-api-token"))return!0}}return!1}function Q(e,t){var r=t.doc,i=t.mirror,n=t.blockClass,a=t.blockSelector,c=t.maskTextClass,u=t.maskTextSelector,o=t.skipChild,f=o===void 0?!1:o,l=t.inlineStylesheet,h=l===void 0?!0:l,b=t.maskInputOptions,k=b===void 0?{}:b,w=t.maskTextFn,x=t.maskInputFn,I=t.slimDOMOptions,T=t.dataURLOptions,p=T===void 0?{}:T,s=t.inlineImages,y=s===void 0?!1:s,M=t.recordCanvas,E=M===void 0?!1:M,C=t.onSerialize,m=t.onIframeLoad,P=t.iframeLoadTimeout,_=P===void 0?5e3:P,L=t.onStylesheetLoad,A=t.stylesheetLoadTimeout,D=A===void 0?5e3:A,S=t.keepIframeSrcFn,N=S===void 0?function(){return!1}:S,F=t.newlyAddedElement,U=F===void 0?!1:F,W=t.preserveWhiteSpace,O=W===void 0?!0:W,R=Xe(e,{doc:r,mirror:i,blockClass:n,blockSelector:a,maskTextClass:c,maskTextSelector:u,inlineStylesheet:h,maskInputOptions:k,maskTextFn:w,maskInputFn:x,dataURLOptions:p,inlineImages:y,recordCanvas:E,keepIframeSrcFn:N,newlyAddedElement:U});if(!R)return console.warn(e,"not serialized"),null;var G;i.hasNode(e)?G=i.getId(e):rt(R,I)||!O&&R.type===v.Text&&!R.isStyle&&!R.textContent.replace(/^\s+|\s+$/gm,"").length?G=ue:G=Fe();var g=Object.assign(R,{id:G});if(i.add(e,g),G===ue)return null;C&&C(e);var J=!f;if(g.type===v.Element){J=J&&!g.needBlock,delete g.needBlock;var ne=e.shadowRoot;ne&&ee(ne)&&(g.isShadowHost=!0)}if((g.type===v.Document||g.type===v.Element)&&J){I.headWhitespace&&g.type===v.Element&&g.tagName==="head"&&(O=!1);for(var ae={doc:r,mirror:i,blockClass:n,blockSelector:a,maskTextClass:c,maskTextSelector:u,skipChild:f,inlineStylesheet:h,maskInputOptions:k,maskTextFn:w,maskInputFn:x,slimDOMOptions:I,dataURLOptions:p,inlineImages:y,recordCanvas:E,preserveWhiteSpace:O,onSerialize:C,onIframeLoad:m,iframeLoadTimeout:_,onStylesheetLoad:L,stylesheetLoadTimeout:D,keepIframeSrcFn:N},V=0,oe=Array.from(e.childNodes);V<oe.length;V++){var Y=oe[V],K=Q(Y,ae);K&&g.childNodes.push(K)}if(Te(e)&&e.shadowRoot)for(var X=0,se=Array.from(e.shadowRoot.childNodes);X<se.length;X++){var Y=se[X],K=Q(Y,ae);K&&(ee(e.shadowRoot)&&(K.isShadow=!0),g.childNodes.push(K))}}return e.parentNode&&Ee(e.parentNode)&&ee(e.parentNode)&&(g.isShadow=!0),g.type===v.Element&&g.tagName==="iframe"&&Ve(e,function(){var B=e.contentDocument;if(B&&m){var ce=Q(B,{doc:B,mirror:i,blockClass:n,blockSelector:a,maskTextClass:c,maskTextSelector:u,skipChild:!1,inlineStylesheet:h,maskInputOptions:k,maskTextFn:w,maskInputFn:x,slimDOMOptions:I,dataURLOptions:p,inlineImages:y,recordCanvas:E,preserveWhiteSpace:O,onSerialize:C,onIframeLoad:m,iframeLoadTimeout:_,onStylesheetLoad:L,stylesheetLoadTimeout:D,keepIframeSrcFn:N});ce&&m(e,ce)}},_),g.type===v.Element&&g.tagName==="link"&&g.attributes.rel==="stylesheet"&&Ye(e,function(){if(L){var B=Q(e,{doc:r,mirror:i,blockClass:n,blockSelector:a,maskTextClass:c,maskTextSelector:u,skipChild:!1,inlineStylesheet:h,maskInputOptions:k,maskTextFn:w,maskInputFn:x,slimDOMOptions:I,dataURLOptions:p,inlineImages:y,recordCanvas:E,preserveWhiteSpace:O,onSerialize:C,onIframeLoad:m,iframeLoadTimeout:_,onStylesheetLoad:L,stylesheetLoadTimeout:D,keepIframeSrcFn:N});B&&L(e,B)}},D),g}function de(e,t){var r=t||{},i=r.mirror,n=i===void 0?new Ne:i,a=r.blockClass,c=a===void 0?"rr-block":a,u=r.blockSelector,o=u===void 0?null:u,f=r.maskTextClass,l=f===void 0?"rr-mask":f,h=r.maskTextSelector,b=h===void 0?null:h,k=r.inlineStylesheet,w=k===void 0?!0:k,x=r.inlineImages,I=x===void 0?!1:x,T=r.recordCanvas,p=T===void 0?!1:T,s=r.maskAllInputs,y=s===void 0?!1:s,M=r.maskTextFn,E=r.maskInputFn,C=r.slimDOM,m=C===void 0?!1:C,P=r.dataURLOptions,_=r.preserveWhiteSpace,L=r.onSerialize,A=r.onIframeLoad,D=r.iframeLoadTimeout,S=r.onStylesheetLoad,N=r.stylesheetLoadTimeout,F=r.keepIframeSrcFn,U=F===void 0?function(){return!1}:F,W=y===!0?{color:!0,date:!0,"datetime-local":!0,email:!0,month:!0,number:!0,range:!0,search:!0,tel:!0,text:!0,time:!0,url:!0,week:!0,textarea:!0,select:!0,password:!0}:y===!1?{password:!0}:y,O=m===!0||m==="all"?{script:!0,comment:!0,headFavicon:!0,headWhitespace:!0,headMetaDescKeywords:m==="all",headMetaSocial:!0,headMetaRobots:!0,headMetaHttpEquiv:!0,headMetaAuthorship:!0,headMetaVerification:!0}:m===!1?{}:m;return Q(e,{doc:e,mirror:n,blockClass:c,blockSelector:o,maskTextClass:l,maskTextSelector:b,skipChild:!1,inlineStylesheet:w,maskInputOptions:W,maskTextFn:M,maskInputFn:E,slimDOMOptions:O,dataURLOptions:P,inlineImages:I,recordCanvas:p,preserveWhiteSpace:_,onSerialize:L,onIframeLoad:A,iframeLoadTimeout:D,onStylesheetLoad:S,stylesheetLoadTimeout:N,keepIframeSrcFn:U,newlyAddedElement:!1})}var it=/([^\\]):hover/,st=new RegExp(it.source,"g");function H(){return typeof crypto<"u"&&crypto.randomUUID?crypto.randomUUID():"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,e=>{let t=Math.random()*16|0;return(e==="x"?t:t&3|8).toString(16)})}function pe(){let e="_krypton_sid";if(typeof sessionStorage<"u"){let t=sessionStorage.getItem(e);return t||(t=H(),sessionStorage.setItem(e,t)),t}return H()}function he(){let e="_krypton_did";if(typeof localStorage<"u"){let t=localStorage.getItem(e);return t||(t=H(),localStorage.setItem(e,t)),t}return H()}function me(){if(typeof window>"u")return{};let e=new URLSearchParams(window.location.search),t={};for(let r of["utm_source","utm_medium","utm_campaign","utm_term","utm_content"]){let i=e.get(r);i&&(t[r]=i)}return t}function ge(){if(typeof window>"u")return"unknown";let e=window.innerWidth;return e<768?"mobile":e<1024?"tablet":"desktop"}function ve(e){if(e.id)return`#${e.id}`;let t=[],r=e;for(;r&&r!==document.body;){let i=r.tagName.toLowerCase();if(r.id){t.unshift(`#${r.id}`);break}if(r.className&&typeof r.className=="string"){let n=r.className.trim().split(/\s+/).slice(0,2).join(".");n&&(i+=`.${n}`)}t.unshift(i),r=r.parentElement}return t.join(" > ")}var q=class{constructor(t){this.eventQueue=[];this.heatmapQueue=[];this.flushTimer=null;this.initialized=!1;this.lastSnapshotUrl=null;this.heatmapSetup=!1;this.geoRequested=!1;this.geoContext=null;this.serverConfig=null;this.configFetched=!1;this.config={autoPageview:!0,heatmap:!1,consentRequired:!1,showConsentBanner:!1,consentCategories:{},flushInterval:5e3,batchSize:20,...t},this.distinctId=H(),this.sessionId=H(),this.consent=this.resolveInitialConsent(),this.canTrackAnalytics()&&this.promotePersistentIds(),this.init()}init(){this.initialized||(this.initialized=!0,this.fetchConfig().then(()=>{this.setupTracking(),this.config.consentRequired&&this.config.showConsentBanner&&!this.hasStoredConsent()&&this.showConsentBanner()}))}resolveInitialConsent(){let t=this.loadStoredConsent();return t||{...this.config.consentRequired?{analytics:!1,heatmaps:!1,geo:!1}:{analytics:!0,heatmaps:!!this.config.heatmap,geo:!1},...this.config.consentCategories}}consentStorageKey(){return`_krypton_consent_${this.config.apiKey}`}hasStoredConsent(){return typeof localStorage>"u"?!1:!!localStorage.getItem(this.consentStorageKey())}loadStoredConsent(){if(typeof localStorage>"u")return null;let t=localStorage.getItem(this.consentStorageKey());if(!t)return null;try{let r=JSON.parse(t);return{analytics:!!r.analytics,heatmaps:!!r.heatmaps,geo:!!r.geo}}catch{return null}}persistConsent(){typeof localStorage>"u"||localStorage.setItem(this.consentStorageKey(),JSON.stringify(this.consent))}promotePersistentIds(){this.distinctId=he(),this.sessionId=pe()}canTrackAnalytics(){return!!this.consent.analytics}isHeatmapFeatureEnabled(){return!this.config.heatmap||this.configFetched&&!this.serverConfig?.heatmaps_enabled?!1:!!this.consent.heatmaps}async fetchConfig(){if(typeof window>"u")return;let t=`_krypton_config_${this.config.apiKey}`,r=sessionStorage.getItem(t);if(r)try{let i=JSON.parse(r);if(i._ts&&Date.now()-i._ts<300*1e3){this.serverConfig=i,this.configFetched=!0;return}}catch{}try{let i=await fetch(`${this.config.endpoint}/api/v1/config?api_key=${encodeURIComponent(this.config.apiKey)}`);if(i.ok){let n=await i.json();this.serverConfig=n,this.configFetched=!0,sessionStorage.setItem(t,JSON.stringify({...n,_ts:Date.now()}))}}catch{}}setupTracking(){if(this.flushTimer=setInterval(()=>this.flush(),this.config.flushInterval),typeof window>"u")return;window.addEventListener("beforeunload",()=>this.flush()),this.config.autoPageview&&this.canTrackAnalytics()&&this.trackPageview();let t=history.pushState;history.pushState=(...i)=>{t.apply(history,i),this.config.autoPageview&&this.canTrackAnalytics()&&setTimeout(()=>this.trackPageview(),0),this.isHeatmapFeatureEnabled()&&setTimeout(()=>this.captureSnapshot(),0)};let r=history.replaceState;history.replaceState=(...i)=>{r.apply(history,i),this.config.autoPageview&&this.canTrackAnalytics()&&setTimeout(()=>this.trackPageview(),0),this.isHeatmapFeatureEnabled()&&setTimeout(()=>this.captureSnapshot(),0)},window.addEventListener("popstate",()=>{this.config.autoPageview&&this.canTrackAnalytics()&&setTimeout(()=>this.trackPageview(),0),this.isHeatmapFeatureEnabled()&&setTimeout(()=>this.captureSnapshot(),0)}),this.isHeatmapFeatureEnabled()&&this.setupHeatmap(),this.consent.geo&&this.captureGeoOnce()}showConsentBanner(){if(typeof document>"u"||document.getElementById("krypton-consent-banner"))return;let t=document.createElement("div");t.id="krypton-consent-banner",t.style.cssText=["position:fixed","left:16px","right:16px","bottom:16px","z-index:2147483647","max-width:720px","margin:0 auto","background:#111827","color:#f9fafb","border-radius:12px","padding:16px","box-shadow:0 12px 30px rgba(0,0,0,0.35)","font-family:system-ui,-apple-system,Segoe UI,Roboto,sans-serif","font-size:14px","line-height:1.4"].join(";");let r=a=>a?"checked":"";t.innerHTML=`
2
+ <div style="font-weight:600;margin-bottom:6px">Privacy preferences</div>
3
+ <div style="opacity:0.9;margin-bottom:10px">Choose what data you allow us to collect.</div>
4
+ <label style="display:flex;gap:8px;align-items:flex-start;margin:6px 0">
5
+ <input type="checkbox" id="krypton-consent-analytics" ${r(this.consent.analytics)} />
6
+ <span><strong>Analytics</strong> (pageviews and custom events)</span>
7
+ </label>
8
+ <label style="display:flex;gap:8px;align-items:flex-start;margin:6px 0">
9
+ <input type="checkbox" id="krypton-consent-heatmaps" ${r(this.consent.heatmaps)} />
10
+ <span><strong>Heatmaps</strong> (click and scroll interaction maps)</span>
11
+ </label>
12
+ <label style="display:flex;gap:8px;align-items:flex-start;margin:6px 0 12px 0">
13
+ <input type="checkbox" id="krypton-consent-geo" ${r(this.consent.geo)} />
14
+ <span><strong>Geo location</strong> (browser geolocation if available)</span>
15
+ </label>
16
+ <div style="display:flex;gap:8px;flex-wrap:wrap">
17
+ <button id="krypton-consent-save" style="border:0;background:#2563eb;color:#fff;padding:8px 12px;border-radius:8px;cursor:pointer">Accept selected</button>
18
+ <button id="krypton-consent-all" style="border:0;background:#16a34a;color:#fff;padding:8px 12px;border-radius:8px;cursor:pointer">Accept all</button>
19
+ <button id="krypton-consent-none" style="border:1px solid #4b5563;background:transparent;color:#f9fafb;padding:8px 12px;border-radius:8px;cursor:pointer">Reject all</button>
20
+ </div>
21
+ `;let i=()=>{t.remove()},n=a=>!!document.getElementById(a)?.checked;t.querySelector("#krypton-consent-save")?.addEventListener("click",()=>{this.setConsent({analytics:n("krypton-consent-analytics"),heatmaps:n("krypton-consent-heatmaps"),geo:n("krypton-consent-geo")}),i()}),t.querySelector("#krypton-consent-all")?.addEventListener("click",()=>{this.setConsent({analytics:!0,heatmaps:!0,geo:!0}),i()}),t.querySelector("#krypton-consent-none")?.addEventListener("click",()=>{this.setConsent({analytics:!1,heatmaps:!1,geo:!1}),i()}),document.body.appendChild(t)}setConsent(t,r=!0){let i={...this.consent};this.consent={analytics:t.analytics??i.analytics,heatmaps:t.heatmaps??i.heatmaps,geo:t.geo??i.geo},r&&this.persistConsent(),!i.analytics&&this.consent.analytics&&(this.promotePersistentIds(),this.config.autoPageview&&this.trackPageview()),i.analytics&&!this.consent.analytics&&(this.eventQueue=[]),!i.heatmaps&&this.isHeatmapFeatureEnabled()&&(this.setupHeatmap(),this.captureSnapshot()),i.heatmaps&&!this.consent.heatmaps&&(this.heatmapQueue=[]),!i.geo&&this.consent.geo&&this.captureGeoOnce()}getConsent(){return{...this.consent}}grantConsent(){this.setConsent({analytics:!0,heatmaps:!0,geo:!0})}revokeConsent(){this.setConsent({analytics:!1,heatmaps:!1,geo:!1}),this.eventQueue=[],this.heatmapQueue=[]}identify(t){this.distinctId=t,typeof localStorage<"u"&&this.canTrackAnalytics()&&localStorage.setItem("_krypton_did",t)}trackPageview(t){this.canTrackAnalytics()&&this.track("$pageview",{...t})}track(t,r){if(!this.canTrackAnalytics())return;let i=me(),n={...r||{}};this.consent.geo&&this.geoContext&&(n.geo=this.geoContext);let a={project_id:this.config.apiKey,distinct_id:this.distinctId,event_name:t,timestamp:new Date().toISOString(),properties:n,page_url:typeof window<"u"?window.location.href:"",page_title:typeof document<"u"?document.title:"",referrer:typeof document<"u"?document.referrer:"",utm_source:i.utm_source||"",utm_medium:i.utm_medium||"",utm_campaign:i.utm_campaign||"",utm_term:i.utm_term||"",utm_content:i.utm_content||"",device_type:ge(),browser:this.getBrowser(),screen_width:typeof window<"u"?window.screen.width:0,screen_height:typeof window<"u"?window.screen.height:0,session_id:this.sessionId};this.eventQueue.push(a),this.eventQueue.length>=this.config.batchSize&&this.flush()}async flush(){await Promise.all([this.flushEvents(),this.flushHeatmapEvents()])}shutdown(){this.flushTimer&&(clearInterval(this.flushTimer),this.flushTimer=null),this.flush()}captureGeoOnce(){this.geoRequested||this.consent.geo&&(typeof navigator>"u"||!navigator.geolocation||(this.geoRequested=!0,navigator.geolocation.getCurrentPosition(t=>{let r=(i,n=3)=>Number(i.toFixed(n));this.geoContext={lat:r(t.coords.latitude),lon:r(t.coords.longitude),accuracy_m:Math.round(t.coords.accuracy),captured_at:new Date().toISOString()}},()=>{},{enableHighAccuracy:!1,maximumAge:600*1e3,timeout:5e3})))}async flushEvents(){if(this.eventQueue.length===0)return;let t=this.eventQueue.splice(0,this.eventQueue.length),r={api_key:this.config.apiKey,events:t};try{await fetch(`${this.config.endpoint}/api/v1/ingest/batch`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(r),keepalive:!0})}catch{this.eventQueue.length<this.config.batchSize*5&&this.eventQueue.unshift(...t)}}async flushHeatmapEvents(){if(this.heatmapQueue.length===0)return;let t=this.heatmapQueue.splice(0,this.heatmapQueue.length),r={api_key:this.config.apiKey,events:t};try{await fetch(`${this.config.endpoint}/api/v1/heatmap`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(r),keepalive:!0})}catch{this.heatmapQueue.length<this.config.batchSize*5&&this.heatmapQueue.unshift(...t)}}captureSnapshot(){if(typeof window>"u"||typeof document>"u"||!this.isHeatmapFeatureEnabled())return;let t=window.location.href;this.lastSnapshotUrl!==t&&(this.lastSnapshotUrl=t,setTimeout(()=>{try{let r=de(document,{maskAllInputs:!0,blockSelector:"[data-krypton-block]",inlineStylesheet:!0});if(!r)return;let i=JSON.stringify(r),n={api_key:this.config.apiKey,page_url:t,snapshot:i,viewport_width:window.innerWidth,viewport_height:window.innerHeight,page_width:document.documentElement.scrollWidth,page_height:document.documentElement.scrollHeight,timestamp:new Date().toISOString()};fetch(`${this.config.endpoint}/api/v1/snapshot`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(n),keepalive:!0}).catch(()=>{})}catch{}},1e3))}setupHeatmap(){if(typeof window>"u"||this.heatmapSetup)return;this.heatmapSetup=!0,this.captureSnapshot(),document.addEventListener("click",i=>{if(!this.isHeatmapFeatureEnabled())return;let n=i.target;this.heatmapQueue.push({project_id:this.config.apiKey,distinct_id:this.distinctId,session_id:this.sessionId,timestamp:new Date().toISOString(),page_url:window.location.href,interaction_type:"click",x:i.clientX+window.scrollX,y:i.clientY+window.scrollY,viewport_width:window.innerWidth,viewport_height:window.innerHeight,page_width:document.documentElement.scrollWidth,page_height:document.documentElement.scrollHeight,selector:ve(n)})});let t=0,r=null;window.addEventListener("scroll",()=>{if(!this.isHeatmapFeatureEnabled())return;let i=window.scrollY||document.documentElement.scrollTop,n=document.documentElement.scrollHeight-window.innerHeight,a=n>0?i/n*100:0;a>t&&(t=a),r&&clearTimeout(r),r=setTimeout(()=>{this.heatmapQueue.push({project_id:this.config.apiKey,distinct_id:this.distinctId,session_id:this.sessionId,timestamp:new Date().toISOString(),page_url:window.location.href,interaction_type:"scroll",scroll_depth:t,viewport_width:window.innerWidth,viewport_height:window.innerHeight,page_width:document.documentElement.scrollWidth,page_height:document.documentElement.scrollHeight})},500)})}getBrowser(){if(typeof navigator>"u")return"unknown";let t=navigator.userAgent;return t.includes("Firefox")?"Firefox":t.includes("Edg")?"Edge":t.includes("Chrome")?"Chrome":t.includes("Safari")?"Safari":"Other"}};function ye(e){return new q(e)}var ke={Krypton:q,init:ye};typeof window<"u"&&(window.KryptonAnalytics=ke);var nt=ke;return Ie(at);})();
package/dist/index.d.mts CHANGED
@@ -9,11 +9,21 @@ interface KryptonConfig {
9
9
  heatmap?: boolean;
10
10
  /** Require consent before tracking (default: false) */
11
11
  consentRequired?: boolean;
12
+ /** Show a built-in consent popup (default: false) */
13
+ showConsentBanner?: boolean;
14
+ /** Optional initial consent values (default deny when consentRequired=true) */
15
+ consentCategories?: Partial<ConsentPreferences>;
12
16
  /** Flush interval in milliseconds (default: 5000) */
13
17
  flushInterval?: number;
14
18
  /** Max events per batch (default: 20) */
15
19
  batchSize?: number;
16
20
  }
21
+ type ConsentCategory = "analytics" | "heatmaps" | "geo";
22
+ interface ConsentPreferences {
23
+ analytics: boolean;
24
+ heatmaps: boolean;
25
+ geo: boolean;
26
+ }
17
27
  interface EventPayload {
18
28
  project_id: string;
19
29
  distinct_id: string;
@@ -61,18 +71,34 @@ declare class Krypton {
61
71
  private eventQueue;
62
72
  private heatmapQueue;
63
73
  private flushTimer;
64
- private consentGiven;
65
74
  private initialized;
66
75
  private lastSnapshotUrl;
76
+ private heatmapSetup;
77
+ private geoRequested;
78
+ private geoContext;
79
+ private consent;
67
80
  private serverConfig;
68
81
  private configFetched;
69
82
  constructor(config: KryptonConfig);
70
83
  private init;
84
+ private resolveInitialConsent;
85
+ private consentStorageKey;
86
+ private hasStoredConsent;
87
+ private loadStoredConsent;
88
+ private persistConsent;
89
+ private promotePersistentIds;
90
+ private canTrackAnalytics;
91
+ private isHeatmapFeatureEnabled;
71
92
  private fetchConfig;
72
93
  private setupTracking;
73
- /** Grant consent and begin tracking */
94
+ /** Built-in consent popup with category toggles (analytics, heatmaps, geo). */
95
+ showConsentBanner(): void;
96
+ /** Set one or more consent categories and apply changes immediately. */
97
+ setConsent(next: Partial<ConsentPreferences>, persist?: boolean): void;
98
+ getConsent(): ConsentPreferences;
99
+ /** Backward-compatible: grant all categories. */
74
100
  grantConsent(): void;
75
- /** Revoke consent and stop tracking */
101
+ /** Backward-compatible: revoke all categories and clear queues. */
76
102
  revokeConsent(): void;
77
103
  /** Identify a user with a custom distinct ID */
78
104
  identify(distinctId: string): void;
@@ -80,10 +106,9 @@ declare class Krypton {
80
106
  trackPageview(properties?: Record<string, unknown>): void;
81
107
  /** Track a custom event */
82
108
  track(eventName: string, properties?: Record<string, unknown>): void;
83
- /** Flush all queued events to the server */
84
109
  flush(): Promise<void>;
85
- /** Shutdown the SDK */
86
110
  shutdown(): void;
111
+ private captureGeoOnce;
87
112
  private flushEvents;
88
113
  private flushHeatmapEvents;
89
114
  private captureSnapshot;
@@ -92,4 +117,4 @@ declare class Krypton {
92
117
  }
93
118
  declare function createKrypton(config: KryptonConfig): Krypton;
94
119
 
95
- export { type EventPayload, type HeatmapEventPayload, Krypton, type KryptonConfig, createKrypton };
120
+ export { type ConsentCategory, type ConsentPreferences, type EventPayload, type HeatmapEventPayload, Krypton, type KryptonConfig, createKrypton };
package/dist/index.d.ts CHANGED
@@ -9,11 +9,21 @@ interface KryptonConfig {
9
9
  heatmap?: boolean;
10
10
  /** Require consent before tracking (default: false) */
11
11
  consentRequired?: boolean;
12
+ /** Show a built-in consent popup (default: false) */
13
+ showConsentBanner?: boolean;
14
+ /** Optional initial consent values (default deny when consentRequired=true) */
15
+ consentCategories?: Partial<ConsentPreferences>;
12
16
  /** Flush interval in milliseconds (default: 5000) */
13
17
  flushInterval?: number;
14
18
  /** Max events per batch (default: 20) */
15
19
  batchSize?: number;
16
20
  }
21
+ type ConsentCategory = "analytics" | "heatmaps" | "geo";
22
+ interface ConsentPreferences {
23
+ analytics: boolean;
24
+ heatmaps: boolean;
25
+ geo: boolean;
26
+ }
17
27
  interface EventPayload {
18
28
  project_id: string;
19
29
  distinct_id: string;
@@ -61,18 +71,34 @@ declare class Krypton {
61
71
  private eventQueue;
62
72
  private heatmapQueue;
63
73
  private flushTimer;
64
- private consentGiven;
65
74
  private initialized;
66
75
  private lastSnapshotUrl;
76
+ private heatmapSetup;
77
+ private geoRequested;
78
+ private geoContext;
79
+ private consent;
67
80
  private serverConfig;
68
81
  private configFetched;
69
82
  constructor(config: KryptonConfig);
70
83
  private init;
84
+ private resolveInitialConsent;
85
+ private consentStorageKey;
86
+ private hasStoredConsent;
87
+ private loadStoredConsent;
88
+ private persistConsent;
89
+ private promotePersistentIds;
90
+ private canTrackAnalytics;
91
+ private isHeatmapFeatureEnabled;
71
92
  private fetchConfig;
72
93
  private setupTracking;
73
- /** Grant consent and begin tracking */
94
+ /** Built-in consent popup with category toggles (analytics, heatmaps, geo). */
95
+ showConsentBanner(): void;
96
+ /** Set one or more consent categories and apply changes immediately. */
97
+ setConsent(next: Partial<ConsentPreferences>, persist?: boolean): void;
98
+ getConsent(): ConsentPreferences;
99
+ /** Backward-compatible: grant all categories. */
74
100
  grantConsent(): void;
75
- /** Revoke consent and stop tracking */
101
+ /** Backward-compatible: revoke all categories and clear queues. */
76
102
  revokeConsent(): void;
77
103
  /** Identify a user with a custom distinct ID */
78
104
  identify(distinctId: string): void;
@@ -80,10 +106,9 @@ declare class Krypton {
80
106
  trackPageview(properties?: Record<string, unknown>): void;
81
107
  /** Track a custom event */
82
108
  track(eventName: string, properties?: Record<string, unknown>): void;
83
- /** Flush all queued events to the server */
84
109
  flush(): Promise<void>;
85
- /** Shutdown the SDK */
86
110
  shutdown(): void;
111
+ private captureGeoOnce;
87
112
  private flushEvents;
88
113
  private flushHeatmapEvents;
89
114
  private captureSnapshot;
@@ -92,4 +117,4 @@ declare class Krypton {
92
117
  }
93
118
  declare function createKrypton(config: KryptonConfig): Krypton;
94
119
 
95
- export { type EventPayload, type HeatmapEventPayload, Krypton, type KryptonConfig, createKrypton };
120
+ export { type ConsentCategory, type ConsentPreferences, type EventPayload, type HeatmapEventPayload, Krypton, type KryptonConfig, createKrypton };
package/dist/index.js CHANGED
@@ -104,30 +104,91 @@ var Krypton = class {
104
104
  this.eventQueue = [];
105
105
  this.heatmapQueue = [];
106
106
  this.flushTimer = null;
107
- this.consentGiven = false;
108
107
  this.initialized = false;
109
108
  this.lastSnapshotUrl = null;
109
+ this.heatmapSetup = false;
110
+ this.geoRequested = false;
111
+ this.geoContext = null;
110
112
  this.serverConfig = null;
111
113
  this.configFetched = false;
112
114
  this.config = {
113
115
  autoPageview: true,
114
116
  heatmap: false,
115
117
  consentRequired: false,
118
+ showConsentBanner: false,
119
+ consentCategories: {},
116
120
  flushInterval: 5e3,
117
121
  batchSize: 20,
118
122
  ...config
119
123
  };
120
- this.distinctId = getDistinctId();
121
- this.sessionId = getSessionId();
122
- if (!this.config.consentRequired) {
123
- this.consentGiven = true;
124
+ this.distinctId = generateId();
125
+ this.sessionId = generateId();
126
+ this.consent = this.resolveInitialConsent();
127
+ if (this.canTrackAnalytics()) {
128
+ this.promotePersistentIds();
124
129
  }
125
130
  this.init();
126
131
  }
127
132
  init() {
128
133
  if (this.initialized) return;
129
134
  this.initialized = true;
130
- this.fetchConfig().then(() => this.setupTracking());
135
+ this.fetchConfig().then(() => {
136
+ this.setupTracking();
137
+ if (this.config.consentRequired && this.config.showConsentBanner && !this.hasStoredConsent()) {
138
+ this.showConsentBanner();
139
+ }
140
+ });
141
+ }
142
+ resolveInitialConsent() {
143
+ const stored = this.loadStoredConsent();
144
+ if (stored) return stored;
145
+ const base = this.config.consentRequired ? { analytics: false, heatmaps: false, geo: false } : {
146
+ analytics: true,
147
+ heatmaps: !!this.config.heatmap,
148
+ geo: false
149
+ };
150
+ return {
151
+ ...base,
152
+ ...this.config.consentCategories
153
+ };
154
+ }
155
+ consentStorageKey() {
156
+ return `_krypton_consent_${this.config.apiKey}`;
157
+ }
158
+ hasStoredConsent() {
159
+ if (typeof localStorage === "undefined") return false;
160
+ return !!localStorage.getItem(this.consentStorageKey());
161
+ }
162
+ loadStoredConsent() {
163
+ if (typeof localStorage === "undefined") return null;
164
+ const raw = localStorage.getItem(this.consentStorageKey());
165
+ if (!raw) return null;
166
+ try {
167
+ const parsed = JSON.parse(raw);
168
+ return {
169
+ analytics: !!parsed.analytics,
170
+ heatmaps: !!parsed.heatmaps,
171
+ geo: !!parsed.geo
172
+ };
173
+ } catch {
174
+ return null;
175
+ }
176
+ }
177
+ persistConsent() {
178
+ if (typeof localStorage === "undefined") return;
179
+ localStorage.setItem(this.consentStorageKey(), JSON.stringify(this.consent));
180
+ }
181
+ promotePersistentIds() {
182
+ this.distinctId = getDistinctId();
183
+ this.sessionId = getSessionId();
184
+ }
185
+ canTrackAnalytics() {
186
+ return !!this.consent.analytics;
187
+ }
188
+ isHeatmapFeatureEnabled() {
189
+ if (!this.config.heatmap) return false;
190
+ if (this.configFetched && !this.serverConfig?.heatmaps_enabled) return false;
191
+ return !!this.consent.heatmaps;
131
192
  }
132
193
  async fetchConfig() {
133
194
  if (typeof window === "undefined") return;
@@ -159,76 +220,188 @@ var Krypton = class {
159
220
  }
160
221
  setupTracking() {
161
222
  this.flushTimer = setInterval(() => this.flush(), this.config.flushInterval);
162
- if (typeof window !== "undefined") {
163
- window.addEventListener("beforeunload", () => this.flush());
164
- if (this.config.autoPageview && this.consentGiven) {
165
- this.trackPageview();
223
+ if (typeof window === "undefined") return;
224
+ window.addEventListener("beforeunload", () => this.flush());
225
+ if (this.config.autoPageview && this.canTrackAnalytics()) {
226
+ this.trackPageview();
227
+ }
228
+ const originalPushState = history.pushState;
229
+ history.pushState = (...args) => {
230
+ originalPushState.apply(history, args);
231
+ if (this.config.autoPageview && this.canTrackAnalytics()) {
232
+ setTimeout(() => this.trackPageview(), 0);
166
233
  }
167
- const heatmapEnabled = this.config.heatmap && (!this.configFetched || this.serverConfig?.heatmaps_enabled);
168
- const originalPushState = history.pushState;
169
- history.pushState = (...args) => {
170
- originalPushState.apply(history, args);
171
- if (this.config.autoPageview && this.consentGiven) {
172
- setTimeout(() => this.trackPageview(), 0);
173
- }
174
- if (heatmapEnabled && this.consentGiven) {
175
- setTimeout(() => this.captureSnapshot(), 0);
176
- }
177
- };
178
- window.addEventListener("popstate", () => {
179
- if (this.config.autoPageview && this.consentGiven) {
180
- setTimeout(() => this.trackPageview(), 0);
181
- }
182
- if (heatmapEnabled && this.consentGiven) {
183
- setTimeout(() => this.captureSnapshot(), 0);
184
- }
185
- });
186
- if (heatmapEnabled && this.consentGiven) {
187
- this.setupHeatmap();
234
+ if (this.isHeatmapFeatureEnabled()) {
235
+ setTimeout(() => this.captureSnapshot(), 0);
236
+ }
237
+ };
238
+ const originalReplaceState = history.replaceState;
239
+ history.replaceState = (...args) => {
240
+ originalReplaceState.apply(history, args);
241
+ if (this.config.autoPageview && this.canTrackAnalytics()) {
242
+ setTimeout(() => this.trackPageview(), 0);
243
+ }
244
+ if (this.isHeatmapFeatureEnabled()) {
245
+ setTimeout(() => this.captureSnapshot(), 0);
246
+ }
247
+ };
248
+ window.addEventListener("popstate", () => {
249
+ if (this.config.autoPageview && this.canTrackAnalytics()) {
250
+ setTimeout(() => this.trackPageview(), 0);
188
251
  }
252
+ if (this.isHeatmapFeatureEnabled()) {
253
+ setTimeout(() => this.captureSnapshot(), 0);
254
+ }
255
+ });
256
+ if (this.isHeatmapFeatureEnabled()) {
257
+ this.setupHeatmap();
258
+ }
259
+ if (this.consent.geo) {
260
+ this.captureGeoOnce();
189
261
  }
190
262
  }
191
- /** Grant consent and begin tracking */
192
- grantConsent() {
193
- this.consentGiven = true;
194
- if (this.config.autoPageview) {
195
- this.trackPageview();
263
+ /** Built-in consent popup with category toggles (analytics, heatmaps, geo). */
264
+ showConsentBanner() {
265
+ if (typeof document === "undefined") return;
266
+ if (document.getElementById("krypton-consent-banner")) return;
267
+ const banner = document.createElement("div");
268
+ banner.id = "krypton-consent-banner";
269
+ banner.style.cssText = [
270
+ "position:fixed",
271
+ "left:16px",
272
+ "right:16px",
273
+ "bottom:16px",
274
+ "z-index:2147483647",
275
+ "max-width:720px",
276
+ "margin:0 auto",
277
+ "background:#111827",
278
+ "color:#f9fafb",
279
+ "border-radius:12px",
280
+ "padding:16px",
281
+ "box-shadow:0 12px 30px rgba(0,0,0,0.35)",
282
+ "font-family:system-ui,-apple-system,Segoe UI,Roboto,sans-serif",
283
+ "font-size:14px",
284
+ "line-height:1.4"
285
+ ].join(";");
286
+ const checked = (value) => value ? "checked" : "";
287
+ banner.innerHTML = `
288
+ <div style="font-weight:600;margin-bottom:6px">Privacy preferences</div>
289
+ <div style="opacity:0.9;margin-bottom:10px">Choose what data you allow us to collect.</div>
290
+ <label style="display:flex;gap:8px;align-items:flex-start;margin:6px 0">
291
+ <input type="checkbox" id="krypton-consent-analytics" ${checked(this.consent.analytics)} />
292
+ <span><strong>Analytics</strong> (pageviews and custom events)</span>
293
+ </label>
294
+ <label style="display:flex;gap:8px;align-items:flex-start;margin:6px 0">
295
+ <input type="checkbox" id="krypton-consent-heatmaps" ${checked(this.consent.heatmaps)} />
296
+ <span><strong>Heatmaps</strong> (click and scroll interaction maps)</span>
297
+ </label>
298
+ <label style="display:flex;gap:8px;align-items:flex-start;margin:6px 0 12px 0">
299
+ <input type="checkbox" id="krypton-consent-geo" ${checked(this.consent.geo)} />
300
+ <span><strong>Geo location</strong> (browser geolocation if available)</span>
301
+ </label>
302
+ <div style="display:flex;gap:8px;flex-wrap:wrap">
303
+ <button id="krypton-consent-save" style="border:0;background:#2563eb;color:#fff;padding:8px 12px;border-radius:8px;cursor:pointer">Accept selected</button>
304
+ <button id="krypton-consent-all" style="border:0;background:#16a34a;color:#fff;padding:8px 12px;border-radius:8px;cursor:pointer">Accept all</button>
305
+ <button id="krypton-consent-none" style="border:1px solid #4b5563;background:transparent;color:#f9fafb;padding:8px 12px;border-radius:8px;cursor:pointer">Reject all</button>
306
+ </div>
307
+ `;
308
+ const remove = () => {
309
+ banner.remove();
310
+ };
311
+ const readValue = (id) => {
312
+ const el = document.getElementById(id);
313
+ return !!el?.checked;
314
+ };
315
+ banner.querySelector("#krypton-consent-save")?.addEventListener("click", () => {
316
+ this.setConsent({
317
+ analytics: readValue("krypton-consent-analytics"),
318
+ heatmaps: readValue("krypton-consent-heatmaps"),
319
+ geo: readValue("krypton-consent-geo")
320
+ });
321
+ remove();
322
+ });
323
+ banner.querySelector("#krypton-consent-all")?.addEventListener("click", () => {
324
+ this.setConsent({ analytics: true, heatmaps: true, geo: true });
325
+ remove();
326
+ });
327
+ banner.querySelector("#krypton-consent-none")?.addEventListener("click", () => {
328
+ this.setConsent({ analytics: false, heatmaps: false, geo: false });
329
+ remove();
330
+ });
331
+ document.body.appendChild(banner);
332
+ }
333
+ /** Set one or more consent categories and apply changes immediately. */
334
+ setConsent(next, persist = true) {
335
+ const prev = { ...this.consent };
336
+ this.consent = {
337
+ analytics: next.analytics ?? prev.analytics,
338
+ heatmaps: next.heatmaps ?? prev.heatmaps,
339
+ geo: next.geo ?? prev.geo
340
+ };
341
+ if (persist) {
342
+ this.persistConsent();
343
+ }
344
+ if (!prev.analytics && this.consent.analytics) {
345
+ this.promotePersistentIds();
346
+ if (this.config.autoPageview) {
347
+ this.trackPageview();
348
+ }
349
+ }
350
+ if (prev.analytics && !this.consent.analytics) {
351
+ this.eventQueue = [];
196
352
  }
197
- const heatmapEnabled = this.config.heatmap && (!this.configFetched || this.serverConfig?.heatmaps_enabled);
198
- if (heatmapEnabled) {
353
+ if (!prev.heatmaps && this.isHeatmapFeatureEnabled()) {
199
354
  this.setupHeatmap();
355
+ this.captureSnapshot();
356
+ }
357
+ if (prev.heatmaps && !this.consent.heatmaps) {
358
+ this.heatmapQueue = [];
359
+ }
360
+ if (!prev.geo && this.consent.geo) {
361
+ this.captureGeoOnce();
200
362
  }
201
363
  }
202
- /** Revoke consent and stop tracking */
364
+ getConsent() {
365
+ return { ...this.consent };
366
+ }
367
+ /** Backward-compatible: grant all categories. */
368
+ grantConsent() {
369
+ this.setConsent({ analytics: true, heatmaps: true, geo: true });
370
+ }
371
+ /** Backward-compatible: revoke all categories and clear queues. */
203
372
  revokeConsent() {
204
- this.consentGiven = false;
373
+ this.setConsent({ analytics: false, heatmaps: false, geo: false });
205
374
  this.eventQueue = [];
206
375
  this.heatmapQueue = [];
207
376
  }
208
377
  /** Identify a user with a custom distinct ID */
209
378
  identify(distinctId) {
210
379
  this.distinctId = distinctId;
211
- if (typeof localStorage !== "undefined") {
380
+ if (typeof localStorage !== "undefined" && this.canTrackAnalytics()) {
212
381
  localStorage.setItem("_krypton_did", distinctId);
213
382
  }
214
383
  }
215
384
  /** Track a pageview */
216
385
  trackPageview(properties) {
217
- if (!this.consentGiven) return;
386
+ if (!this.canTrackAnalytics()) return;
218
387
  this.track("$pageview", {
219
388
  ...properties
220
389
  });
221
390
  }
222
391
  /** Track a custom event */
223
392
  track(eventName, properties) {
224
- if (!this.consentGiven) return;
393
+ if (!this.canTrackAnalytics()) return;
225
394
  const utm = getUTMParams();
395
+ const mergedProperties = { ...properties || {} };
396
+ if (this.consent.geo && this.geoContext) {
397
+ mergedProperties.geo = this.geoContext;
398
+ }
226
399
  const event = {
227
400
  project_id: this.config.apiKey,
228
401
  distinct_id: this.distinctId,
229
402
  event_name: eventName,
230
403
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
231
- properties,
404
+ properties: mergedProperties,
232
405
  page_url: typeof window !== "undefined" ? window.location.href : "",
233
406
  page_title: typeof document !== "undefined" ? document.title : "",
234
407
  referrer: typeof document !== "undefined" ? document.referrer : "",
@@ -248,11 +421,9 @@ var Krypton = class {
248
421
  this.flush();
249
422
  }
250
423
  }
251
- /** Flush all queued events to the server */
252
424
  async flush() {
253
425
  await Promise.all([this.flushEvents(), this.flushHeatmapEvents()]);
254
426
  }
255
- /** Shutdown the SDK */
256
427
  shutdown() {
257
428
  if (this.flushTimer) {
258
429
  clearInterval(this.flushTimer);
@@ -260,7 +431,30 @@ var Krypton = class {
260
431
  }
261
432
  this.flush();
262
433
  }
263
- // ---- Private methods ----
434
+ captureGeoOnce() {
435
+ if (this.geoRequested) return;
436
+ if (!this.consent.geo) return;
437
+ if (typeof navigator === "undefined" || !navigator.geolocation) return;
438
+ this.geoRequested = true;
439
+ navigator.geolocation.getCurrentPosition(
440
+ (pos) => {
441
+ const round = (value, precision = 3) => Number(value.toFixed(precision));
442
+ this.geoContext = {
443
+ lat: round(pos.coords.latitude),
444
+ lon: round(pos.coords.longitude),
445
+ accuracy_m: Math.round(pos.coords.accuracy),
446
+ captured_at: (/* @__PURE__ */ new Date()).toISOString()
447
+ };
448
+ },
449
+ () => {
450
+ },
451
+ {
452
+ enableHighAccuracy: false,
453
+ maximumAge: 10 * 60 * 1e3,
454
+ timeout: 5e3
455
+ }
456
+ );
457
+ }
264
458
  async flushEvents() {
265
459
  if (this.eventQueue.length === 0) return;
266
460
  const events = this.eventQueue.splice(0, this.eventQueue.length);
@@ -303,6 +497,7 @@ var Krypton = class {
303
497
  }
304
498
  captureSnapshot() {
305
499
  if (typeof window === "undefined" || typeof document === "undefined") return;
500
+ if (!this.isHeatmapFeatureEnabled()) return;
306
501
  const currentUrl = window.location.href;
307
502
  if (this.lastSnapshotUrl === currentUrl) return;
308
503
  this.lastSnapshotUrl = currentUrl;
@@ -338,9 +533,11 @@ var Krypton = class {
338
533
  }
339
534
  setupHeatmap() {
340
535
  if (typeof window === "undefined") return;
536
+ if (this.heatmapSetup) return;
537
+ this.heatmapSetup = true;
341
538
  this.captureSnapshot();
342
539
  document.addEventListener("click", (e) => {
343
- if (!this.consentGiven) return;
540
+ if (!this.isHeatmapFeatureEnabled()) return;
344
541
  const target = e.target;
345
542
  this.heatmapQueue.push({
346
543
  project_id: this.config.apiKey,
@@ -361,7 +558,7 @@ var Krypton = class {
361
558
  let maxScrollDepth = 0;
362
559
  let scrollTimeout = null;
363
560
  window.addEventListener("scroll", () => {
364
- if (!this.consentGiven) return;
561
+ if (!this.isHeatmapFeatureEnabled()) return;
365
562
  const scrollTop = window.scrollY || document.documentElement.scrollTop;
366
563
  const docHeight = document.documentElement.scrollHeight - window.innerHeight;
367
564
  const depth = docHeight > 0 ? scrollTop / docHeight * 100 : 0;
package/dist/index.mjs CHANGED
@@ -79,30 +79,91 @@ var Krypton = class {
79
79
  this.eventQueue = [];
80
80
  this.heatmapQueue = [];
81
81
  this.flushTimer = null;
82
- this.consentGiven = false;
83
82
  this.initialized = false;
84
83
  this.lastSnapshotUrl = null;
84
+ this.heatmapSetup = false;
85
+ this.geoRequested = false;
86
+ this.geoContext = null;
85
87
  this.serverConfig = null;
86
88
  this.configFetched = false;
87
89
  this.config = {
88
90
  autoPageview: true,
89
91
  heatmap: false,
90
92
  consentRequired: false,
93
+ showConsentBanner: false,
94
+ consentCategories: {},
91
95
  flushInterval: 5e3,
92
96
  batchSize: 20,
93
97
  ...config
94
98
  };
95
- this.distinctId = getDistinctId();
96
- this.sessionId = getSessionId();
97
- if (!this.config.consentRequired) {
98
- this.consentGiven = true;
99
+ this.distinctId = generateId();
100
+ this.sessionId = generateId();
101
+ this.consent = this.resolveInitialConsent();
102
+ if (this.canTrackAnalytics()) {
103
+ this.promotePersistentIds();
99
104
  }
100
105
  this.init();
101
106
  }
102
107
  init() {
103
108
  if (this.initialized) return;
104
109
  this.initialized = true;
105
- this.fetchConfig().then(() => this.setupTracking());
110
+ this.fetchConfig().then(() => {
111
+ this.setupTracking();
112
+ if (this.config.consentRequired && this.config.showConsentBanner && !this.hasStoredConsent()) {
113
+ this.showConsentBanner();
114
+ }
115
+ });
116
+ }
117
+ resolveInitialConsent() {
118
+ const stored = this.loadStoredConsent();
119
+ if (stored) return stored;
120
+ const base = this.config.consentRequired ? { analytics: false, heatmaps: false, geo: false } : {
121
+ analytics: true,
122
+ heatmaps: !!this.config.heatmap,
123
+ geo: false
124
+ };
125
+ return {
126
+ ...base,
127
+ ...this.config.consentCategories
128
+ };
129
+ }
130
+ consentStorageKey() {
131
+ return `_krypton_consent_${this.config.apiKey}`;
132
+ }
133
+ hasStoredConsent() {
134
+ if (typeof localStorage === "undefined") return false;
135
+ return !!localStorage.getItem(this.consentStorageKey());
136
+ }
137
+ loadStoredConsent() {
138
+ if (typeof localStorage === "undefined") return null;
139
+ const raw = localStorage.getItem(this.consentStorageKey());
140
+ if (!raw) return null;
141
+ try {
142
+ const parsed = JSON.parse(raw);
143
+ return {
144
+ analytics: !!parsed.analytics,
145
+ heatmaps: !!parsed.heatmaps,
146
+ geo: !!parsed.geo
147
+ };
148
+ } catch {
149
+ return null;
150
+ }
151
+ }
152
+ persistConsent() {
153
+ if (typeof localStorage === "undefined") return;
154
+ localStorage.setItem(this.consentStorageKey(), JSON.stringify(this.consent));
155
+ }
156
+ promotePersistentIds() {
157
+ this.distinctId = getDistinctId();
158
+ this.sessionId = getSessionId();
159
+ }
160
+ canTrackAnalytics() {
161
+ return !!this.consent.analytics;
162
+ }
163
+ isHeatmapFeatureEnabled() {
164
+ if (!this.config.heatmap) return false;
165
+ if (this.configFetched && !this.serverConfig?.heatmaps_enabled) return false;
166
+ return !!this.consent.heatmaps;
106
167
  }
107
168
  async fetchConfig() {
108
169
  if (typeof window === "undefined") return;
@@ -134,76 +195,188 @@ var Krypton = class {
134
195
  }
135
196
  setupTracking() {
136
197
  this.flushTimer = setInterval(() => this.flush(), this.config.flushInterval);
137
- if (typeof window !== "undefined") {
138
- window.addEventListener("beforeunload", () => this.flush());
139
- if (this.config.autoPageview && this.consentGiven) {
140
- this.trackPageview();
198
+ if (typeof window === "undefined") return;
199
+ window.addEventListener("beforeunload", () => this.flush());
200
+ if (this.config.autoPageview && this.canTrackAnalytics()) {
201
+ this.trackPageview();
202
+ }
203
+ const originalPushState = history.pushState;
204
+ history.pushState = (...args) => {
205
+ originalPushState.apply(history, args);
206
+ if (this.config.autoPageview && this.canTrackAnalytics()) {
207
+ setTimeout(() => this.trackPageview(), 0);
141
208
  }
142
- const heatmapEnabled = this.config.heatmap && (!this.configFetched || this.serverConfig?.heatmaps_enabled);
143
- const originalPushState = history.pushState;
144
- history.pushState = (...args) => {
145
- originalPushState.apply(history, args);
146
- if (this.config.autoPageview && this.consentGiven) {
147
- setTimeout(() => this.trackPageview(), 0);
148
- }
149
- if (heatmapEnabled && this.consentGiven) {
150
- setTimeout(() => this.captureSnapshot(), 0);
151
- }
152
- };
153
- window.addEventListener("popstate", () => {
154
- if (this.config.autoPageview && this.consentGiven) {
155
- setTimeout(() => this.trackPageview(), 0);
156
- }
157
- if (heatmapEnabled && this.consentGiven) {
158
- setTimeout(() => this.captureSnapshot(), 0);
159
- }
160
- });
161
- if (heatmapEnabled && this.consentGiven) {
162
- this.setupHeatmap();
209
+ if (this.isHeatmapFeatureEnabled()) {
210
+ setTimeout(() => this.captureSnapshot(), 0);
211
+ }
212
+ };
213
+ const originalReplaceState = history.replaceState;
214
+ history.replaceState = (...args) => {
215
+ originalReplaceState.apply(history, args);
216
+ if (this.config.autoPageview && this.canTrackAnalytics()) {
217
+ setTimeout(() => this.trackPageview(), 0);
218
+ }
219
+ if (this.isHeatmapFeatureEnabled()) {
220
+ setTimeout(() => this.captureSnapshot(), 0);
221
+ }
222
+ };
223
+ window.addEventListener("popstate", () => {
224
+ if (this.config.autoPageview && this.canTrackAnalytics()) {
225
+ setTimeout(() => this.trackPageview(), 0);
163
226
  }
227
+ if (this.isHeatmapFeatureEnabled()) {
228
+ setTimeout(() => this.captureSnapshot(), 0);
229
+ }
230
+ });
231
+ if (this.isHeatmapFeatureEnabled()) {
232
+ this.setupHeatmap();
233
+ }
234
+ if (this.consent.geo) {
235
+ this.captureGeoOnce();
164
236
  }
165
237
  }
166
- /** Grant consent and begin tracking */
167
- grantConsent() {
168
- this.consentGiven = true;
169
- if (this.config.autoPageview) {
170
- this.trackPageview();
238
+ /** Built-in consent popup with category toggles (analytics, heatmaps, geo). */
239
+ showConsentBanner() {
240
+ if (typeof document === "undefined") return;
241
+ if (document.getElementById("krypton-consent-banner")) return;
242
+ const banner = document.createElement("div");
243
+ banner.id = "krypton-consent-banner";
244
+ banner.style.cssText = [
245
+ "position:fixed",
246
+ "left:16px",
247
+ "right:16px",
248
+ "bottom:16px",
249
+ "z-index:2147483647",
250
+ "max-width:720px",
251
+ "margin:0 auto",
252
+ "background:#111827",
253
+ "color:#f9fafb",
254
+ "border-radius:12px",
255
+ "padding:16px",
256
+ "box-shadow:0 12px 30px rgba(0,0,0,0.35)",
257
+ "font-family:system-ui,-apple-system,Segoe UI,Roboto,sans-serif",
258
+ "font-size:14px",
259
+ "line-height:1.4"
260
+ ].join(";");
261
+ const checked = (value) => value ? "checked" : "";
262
+ banner.innerHTML = `
263
+ <div style="font-weight:600;margin-bottom:6px">Privacy preferences</div>
264
+ <div style="opacity:0.9;margin-bottom:10px">Choose what data you allow us to collect.</div>
265
+ <label style="display:flex;gap:8px;align-items:flex-start;margin:6px 0">
266
+ <input type="checkbox" id="krypton-consent-analytics" ${checked(this.consent.analytics)} />
267
+ <span><strong>Analytics</strong> (pageviews and custom events)</span>
268
+ </label>
269
+ <label style="display:flex;gap:8px;align-items:flex-start;margin:6px 0">
270
+ <input type="checkbox" id="krypton-consent-heatmaps" ${checked(this.consent.heatmaps)} />
271
+ <span><strong>Heatmaps</strong> (click and scroll interaction maps)</span>
272
+ </label>
273
+ <label style="display:flex;gap:8px;align-items:flex-start;margin:6px 0 12px 0">
274
+ <input type="checkbox" id="krypton-consent-geo" ${checked(this.consent.geo)} />
275
+ <span><strong>Geo location</strong> (browser geolocation if available)</span>
276
+ </label>
277
+ <div style="display:flex;gap:8px;flex-wrap:wrap">
278
+ <button id="krypton-consent-save" style="border:0;background:#2563eb;color:#fff;padding:8px 12px;border-radius:8px;cursor:pointer">Accept selected</button>
279
+ <button id="krypton-consent-all" style="border:0;background:#16a34a;color:#fff;padding:8px 12px;border-radius:8px;cursor:pointer">Accept all</button>
280
+ <button id="krypton-consent-none" style="border:1px solid #4b5563;background:transparent;color:#f9fafb;padding:8px 12px;border-radius:8px;cursor:pointer">Reject all</button>
281
+ </div>
282
+ `;
283
+ const remove = () => {
284
+ banner.remove();
285
+ };
286
+ const readValue = (id) => {
287
+ const el = document.getElementById(id);
288
+ return !!el?.checked;
289
+ };
290
+ banner.querySelector("#krypton-consent-save")?.addEventListener("click", () => {
291
+ this.setConsent({
292
+ analytics: readValue("krypton-consent-analytics"),
293
+ heatmaps: readValue("krypton-consent-heatmaps"),
294
+ geo: readValue("krypton-consent-geo")
295
+ });
296
+ remove();
297
+ });
298
+ banner.querySelector("#krypton-consent-all")?.addEventListener("click", () => {
299
+ this.setConsent({ analytics: true, heatmaps: true, geo: true });
300
+ remove();
301
+ });
302
+ banner.querySelector("#krypton-consent-none")?.addEventListener("click", () => {
303
+ this.setConsent({ analytics: false, heatmaps: false, geo: false });
304
+ remove();
305
+ });
306
+ document.body.appendChild(banner);
307
+ }
308
+ /** Set one or more consent categories and apply changes immediately. */
309
+ setConsent(next, persist = true) {
310
+ const prev = { ...this.consent };
311
+ this.consent = {
312
+ analytics: next.analytics ?? prev.analytics,
313
+ heatmaps: next.heatmaps ?? prev.heatmaps,
314
+ geo: next.geo ?? prev.geo
315
+ };
316
+ if (persist) {
317
+ this.persistConsent();
318
+ }
319
+ if (!prev.analytics && this.consent.analytics) {
320
+ this.promotePersistentIds();
321
+ if (this.config.autoPageview) {
322
+ this.trackPageview();
323
+ }
324
+ }
325
+ if (prev.analytics && !this.consent.analytics) {
326
+ this.eventQueue = [];
171
327
  }
172
- const heatmapEnabled = this.config.heatmap && (!this.configFetched || this.serverConfig?.heatmaps_enabled);
173
- if (heatmapEnabled) {
328
+ if (!prev.heatmaps && this.isHeatmapFeatureEnabled()) {
174
329
  this.setupHeatmap();
330
+ this.captureSnapshot();
331
+ }
332
+ if (prev.heatmaps && !this.consent.heatmaps) {
333
+ this.heatmapQueue = [];
334
+ }
335
+ if (!prev.geo && this.consent.geo) {
336
+ this.captureGeoOnce();
175
337
  }
176
338
  }
177
- /** Revoke consent and stop tracking */
339
+ getConsent() {
340
+ return { ...this.consent };
341
+ }
342
+ /** Backward-compatible: grant all categories. */
343
+ grantConsent() {
344
+ this.setConsent({ analytics: true, heatmaps: true, geo: true });
345
+ }
346
+ /** Backward-compatible: revoke all categories and clear queues. */
178
347
  revokeConsent() {
179
- this.consentGiven = false;
348
+ this.setConsent({ analytics: false, heatmaps: false, geo: false });
180
349
  this.eventQueue = [];
181
350
  this.heatmapQueue = [];
182
351
  }
183
352
  /** Identify a user with a custom distinct ID */
184
353
  identify(distinctId) {
185
354
  this.distinctId = distinctId;
186
- if (typeof localStorage !== "undefined") {
355
+ if (typeof localStorage !== "undefined" && this.canTrackAnalytics()) {
187
356
  localStorage.setItem("_krypton_did", distinctId);
188
357
  }
189
358
  }
190
359
  /** Track a pageview */
191
360
  trackPageview(properties) {
192
- if (!this.consentGiven) return;
361
+ if (!this.canTrackAnalytics()) return;
193
362
  this.track("$pageview", {
194
363
  ...properties
195
364
  });
196
365
  }
197
366
  /** Track a custom event */
198
367
  track(eventName, properties) {
199
- if (!this.consentGiven) return;
368
+ if (!this.canTrackAnalytics()) return;
200
369
  const utm = getUTMParams();
370
+ const mergedProperties = { ...properties || {} };
371
+ if (this.consent.geo && this.geoContext) {
372
+ mergedProperties.geo = this.geoContext;
373
+ }
201
374
  const event = {
202
375
  project_id: this.config.apiKey,
203
376
  distinct_id: this.distinctId,
204
377
  event_name: eventName,
205
378
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
206
- properties,
379
+ properties: mergedProperties,
207
380
  page_url: typeof window !== "undefined" ? window.location.href : "",
208
381
  page_title: typeof document !== "undefined" ? document.title : "",
209
382
  referrer: typeof document !== "undefined" ? document.referrer : "",
@@ -223,11 +396,9 @@ var Krypton = class {
223
396
  this.flush();
224
397
  }
225
398
  }
226
- /** Flush all queued events to the server */
227
399
  async flush() {
228
400
  await Promise.all([this.flushEvents(), this.flushHeatmapEvents()]);
229
401
  }
230
- /** Shutdown the SDK */
231
402
  shutdown() {
232
403
  if (this.flushTimer) {
233
404
  clearInterval(this.flushTimer);
@@ -235,7 +406,30 @@ var Krypton = class {
235
406
  }
236
407
  this.flush();
237
408
  }
238
- // ---- Private methods ----
409
+ captureGeoOnce() {
410
+ if (this.geoRequested) return;
411
+ if (!this.consent.geo) return;
412
+ if (typeof navigator === "undefined" || !navigator.geolocation) return;
413
+ this.geoRequested = true;
414
+ navigator.geolocation.getCurrentPosition(
415
+ (pos) => {
416
+ const round = (value, precision = 3) => Number(value.toFixed(precision));
417
+ this.geoContext = {
418
+ lat: round(pos.coords.latitude),
419
+ lon: round(pos.coords.longitude),
420
+ accuracy_m: Math.round(pos.coords.accuracy),
421
+ captured_at: (/* @__PURE__ */ new Date()).toISOString()
422
+ };
423
+ },
424
+ () => {
425
+ },
426
+ {
427
+ enableHighAccuracy: false,
428
+ maximumAge: 10 * 60 * 1e3,
429
+ timeout: 5e3
430
+ }
431
+ );
432
+ }
239
433
  async flushEvents() {
240
434
  if (this.eventQueue.length === 0) return;
241
435
  const events = this.eventQueue.splice(0, this.eventQueue.length);
@@ -278,6 +472,7 @@ var Krypton = class {
278
472
  }
279
473
  captureSnapshot() {
280
474
  if (typeof window === "undefined" || typeof document === "undefined") return;
475
+ if (!this.isHeatmapFeatureEnabled()) return;
281
476
  const currentUrl = window.location.href;
282
477
  if (this.lastSnapshotUrl === currentUrl) return;
283
478
  this.lastSnapshotUrl = currentUrl;
@@ -313,9 +508,11 @@ var Krypton = class {
313
508
  }
314
509
  setupHeatmap() {
315
510
  if (typeof window === "undefined") return;
511
+ if (this.heatmapSetup) return;
512
+ this.heatmapSetup = true;
316
513
  this.captureSnapshot();
317
514
  document.addEventListener("click", (e) => {
318
- if (!this.consentGiven) return;
515
+ if (!this.isHeatmapFeatureEnabled()) return;
319
516
  const target = e.target;
320
517
  this.heatmapQueue.push({
321
518
  project_id: this.config.apiKey,
@@ -336,7 +533,7 @@ var Krypton = class {
336
533
  let maxScrollDepth = 0;
337
534
  let scrollTimeout = null;
338
535
  window.addEventListener("scroll", () => {
339
- if (!this.consentGiven) return;
536
+ if (!this.isHeatmapFeatureEnabled()) return;
340
537
  const scrollTop = window.scrollY || document.documentElement.scrollTop;
341
538
  const docHeight = document.documentElement.scrollHeight - window.innerHeight;
342
539
  const depth = docHeight > 0 ? scrollTop / docHeight * 100 : 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kryptonhq/analytics",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Krypton Analytics JavaScript SDK — privacy-first analytics tracking",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",