@kryptonhq/analytics 0.1.0 → 0.1.2

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";try{if(typeof sessionStorage<"u"){let t=sessionStorage.getItem(e);return t||(t=H(),sessionStorage.setItem(e,t)),t}}catch{}return H()}function he(){let e="_krypton_did";try{if(typeof localStorage<"u"){let t=localStorage.getItem(e);return t||(t=H(),localStorage.setItem(e,t)),t}}catch{}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(){try{return typeof localStorage>"u"?!1:!!localStorage.getItem(this.consentStorageKey())}catch{return!1}}loadStoredConsent(){try{if(typeof localStorage>"u")return null;let t=localStorage.getItem(this.consentStorageKey());if(!t)return null;let r=JSON.parse(t);return{analytics:!!r.analytics,heatmaps:!!r.heatmaps,geo:!!r.geo}}catch{return null}}persistConsent(){try{if(typeof localStorage>"u")return;localStorage.setItem(this.consentStorageKey(),JSON.stringify(this.consent))}catch{}}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=null;try{r=sessionStorage.getItem(t)}catch{r=null}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;try{sessionStorage.setItem(t,JSON.stringify({...n,_ts:Date.now()}))}catch{}}}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){if(this.distinctId=t,this.canTrackAnalytics())try{typeof localStorage<"u"&&localStorage.setItem("_krypton_did",t)}catch{}}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
@@ -39,25 +39,31 @@ function generateId() {
39
39
  }
40
40
  function getSessionId() {
41
41
  const key = "_krypton_sid";
42
- if (typeof sessionStorage !== "undefined") {
43
- let sid = sessionStorage.getItem(key);
44
- if (!sid) {
45
- sid = generateId();
46
- sessionStorage.setItem(key, sid);
42
+ try {
43
+ if (typeof sessionStorage !== "undefined") {
44
+ let sid = sessionStorage.getItem(key);
45
+ if (!sid) {
46
+ sid = generateId();
47
+ sessionStorage.setItem(key, sid);
48
+ }
49
+ return sid;
47
50
  }
48
- return sid;
51
+ } catch {
49
52
  }
50
53
  return generateId();
51
54
  }
52
55
  function getDistinctId() {
53
56
  const key = "_krypton_did";
54
- if (typeof localStorage !== "undefined") {
55
- let did = localStorage.getItem(key);
56
- if (!did) {
57
- did = generateId();
58
- localStorage.setItem(key, did);
57
+ try {
58
+ if (typeof localStorage !== "undefined") {
59
+ let did = localStorage.getItem(key);
60
+ if (!did) {
61
+ did = generateId();
62
+ localStorage.setItem(key, did);
63
+ }
64
+ return did;
59
65
  }
60
- return did;
66
+ } catch {
61
67
  }
62
68
  return generateId();
63
69
  }
@@ -104,35 +110,108 @@ var Krypton = class {
104
110
  this.eventQueue = [];
105
111
  this.heatmapQueue = [];
106
112
  this.flushTimer = null;
107
- this.consentGiven = false;
108
113
  this.initialized = false;
109
114
  this.lastSnapshotUrl = null;
115
+ this.heatmapSetup = false;
116
+ this.geoRequested = false;
117
+ this.geoContext = null;
110
118
  this.serverConfig = null;
111
119
  this.configFetched = false;
112
120
  this.config = {
113
121
  autoPageview: true,
114
122
  heatmap: false,
115
123
  consentRequired: false,
124
+ showConsentBanner: false,
125
+ consentCategories: {},
116
126
  flushInterval: 5e3,
117
127
  batchSize: 20,
118
128
  ...config
119
129
  };
120
- this.distinctId = getDistinctId();
121
- this.sessionId = getSessionId();
122
- if (!this.config.consentRequired) {
123
- this.consentGiven = true;
130
+ this.distinctId = generateId();
131
+ this.sessionId = generateId();
132
+ this.consent = this.resolveInitialConsent();
133
+ if (this.canTrackAnalytics()) {
134
+ this.promotePersistentIds();
124
135
  }
125
136
  this.init();
126
137
  }
127
138
  init() {
128
139
  if (this.initialized) return;
129
140
  this.initialized = true;
130
- this.fetchConfig().then(() => this.setupTracking());
141
+ this.fetchConfig().then(() => {
142
+ this.setupTracking();
143
+ if (this.config.consentRequired && this.config.showConsentBanner && !this.hasStoredConsent()) {
144
+ this.showConsentBanner();
145
+ }
146
+ });
147
+ }
148
+ resolveInitialConsent() {
149
+ const stored = this.loadStoredConsent();
150
+ if (stored) return stored;
151
+ const base = this.config.consentRequired ? { analytics: false, heatmaps: false, geo: false } : {
152
+ analytics: true,
153
+ heatmaps: !!this.config.heatmap,
154
+ geo: false
155
+ };
156
+ return {
157
+ ...base,
158
+ ...this.config.consentCategories
159
+ };
160
+ }
161
+ consentStorageKey() {
162
+ return `_krypton_consent_${this.config.apiKey}`;
163
+ }
164
+ hasStoredConsent() {
165
+ try {
166
+ if (typeof localStorage === "undefined") return false;
167
+ return !!localStorage.getItem(this.consentStorageKey());
168
+ } catch {
169
+ return false;
170
+ }
171
+ }
172
+ loadStoredConsent() {
173
+ try {
174
+ if (typeof localStorage === "undefined") return null;
175
+ const raw = localStorage.getItem(this.consentStorageKey());
176
+ if (!raw) return null;
177
+ const parsed = JSON.parse(raw);
178
+ return {
179
+ analytics: !!parsed.analytics,
180
+ heatmaps: !!parsed.heatmaps,
181
+ geo: !!parsed.geo
182
+ };
183
+ } catch {
184
+ return null;
185
+ }
186
+ }
187
+ persistConsent() {
188
+ try {
189
+ if (typeof localStorage === "undefined") return;
190
+ localStorage.setItem(this.consentStorageKey(), JSON.stringify(this.consent));
191
+ } catch {
192
+ }
193
+ }
194
+ promotePersistentIds() {
195
+ this.distinctId = getDistinctId();
196
+ this.sessionId = getSessionId();
197
+ }
198
+ canTrackAnalytics() {
199
+ return !!this.consent.analytics;
200
+ }
201
+ isHeatmapFeatureEnabled() {
202
+ if (!this.config.heatmap) return false;
203
+ if (this.configFetched && !this.serverConfig?.heatmaps_enabled) return false;
204
+ return !!this.consent.heatmaps;
131
205
  }
132
206
  async fetchConfig() {
133
207
  if (typeof window === "undefined") return;
134
208
  const cacheKey = `_krypton_config_${this.config.apiKey}`;
135
- const cached = sessionStorage.getItem(cacheKey);
209
+ let cached = null;
210
+ try {
211
+ cached = sessionStorage.getItem(cacheKey);
212
+ } catch {
213
+ cached = null;
214
+ }
136
215
  if (cached) {
137
216
  try {
138
217
  const parsed = JSON.parse(cached);
@@ -152,83 +231,203 @@ var Krypton = class {
152
231
  const data = await res.json();
153
232
  this.serverConfig = data;
154
233
  this.configFetched = true;
155
- sessionStorage.setItem(cacheKey, JSON.stringify({ ...data, _ts: Date.now() }));
234
+ try {
235
+ sessionStorage.setItem(cacheKey, JSON.stringify({ ...data, _ts: Date.now() }));
236
+ } catch {
237
+ }
156
238
  }
157
239
  } catch {
158
240
  }
159
241
  }
160
242
  setupTracking() {
161
243
  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();
244
+ if (typeof window === "undefined") return;
245
+ window.addEventListener("beforeunload", () => this.flush());
246
+ if (this.config.autoPageview && this.canTrackAnalytics()) {
247
+ this.trackPageview();
248
+ }
249
+ const originalPushState = history.pushState;
250
+ history.pushState = (...args) => {
251
+ originalPushState.apply(history, args);
252
+ if (this.config.autoPageview && this.canTrackAnalytics()) {
253
+ setTimeout(() => this.trackPageview(), 0);
166
254
  }
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();
255
+ if (this.isHeatmapFeatureEnabled()) {
256
+ setTimeout(() => this.captureSnapshot(), 0);
257
+ }
258
+ };
259
+ const originalReplaceState = history.replaceState;
260
+ history.replaceState = (...args) => {
261
+ originalReplaceState.apply(history, args);
262
+ if (this.config.autoPageview && this.canTrackAnalytics()) {
263
+ setTimeout(() => this.trackPageview(), 0);
188
264
  }
265
+ if (this.isHeatmapFeatureEnabled()) {
266
+ setTimeout(() => this.captureSnapshot(), 0);
267
+ }
268
+ };
269
+ window.addEventListener("popstate", () => {
270
+ if (this.config.autoPageview && this.canTrackAnalytics()) {
271
+ setTimeout(() => this.trackPageview(), 0);
272
+ }
273
+ if (this.isHeatmapFeatureEnabled()) {
274
+ setTimeout(() => this.captureSnapshot(), 0);
275
+ }
276
+ });
277
+ if (this.isHeatmapFeatureEnabled()) {
278
+ this.setupHeatmap();
279
+ }
280
+ if (this.consent.geo) {
281
+ this.captureGeoOnce();
189
282
  }
190
283
  }
191
- /** Grant consent and begin tracking */
192
- grantConsent() {
193
- this.consentGiven = true;
194
- if (this.config.autoPageview) {
195
- this.trackPageview();
284
+ /** Built-in consent popup with category toggles (analytics, heatmaps, geo). */
285
+ showConsentBanner() {
286
+ if (typeof document === "undefined") return;
287
+ if (document.getElementById("krypton-consent-banner")) return;
288
+ const banner = document.createElement("div");
289
+ banner.id = "krypton-consent-banner";
290
+ banner.style.cssText = [
291
+ "position:fixed",
292
+ "left:16px",
293
+ "right:16px",
294
+ "bottom:16px",
295
+ "z-index:2147483647",
296
+ "max-width:720px",
297
+ "margin:0 auto",
298
+ "background:#111827",
299
+ "color:#f9fafb",
300
+ "border-radius:12px",
301
+ "padding:16px",
302
+ "box-shadow:0 12px 30px rgba(0,0,0,0.35)",
303
+ "font-family:system-ui,-apple-system,Segoe UI,Roboto,sans-serif",
304
+ "font-size:14px",
305
+ "line-height:1.4"
306
+ ].join(";");
307
+ const checked = (value) => value ? "checked" : "";
308
+ banner.innerHTML = `
309
+ <div style="font-weight:600;margin-bottom:6px">Privacy preferences</div>
310
+ <div style="opacity:0.9;margin-bottom:10px">Choose what data you allow us to collect.</div>
311
+ <label style="display:flex;gap:8px;align-items:flex-start;margin:6px 0">
312
+ <input type="checkbox" id="krypton-consent-analytics" ${checked(this.consent.analytics)} />
313
+ <span><strong>Analytics</strong> (pageviews and custom events)</span>
314
+ </label>
315
+ <label style="display:flex;gap:8px;align-items:flex-start;margin:6px 0">
316
+ <input type="checkbox" id="krypton-consent-heatmaps" ${checked(this.consent.heatmaps)} />
317
+ <span><strong>Heatmaps</strong> (click and scroll interaction maps)</span>
318
+ </label>
319
+ <label style="display:flex;gap:8px;align-items:flex-start;margin:6px 0 12px 0">
320
+ <input type="checkbox" id="krypton-consent-geo" ${checked(this.consent.geo)} />
321
+ <span><strong>Geo location</strong> (browser geolocation if available)</span>
322
+ </label>
323
+ <div style="display:flex;gap:8px;flex-wrap:wrap">
324
+ <button id="krypton-consent-save" style="border:0;background:#2563eb;color:#fff;padding:8px 12px;border-radius:8px;cursor:pointer">Accept selected</button>
325
+ <button id="krypton-consent-all" style="border:0;background:#16a34a;color:#fff;padding:8px 12px;border-radius:8px;cursor:pointer">Accept all</button>
326
+ <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>
327
+ </div>
328
+ `;
329
+ const remove = () => {
330
+ banner.remove();
331
+ };
332
+ const readValue = (id) => {
333
+ const el = document.getElementById(id);
334
+ return !!el?.checked;
335
+ };
336
+ banner.querySelector("#krypton-consent-save")?.addEventListener("click", () => {
337
+ this.setConsent({
338
+ analytics: readValue("krypton-consent-analytics"),
339
+ heatmaps: readValue("krypton-consent-heatmaps"),
340
+ geo: readValue("krypton-consent-geo")
341
+ });
342
+ remove();
343
+ });
344
+ banner.querySelector("#krypton-consent-all")?.addEventListener("click", () => {
345
+ this.setConsent({ analytics: true, heatmaps: true, geo: true });
346
+ remove();
347
+ });
348
+ banner.querySelector("#krypton-consent-none")?.addEventListener("click", () => {
349
+ this.setConsent({ analytics: false, heatmaps: false, geo: false });
350
+ remove();
351
+ });
352
+ document.body.appendChild(banner);
353
+ }
354
+ /** Set one or more consent categories and apply changes immediately. */
355
+ setConsent(next, persist = true) {
356
+ const prev = { ...this.consent };
357
+ this.consent = {
358
+ analytics: next.analytics ?? prev.analytics,
359
+ heatmaps: next.heatmaps ?? prev.heatmaps,
360
+ geo: next.geo ?? prev.geo
361
+ };
362
+ if (persist) {
363
+ this.persistConsent();
364
+ }
365
+ if (!prev.analytics && this.consent.analytics) {
366
+ this.promotePersistentIds();
367
+ if (this.config.autoPageview) {
368
+ this.trackPageview();
369
+ }
196
370
  }
197
- const heatmapEnabled = this.config.heatmap && (!this.configFetched || this.serverConfig?.heatmaps_enabled);
198
- if (heatmapEnabled) {
371
+ if (prev.analytics && !this.consent.analytics) {
372
+ this.eventQueue = [];
373
+ }
374
+ if (!prev.heatmaps && this.isHeatmapFeatureEnabled()) {
199
375
  this.setupHeatmap();
376
+ this.captureSnapshot();
377
+ }
378
+ if (prev.heatmaps && !this.consent.heatmaps) {
379
+ this.heatmapQueue = [];
380
+ }
381
+ if (!prev.geo && this.consent.geo) {
382
+ this.captureGeoOnce();
200
383
  }
201
384
  }
202
- /** Revoke consent and stop tracking */
385
+ getConsent() {
386
+ return { ...this.consent };
387
+ }
388
+ /** Backward-compatible: grant all categories. */
389
+ grantConsent() {
390
+ this.setConsent({ analytics: true, heatmaps: true, geo: true });
391
+ }
392
+ /** Backward-compatible: revoke all categories and clear queues. */
203
393
  revokeConsent() {
204
- this.consentGiven = false;
394
+ this.setConsent({ analytics: false, heatmaps: false, geo: false });
205
395
  this.eventQueue = [];
206
396
  this.heatmapQueue = [];
207
397
  }
208
398
  /** Identify a user with a custom distinct ID */
209
399
  identify(distinctId) {
210
400
  this.distinctId = distinctId;
211
- if (typeof localStorage !== "undefined") {
212
- localStorage.setItem("_krypton_did", distinctId);
401
+ if (this.canTrackAnalytics()) {
402
+ try {
403
+ if (typeof localStorage !== "undefined") {
404
+ localStorage.setItem("_krypton_did", distinctId);
405
+ }
406
+ } catch {
407
+ }
213
408
  }
214
409
  }
215
410
  /** Track a pageview */
216
411
  trackPageview(properties) {
217
- if (!this.consentGiven) return;
412
+ if (!this.canTrackAnalytics()) return;
218
413
  this.track("$pageview", {
219
414
  ...properties
220
415
  });
221
416
  }
222
417
  /** Track a custom event */
223
418
  track(eventName, properties) {
224
- if (!this.consentGiven) return;
419
+ if (!this.canTrackAnalytics()) return;
225
420
  const utm = getUTMParams();
421
+ const mergedProperties = { ...properties || {} };
422
+ if (this.consent.geo && this.geoContext) {
423
+ mergedProperties.geo = this.geoContext;
424
+ }
226
425
  const event = {
227
426
  project_id: this.config.apiKey,
228
427
  distinct_id: this.distinctId,
229
428
  event_name: eventName,
230
429
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
231
- properties,
430
+ properties: mergedProperties,
232
431
  page_url: typeof window !== "undefined" ? window.location.href : "",
233
432
  page_title: typeof document !== "undefined" ? document.title : "",
234
433
  referrer: typeof document !== "undefined" ? document.referrer : "",
@@ -248,11 +447,9 @@ var Krypton = class {
248
447
  this.flush();
249
448
  }
250
449
  }
251
- /** Flush all queued events to the server */
252
450
  async flush() {
253
451
  await Promise.all([this.flushEvents(), this.flushHeatmapEvents()]);
254
452
  }
255
- /** Shutdown the SDK */
256
453
  shutdown() {
257
454
  if (this.flushTimer) {
258
455
  clearInterval(this.flushTimer);
@@ -260,7 +457,30 @@ var Krypton = class {
260
457
  }
261
458
  this.flush();
262
459
  }
263
- // ---- Private methods ----
460
+ captureGeoOnce() {
461
+ if (this.geoRequested) return;
462
+ if (!this.consent.geo) return;
463
+ if (typeof navigator === "undefined" || !navigator.geolocation) return;
464
+ this.geoRequested = true;
465
+ navigator.geolocation.getCurrentPosition(
466
+ (pos) => {
467
+ const round = (value, precision = 3) => Number(value.toFixed(precision));
468
+ this.geoContext = {
469
+ lat: round(pos.coords.latitude),
470
+ lon: round(pos.coords.longitude),
471
+ accuracy_m: Math.round(pos.coords.accuracy),
472
+ captured_at: (/* @__PURE__ */ new Date()).toISOString()
473
+ };
474
+ },
475
+ () => {
476
+ },
477
+ {
478
+ enableHighAccuracy: false,
479
+ maximumAge: 10 * 60 * 1e3,
480
+ timeout: 5e3
481
+ }
482
+ );
483
+ }
264
484
  async flushEvents() {
265
485
  if (this.eventQueue.length === 0) return;
266
486
  const events = this.eventQueue.splice(0, this.eventQueue.length);
@@ -303,6 +523,7 @@ var Krypton = class {
303
523
  }
304
524
  captureSnapshot() {
305
525
  if (typeof window === "undefined" || typeof document === "undefined") return;
526
+ if (!this.isHeatmapFeatureEnabled()) return;
306
527
  const currentUrl = window.location.href;
307
528
  if (this.lastSnapshotUrl === currentUrl) return;
308
529
  this.lastSnapshotUrl = currentUrl;
@@ -338,9 +559,11 @@ var Krypton = class {
338
559
  }
339
560
  setupHeatmap() {
340
561
  if (typeof window === "undefined") return;
562
+ if (this.heatmapSetup) return;
563
+ this.heatmapSetup = true;
341
564
  this.captureSnapshot();
342
565
  document.addEventListener("click", (e) => {
343
- if (!this.consentGiven) return;
566
+ if (!this.isHeatmapFeatureEnabled()) return;
344
567
  const target = e.target;
345
568
  this.heatmapQueue.push({
346
569
  project_id: this.config.apiKey,
@@ -361,7 +584,7 @@ var Krypton = class {
361
584
  let maxScrollDepth = 0;
362
585
  let scrollTimeout = null;
363
586
  window.addEventListener("scroll", () => {
364
- if (!this.consentGiven) return;
587
+ if (!this.isHeatmapFeatureEnabled()) return;
365
588
  const scrollTop = window.scrollY || document.documentElement.scrollTop;
366
589
  const docHeight = document.documentElement.scrollHeight - window.innerHeight;
367
590
  const depth = docHeight > 0 ? scrollTop / docHeight * 100 : 0;
package/dist/index.mjs CHANGED
@@ -14,25 +14,31 @@ function generateId() {
14
14
  }
15
15
  function getSessionId() {
16
16
  const key = "_krypton_sid";
17
- if (typeof sessionStorage !== "undefined") {
18
- let sid = sessionStorage.getItem(key);
19
- if (!sid) {
20
- sid = generateId();
21
- sessionStorage.setItem(key, sid);
17
+ try {
18
+ if (typeof sessionStorage !== "undefined") {
19
+ let sid = sessionStorage.getItem(key);
20
+ if (!sid) {
21
+ sid = generateId();
22
+ sessionStorage.setItem(key, sid);
23
+ }
24
+ return sid;
22
25
  }
23
- return sid;
26
+ } catch {
24
27
  }
25
28
  return generateId();
26
29
  }
27
30
  function getDistinctId() {
28
31
  const key = "_krypton_did";
29
- if (typeof localStorage !== "undefined") {
30
- let did = localStorage.getItem(key);
31
- if (!did) {
32
- did = generateId();
33
- localStorage.setItem(key, did);
32
+ try {
33
+ if (typeof localStorage !== "undefined") {
34
+ let did = localStorage.getItem(key);
35
+ if (!did) {
36
+ did = generateId();
37
+ localStorage.setItem(key, did);
38
+ }
39
+ return did;
34
40
  }
35
- return did;
41
+ } catch {
36
42
  }
37
43
  return generateId();
38
44
  }
@@ -79,35 +85,108 @@ var Krypton = class {
79
85
  this.eventQueue = [];
80
86
  this.heatmapQueue = [];
81
87
  this.flushTimer = null;
82
- this.consentGiven = false;
83
88
  this.initialized = false;
84
89
  this.lastSnapshotUrl = null;
90
+ this.heatmapSetup = false;
91
+ this.geoRequested = false;
92
+ this.geoContext = null;
85
93
  this.serverConfig = null;
86
94
  this.configFetched = false;
87
95
  this.config = {
88
96
  autoPageview: true,
89
97
  heatmap: false,
90
98
  consentRequired: false,
99
+ showConsentBanner: false,
100
+ consentCategories: {},
91
101
  flushInterval: 5e3,
92
102
  batchSize: 20,
93
103
  ...config
94
104
  };
95
- this.distinctId = getDistinctId();
96
- this.sessionId = getSessionId();
97
- if (!this.config.consentRequired) {
98
- this.consentGiven = true;
105
+ this.distinctId = generateId();
106
+ this.sessionId = generateId();
107
+ this.consent = this.resolveInitialConsent();
108
+ if (this.canTrackAnalytics()) {
109
+ this.promotePersistentIds();
99
110
  }
100
111
  this.init();
101
112
  }
102
113
  init() {
103
114
  if (this.initialized) return;
104
115
  this.initialized = true;
105
- this.fetchConfig().then(() => this.setupTracking());
116
+ this.fetchConfig().then(() => {
117
+ this.setupTracking();
118
+ if (this.config.consentRequired && this.config.showConsentBanner && !this.hasStoredConsent()) {
119
+ this.showConsentBanner();
120
+ }
121
+ });
122
+ }
123
+ resolveInitialConsent() {
124
+ const stored = this.loadStoredConsent();
125
+ if (stored) return stored;
126
+ const base = this.config.consentRequired ? { analytics: false, heatmaps: false, geo: false } : {
127
+ analytics: true,
128
+ heatmaps: !!this.config.heatmap,
129
+ geo: false
130
+ };
131
+ return {
132
+ ...base,
133
+ ...this.config.consentCategories
134
+ };
135
+ }
136
+ consentStorageKey() {
137
+ return `_krypton_consent_${this.config.apiKey}`;
138
+ }
139
+ hasStoredConsent() {
140
+ try {
141
+ if (typeof localStorage === "undefined") return false;
142
+ return !!localStorage.getItem(this.consentStorageKey());
143
+ } catch {
144
+ return false;
145
+ }
146
+ }
147
+ loadStoredConsent() {
148
+ try {
149
+ if (typeof localStorage === "undefined") return null;
150
+ const raw = localStorage.getItem(this.consentStorageKey());
151
+ if (!raw) return null;
152
+ const parsed = JSON.parse(raw);
153
+ return {
154
+ analytics: !!parsed.analytics,
155
+ heatmaps: !!parsed.heatmaps,
156
+ geo: !!parsed.geo
157
+ };
158
+ } catch {
159
+ return null;
160
+ }
161
+ }
162
+ persistConsent() {
163
+ try {
164
+ if (typeof localStorage === "undefined") return;
165
+ localStorage.setItem(this.consentStorageKey(), JSON.stringify(this.consent));
166
+ } catch {
167
+ }
168
+ }
169
+ promotePersistentIds() {
170
+ this.distinctId = getDistinctId();
171
+ this.sessionId = getSessionId();
172
+ }
173
+ canTrackAnalytics() {
174
+ return !!this.consent.analytics;
175
+ }
176
+ isHeatmapFeatureEnabled() {
177
+ if (!this.config.heatmap) return false;
178
+ if (this.configFetched && !this.serverConfig?.heatmaps_enabled) return false;
179
+ return !!this.consent.heatmaps;
106
180
  }
107
181
  async fetchConfig() {
108
182
  if (typeof window === "undefined") return;
109
183
  const cacheKey = `_krypton_config_${this.config.apiKey}`;
110
- const cached = sessionStorage.getItem(cacheKey);
184
+ let cached = null;
185
+ try {
186
+ cached = sessionStorage.getItem(cacheKey);
187
+ } catch {
188
+ cached = null;
189
+ }
111
190
  if (cached) {
112
191
  try {
113
192
  const parsed = JSON.parse(cached);
@@ -127,83 +206,203 @@ var Krypton = class {
127
206
  const data = await res.json();
128
207
  this.serverConfig = data;
129
208
  this.configFetched = true;
130
- sessionStorage.setItem(cacheKey, JSON.stringify({ ...data, _ts: Date.now() }));
209
+ try {
210
+ sessionStorage.setItem(cacheKey, JSON.stringify({ ...data, _ts: Date.now() }));
211
+ } catch {
212
+ }
131
213
  }
132
214
  } catch {
133
215
  }
134
216
  }
135
217
  setupTracking() {
136
218
  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();
219
+ if (typeof window === "undefined") return;
220
+ window.addEventListener("beforeunload", () => this.flush());
221
+ if (this.config.autoPageview && this.canTrackAnalytics()) {
222
+ this.trackPageview();
223
+ }
224
+ const originalPushState = history.pushState;
225
+ history.pushState = (...args) => {
226
+ originalPushState.apply(history, args);
227
+ if (this.config.autoPageview && this.canTrackAnalytics()) {
228
+ setTimeout(() => this.trackPageview(), 0);
141
229
  }
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();
230
+ if (this.isHeatmapFeatureEnabled()) {
231
+ setTimeout(() => this.captureSnapshot(), 0);
232
+ }
233
+ };
234
+ const originalReplaceState = history.replaceState;
235
+ history.replaceState = (...args) => {
236
+ originalReplaceState.apply(history, args);
237
+ if (this.config.autoPageview && this.canTrackAnalytics()) {
238
+ setTimeout(() => this.trackPageview(), 0);
163
239
  }
240
+ if (this.isHeatmapFeatureEnabled()) {
241
+ setTimeout(() => this.captureSnapshot(), 0);
242
+ }
243
+ };
244
+ window.addEventListener("popstate", () => {
245
+ if (this.config.autoPageview && this.canTrackAnalytics()) {
246
+ setTimeout(() => this.trackPageview(), 0);
247
+ }
248
+ if (this.isHeatmapFeatureEnabled()) {
249
+ setTimeout(() => this.captureSnapshot(), 0);
250
+ }
251
+ });
252
+ if (this.isHeatmapFeatureEnabled()) {
253
+ this.setupHeatmap();
254
+ }
255
+ if (this.consent.geo) {
256
+ this.captureGeoOnce();
164
257
  }
165
258
  }
166
- /** Grant consent and begin tracking */
167
- grantConsent() {
168
- this.consentGiven = true;
169
- if (this.config.autoPageview) {
170
- this.trackPageview();
259
+ /** Built-in consent popup with category toggles (analytics, heatmaps, geo). */
260
+ showConsentBanner() {
261
+ if (typeof document === "undefined") return;
262
+ if (document.getElementById("krypton-consent-banner")) return;
263
+ const banner = document.createElement("div");
264
+ banner.id = "krypton-consent-banner";
265
+ banner.style.cssText = [
266
+ "position:fixed",
267
+ "left:16px",
268
+ "right:16px",
269
+ "bottom:16px",
270
+ "z-index:2147483647",
271
+ "max-width:720px",
272
+ "margin:0 auto",
273
+ "background:#111827",
274
+ "color:#f9fafb",
275
+ "border-radius:12px",
276
+ "padding:16px",
277
+ "box-shadow:0 12px 30px rgba(0,0,0,0.35)",
278
+ "font-family:system-ui,-apple-system,Segoe UI,Roboto,sans-serif",
279
+ "font-size:14px",
280
+ "line-height:1.4"
281
+ ].join(";");
282
+ const checked = (value) => value ? "checked" : "";
283
+ banner.innerHTML = `
284
+ <div style="font-weight:600;margin-bottom:6px">Privacy preferences</div>
285
+ <div style="opacity:0.9;margin-bottom:10px">Choose what data you allow us to collect.</div>
286
+ <label style="display:flex;gap:8px;align-items:flex-start;margin:6px 0">
287
+ <input type="checkbox" id="krypton-consent-analytics" ${checked(this.consent.analytics)} />
288
+ <span><strong>Analytics</strong> (pageviews and custom events)</span>
289
+ </label>
290
+ <label style="display:flex;gap:8px;align-items:flex-start;margin:6px 0">
291
+ <input type="checkbox" id="krypton-consent-heatmaps" ${checked(this.consent.heatmaps)} />
292
+ <span><strong>Heatmaps</strong> (click and scroll interaction maps)</span>
293
+ </label>
294
+ <label style="display:flex;gap:8px;align-items:flex-start;margin:6px 0 12px 0">
295
+ <input type="checkbox" id="krypton-consent-geo" ${checked(this.consent.geo)} />
296
+ <span><strong>Geo location</strong> (browser geolocation if available)</span>
297
+ </label>
298
+ <div style="display:flex;gap:8px;flex-wrap:wrap">
299
+ <button id="krypton-consent-save" style="border:0;background:#2563eb;color:#fff;padding:8px 12px;border-radius:8px;cursor:pointer">Accept selected</button>
300
+ <button id="krypton-consent-all" style="border:0;background:#16a34a;color:#fff;padding:8px 12px;border-radius:8px;cursor:pointer">Accept all</button>
301
+ <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>
302
+ </div>
303
+ `;
304
+ const remove = () => {
305
+ banner.remove();
306
+ };
307
+ const readValue = (id) => {
308
+ const el = document.getElementById(id);
309
+ return !!el?.checked;
310
+ };
311
+ banner.querySelector("#krypton-consent-save")?.addEventListener("click", () => {
312
+ this.setConsent({
313
+ analytics: readValue("krypton-consent-analytics"),
314
+ heatmaps: readValue("krypton-consent-heatmaps"),
315
+ geo: readValue("krypton-consent-geo")
316
+ });
317
+ remove();
318
+ });
319
+ banner.querySelector("#krypton-consent-all")?.addEventListener("click", () => {
320
+ this.setConsent({ analytics: true, heatmaps: true, geo: true });
321
+ remove();
322
+ });
323
+ banner.querySelector("#krypton-consent-none")?.addEventListener("click", () => {
324
+ this.setConsent({ analytics: false, heatmaps: false, geo: false });
325
+ remove();
326
+ });
327
+ document.body.appendChild(banner);
328
+ }
329
+ /** Set one or more consent categories and apply changes immediately. */
330
+ setConsent(next, persist = true) {
331
+ const prev = { ...this.consent };
332
+ this.consent = {
333
+ analytics: next.analytics ?? prev.analytics,
334
+ heatmaps: next.heatmaps ?? prev.heatmaps,
335
+ geo: next.geo ?? prev.geo
336
+ };
337
+ if (persist) {
338
+ this.persistConsent();
339
+ }
340
+ if (!prev.analytics && this.consent.analytics) {
341
+ this.promotePersistentIds();
342
+ if (this.config.autoPageview) {
343
+ this.trackPageview();
344
+ }
171
345
  }
172
- const heatmapEnabled = this.config.heatmap && (!this.configFetched || this.serverConfig?.heatmaps_enabled);
173
- if (heatmapEnabled) {
346
+ if (prev.analytics && !this.consent.analytics) {
347
+ this.eventQueue = [];
348
+ }
349
+ if (!prev.heatmaps && this.isHeatmapFeatureEnabled()) {
174
350
  this.setupHeatmap();
351
+ this.captureSnapshot();
352
+ }
353
+ if (prev.heatmaps && !this.consent.heatmaps) {
354
+ this.heatmapQueue = [];
355
+ }
356
+ if (!prev.geo && this.consent.geo) {
357
+ this.captureGeoOnce();
175
358
  }
176
359
  }
177
- /** Revoke consent and stop tracking */
360
+ getConsent() {
361
+ return { ...this.consent };
362
+ }
363
+ /** Backward-compatible: grant all categories. */
364
+ grantConsent() {
365
+ this.setConsent({ analytics: true, heatmaps: true, geo: true });
366
+ }
367
+ /** Backward-compatible: revoke all categories and clear queues. */
178
368
  revokeConsent() {
179
- this.consentGiven = false;
369
+ this.setConsent({ analytics: false, heatmaps: false, geo: false });
180
370
  this.eventQueue = [];
181
371
  this.heatmapQueue = [];
182
372
  }
183
373
  /** Identify a user with a custom distinct ID */
184
374
  identify(distinctId) {
185
375
  this.distinctId = distinctId;
186
- if (typeof localStorage !== "undefined") {
187
- localStorage.setItem("_krypton_did", distinctId);
376
+ if (this.canTrackAnalytics()) {
377
+ try {
378
+ if (typeof localStorage !== "undefined") {
379
+ localStorage.setItem("_krypton_did", distinctId);
380
+ }
381
+ } catch {
382
+ }
188
383
  }
189
384
  }
190
385
  /** Track a pageview */
191
386
  trackPageview(properties) {
192
- if (!this.consentGiven) return;
387
+ if (!this.canTrackAnalytics()) return;
193
388
  this.track("$pageview", {
194
389
  ...properties
195
390
  });
196
391
  }
197
392
  /** Track a custom event */
198
393
  track(eventName, properties) {
199
- if (!this.consentGiven) return;
394
+ if (!this.canTrackAnalytics()) return;
200
395
  const utm = getUTMParams();
396
+ const mergedProperties = { ...properties || {} };
397
+ if (this.consent.geo && this.geoContext) {
398
+ mergedProperties.geo = this.geoContext;
399
+ }
201
400
  const event = {
202
401
  project_id: this.config.apiKey,
203
402
  distinct_id: this.distinctId,
204
403
  event_name: eventName,
205
404
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
206
- properties,
405
+ properties: mergedProperties,
207
406
  page_url: typeof window !== "undefined" ? window.location.href : "",
208
407
  page_title: typeof document !== "undefined" ? document.title : "",
209
408
  referrer: typeof document !== "undefined" ? document.referrer : "",
@@ -223,11 +422,9 @@ var Krypton = class {
223
422
  this.flush();
224
423
  }
225
424
  }
226
- /** Flush all queued events to the server */
227
425
  async flush() {
228
426
  await Promise.all([this.flushEvents(), this.flushHeatmapEvents()]);
229
427
  }
230
- /** Shutdown the SDK */
231
428
  shutdown() {
232
429
  if (this.flushTimer) {
233
430
  clearInterval(this.flushTimer);
@@ -235,7 +432,30 @@ var Krypton = class {
235
432
  }
236
433
  this.flush();
237
434
  }
238
- // ---- Private methods ----
435
+ captureGeoOnce() {
436
+ if (this.geoRequested) return;
437
+ if (!this.consent.geo) return;
438
+ if (typeof navigator === "undefined" || !navigator.geolocation) return;
439
+ this.geoRequested = true;
440
+ navigator.geolocation.getCurrentPosition(
441
+ (pos) => {
442
+ const round = (value, precision = 3) => Number(value.toFixed(precision));
443
+ this.geoContext = {
444
+ lat: round(pos.coords.latitude),
445
+ lon: round(pos.coords.longitude),
446
+ accuracy_m: Math.round(pos.coords.accuracy),
447
+ captured_at: (/* @__PURE__ */ new Date()).toISOString()
448
+ };
449
+ },
450
+ () => {
451
+ },
452
+ {
453
+ enableHighAccuracy: false,
454
+ maximumAge: 10 * 60 * 1e3,
455
+ timeout: 5e3
456
+ }
457
+ );
458
+ }
239
459
  async flushEvents() {
240
460
  if (this.eventQueue.length === 0) return;
241
461
  const events = this.eventQueue.splice(0, this.eventQueue.length);
@@ -278,6 +498,7 @@ var Krypton = class {
278
498
  }
279
499
  captureSnapshot() {
280
500
  if (typeof window === "undefined" || typeof document === "undefined") return;
501
+ if (!this.isHeatmapFeatureEnabled()) return;
281
502
  const currentUrl = window.location.href;
282
503
  if (this.lastSnapshotUrl === currentUrl) return;
283
504
  this.lastSnapshotUrl = currentUrl;
@@ -313,9 +534,11 @@ var Krypton = class {
313
534
  }
314
535
  setupHeatmap() {
315
536
  if (typeof window === "undefined") return;
537
+ if (this.heatmapSetup) return;
538
+ this.heatmapSetup = true;
316
539
  this.captureSnapshot();
317
540
  document.addEventListener("click", (e) => {
318
- if (!this.consentGiven) return;
541
+ if (!this.isHeatmapFeatureEnabled()) return;
319
542
  const target = e.target;
320
543
  this.heatmapQueue.push({
321
544
  project_id: this.config.apiKey,
@@ -336,7 +559,7 @@ var Krypton = class {
336
559
  let maxScrollDepth = 0;
337
560
  let scrollTimeout = null;
338
561
  window.addEventListener("scroll", () => {
339
- if (!this.consentGiven) return;
562
+ if (!this.isHeatmapFeatureEnabled()) return;
340
563
  const scrollTop = window.scrollY || document.documentElement.scrollTop;
341
564
  const docHeight = document.documentElement.scrollHeight - window.innerHeight;
342
565
  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.2",
4
4
  "description": "Krypton Analytics JavaScript SDK — privacy-first analytics tracking",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",