@clickroom/sdk-js 0.2.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/clickroom.js CHANGED
@@ -1,2 +1 @@
1
- "use strict";(()=>{var ee=new Set(["a","button","input","select","textarea","label"]),te="ch-no-capture",ne="data-ch-no-capture";function S(e){return e.replace(/\\/g,"\\\\").replace(/"/g,'\\"').replace(/;/g,"\\;").replace(/\|/g,"\\|").replace(/\r?\n/g," ").trim()}function re(e){let t=1,n=e.previousElementSibling;for(;n;)t+=1,n=n.previousElementSibling;return t}function oe(e){let t=1,n=e.tagName,r=e.previousElementSibling;for(;r;)r.tagName===n&&(t+=1),r=r.previousElementSibling;return t}function ie(e){var r,i;let n=((i=(r=e.innerText)!=null?r:e.textContent)!=null?i:"").replace(/\s+/g," ").trim();return n.length>255?n.slice(0,255):n}function ae(e){let t=e.tagName.toLowerCase(),n=[],r=e.getAttribute("id");r&&n.push(`attr__id="${S(r)}"`);let i=typeof e.className=="string"?e.className:"";if(i&&n.push(`attr__class="${S(i)}"`),t==="a"){let l=e.getAttribute("href");l&&n.push(`href="${S(l)}"`)}let c=ie(e);c&&n.push(`text="${S(c)}"`),n.push(`nth-child="${re(e)}"`),n.push(`nth-of-type="${oe(e)}"`);let s=e.attributes;for(let l=0;l<s.length;l+=1){let f=s.item(l);f!=null&&f.name.startsWith("data-")&&n.push(`attr__${f.name}="${S(f.value)}"`)}return`${t}:${n.join(";")}`}function N(e){let t=[],n=e,r=0;for(;n&&r<15;){let i=n.tagName.toLowerCase();if(i==="body"||i==="html")break;t.push(ae(n)),n=n.parentElement,r+=1}return t.join("|")}function se(e){var n;let t=e;for(;t;){if((n=t.classList)!=null&&n.contains(te)||t.hasAttribute(ne))return!0;t=t.parentElement}return!1}function ce(e){let t=e.tagName.toLowerCase();return!!(ee.has(t)||e.hasAttribute("data-ch-capture")||e.getAttribute("role")||e.hasAttribute("onclick"))}function D(e){let t=e,n=0;for(;t&&n<15;){if(se(t))return null;if(ce(t))return t;t=t.parentElement,n+=1}return null}function H(){return{$current_url:location.href,$host:location.host,$pathname:location.pathname}}function B(e){if(typeof document=="undefined"||typeof window=="undefined")return()=>{};let t=f=>{try{let u=f.target;if(!(u instanceof Element))return;let a=D(u);if(!a)return;let g=N(a);e.capture("$autocapture",{$event_type:f.type,...H()},g)}catch(u){}},n=["click","submit","change"];if(e.config.autocapture)for(let f of n)document.addEventListener(f,t,!0);let r="",i=()=>{try{if(!e.config.capture_pageview||location.href===r)return;r=location.href,e.capture("$pageview",{...H(),$referrer:document.referrer})}catch(f){}},c=history.pushState.bind(history),s=history.replaceState.bind(history),l=!1;return e.config.capture_pageview&&(history.pushState=(...f)=>{c(...f),i()},history.replaceState=(...f)=>{s(...f),i()},l=!0,window.addEventListener("popstate",i),window.addEventListener("hashchange",i),i()),()=>{if(e.config.autocapture)for(let f of n)document.removeEventListener(f,t,!0);l&&(history.pushState=c,history.replaceState=s,window.removeEventListener("popstate",i),window.removeEventListener("hashchange",i))}}var le=/^(cpc|ppc|paid|display)$/i,ue=["google.","bing.com","duckduckgo.com","yahoo.","baidu.com","yandex.","ecosia.org","ask.com","aol.com"],fe=["facebook.com","fb.com","twitter.com","x.com","t.co","linkedin.com","instagram.com","tiktok.com","reddit.com","pinterest.","youtube.com","threads.net"],U=(e,t)=>t.some(n=>e.includes(n)),de=e=>{if(e)try{return new URL(e).hostname.toLowerCase()}catch(t){return}};function V(e,t,n){let r=de(e),i=t.$utm_medium,c=!!r,s=Object.keys(t).length>0;return!c&&!s?"direct":i&&le.test(i)?"paid":r&&U(r,ue)?"organic search":r&&U(r,fe)?"social":i&&i.toLowerCase()==="email"?"email":r&&r!==n.toLowerCase()?"referral":"direct"}var ge=[{name:"Edge",re:/Edg(?:e|A|iOS)?\/(\d+)/},{name:"Opera",re:/(?:OPR|Opera)\/(\d+)/},{name:"Samsung Internet",re:/SamsungBrowser\/(\d+)/},{name:"Firefox",re:/(?:Firefox|FxiOS)\/(\d+)/},{name:"Chrome",re:/(?:Chrome|CriOS)\/(\d+)/},{name:"Safari",re:/Version\/(\d+).*Safari/}],pe=e=>{for(let t of ge){let n=e.match(t.re);if(n)return{name:t.name,version:n[1]}}return{}},me=e=>{if(/Windows/.test(e))return"Windows";if(/iPhone|iPad|iPod/.test(e))return"iOS";if(/Android/.test(e))return"Android";if(/CrOS/.test(e))return"Chrome OS";if(/Mac OS X|Macintosh/.test(e))return"macOS";if(/Linux/.test(e))return"Linux"},he=e=>/iPad/.test(e)?"Tablet":/Android/.test(e)?/Mobile/.test(e)?"Mobile":"Tablet":/iPhone|iPod/.test(e)||/Mobile/.test(e)?"Mobile":"Desktop";function X(e,t){let n={},r=e!=null?e:"";if(r){let i=pe(r);i.name&&(n.$browser=i.name),i.version&&(n.$browser_version=i.version);let c=me(r);c&&(n.$os=c),n.$device_type=he(r)}return t&&(n.$lang=t),n}var ze=Number(BigInt("0xfffffffffffffff"));var R="ckrm_flags",P=()=>{try{return typeof localStorage=="undefined"?null:localStorage}catch(e){return null}};function j(e){let t=n();function n(){var u;try{let a=(u=P())==null?void 0:u.getItem(R);if(!a)return{};let g=JSON.parse(a);if(g&&typeof g=="object")return g}catch(a){}return{}}let r=u=>{var a;try{(a=P())==null||a.setItem(R,JSON.stringify(u))}catch(g){}},i,c=()=>`${e.getApiHost().replace(/\/+$/,"")}/v1/flags`,s=()=>`${e.getApiHost().replace(/\/+$/,"")}/v1/flags/version`,l=async()=>{try{if(typeof fetch!="function")return t;let u=await fetch(c(),{method:"POST",headers:{"content-type":"application/json",authorization:`Bearer ${e.getWriteKey()}`},body:JSON.stringify({distinct_id:e.getDistinctId(),person_properties:e.getPersonProperties()})});if(!u.ok)return t;let a=await u.json(),g=a==null?void 0:a.flags;return g&&typeof g=="object"&&(t=g,r(t)),t}catch(u){return t}};return{getCache:()=>t,isFeatureEnabled:u=>{var a,g;return(g=(a=t[u])==null?void 0:a.enabled)!=null?g:!1},getFeatureFlag:u=>{let a=t[u];return a!=null&&a.enabled?a.variant!=null?a.variant:!0:!1},getFeatureFlagPayload:u=>{var a,g;return(g=(a=t[u])==null?void 0:a.payload)!=null?g:null},fetchFlags:l,checkVersionAndMaybeFetch:async()=>{let u=!0,a;try{if(typeof fetch=="function"){let w=await fetch(s(),{method:"GET",headers:{authorization:`Bearer ${e.getWriteKey()}`}});if(w.ok){let y=await w.json();a=y==null?void 0:y.version,u=i===void 0||a!==i}}}catch(w){}if(!u)return{changed:!1,flags:t};let g=await l();return a!==void 0&&(i=a),{changed:!0,flags:g}},clear:()=>{var u;t={};try{(u=P())==null||u.removeItem(R)}catch(a){}}}}var x="ckrm_sid",ye=e=>{if(!e)return null;try{let t=JSON.parse(e);if(t&&typeof t=="object"&&typeof t.id=="string"&&typeof t.start=="number"&&typeof t.last=="number")return t}catch(t){}return null};function K({now:e,storage:t,genId:n}){let r=()=>{var s,l;try{return ye((l=(s=t())==null?void 0:s.getItem(x))!=null?l:null)}catch(f){return null}},i=s=>{var l;try{(l=t())==null||l.setItem(x,JSON.stringify(s))}catch(f){}},c=s=>{let l={id:n(),start:s,last:s};return i(l),l};return{getSessionId(){let s=e(),l=r();return!l||s-l.last>18e5||s-l.start>864e5?c(s).id:(l.last=s,i(l),l.id)},reset(){var s;try{(s=t())==null||s.removeItem(x)}catch(l){}}}}var A=()=>typeof window!="undefined";function G(e,t){let n=[],r=[],i=0,c=null,s=null,l=()=>{let{api_host:d}=e();return`${d.replace(/\/+$/,"")}/v1/capture?token=${encodeURIComponent(t())}`},f=d=>JSON.stringify({batch:d}),u=()=>{if(c!==null||r.length===0)return;let d=Math.min(1e3*2**i,3e4);try{c=setTimeout(()=>{c=null,v(!1)},d)}catch(m){c=null}},a=d=>{r=r.concat(d),r.length>1e3&&(r=r.slice(r.length-1e3)),i=Math.min(i+1,5),u()},g=(d,m)=>{try{if(typeof navigator=="undefined"||typeof navigator.sendBeacon!="function")return!1;let p=new Blob([m],{type:"text/plain"});return navigator.sendBeacon(d,p)}catch(p){return!1}},w=async(d,m)=>{let p;try{p=await d.json()}catch(h){}console.warn(`[clickroom] capture() batch rejected by server (HTTP ${d.status}); dropping ${m.length} event(s)`,p)},y=(d,m,p)=>{try{if(typeof fetch!="function"){a(p);return}fetch(d,{method:"POST",keepalive:!0,headers:{"content-type":"application/json"},body:m}).then(h=>{if(h.ok){i=0,r.length>0&&u();return}if(h.status>=500){a(p);return}w(h,p)}).catch(()=>{a(p)})}catch(h){a(p)}};function v(d=!1){if(!A()){n=[];return}let m=r.concat(n);if(n=[],r=[],m.length!==0)try{let p=l();for(let h=0;h<m.length;h+=100){let k=m.slice(h,h+100),I=f(k);if(!(d&&g(p,I))){if(d){y(p,I,k);continue}y(p,I,k)}}}catch(p){}}let O=()=>{try{typeof document!="undefined"&&document.hidden&&v(!0)}catch(d){}},_=()=>{v(!0)},Z=d=>{if(A())try{n.push(d),n.length>=20&&v(!1)}catch(m){}},Q=()=>{try{s!==null&&(clearInterval(s),s=null),c!==null&&(clearTimeout(c),c=null),typeof document!="undefined"&&document.removeEventListener("visibilitychange",O),typeof window!="undefined"&&(window.removeEventListener("pagehide",_),window.removeEventListener("beforeunload",_))}catch(d){}};if(A())try{s=setInterval(()=>v(!1),3e3),typeof document!="undefined"&&document.addEventListener("visibilitychange",O),window.addEventListener("pagehide",_),window.addEventListener("beforeunload",_)}catch(d){}return{enqueue:Z,flush:v,teardown:Q}}var ve=["source","medium","campaign","term","content"];function z(e){let t={};if(!e)return t;try{let n=new URLSearchParams(e);for(let r of ve){let i=n.get(`utm_${r}`);i&&(t[`$utm_${r}`]=i)}}catch(n){}return t}var we="clickroom-js",Se="0.1.0",F="ckrm_did",be="ckrm_",E={api_host:"https://sdk.clickroom.co",autocapture:!0,capture_pageview:!0},_e=()=>typeof window!="undefined",Ee=5e3,o={initialized:!1,warnedNotInit:!1,writeKey:"",config:{...E},distinctId:"",personProperties:{},transport:null,session:null,sessionContext:null,teardownAutocapture:null,flagListeners:[],flagClient:null,flagsLoaded:!1,emittedFlagCalls:new Set,flagPollId:null},M=()=>{try{return typeof localStorage=="undefined"?null:localStorage}catch(e){return null}},Fe=()=>{try{return typeof sessionStorage=="undefined"?null:sessionStorage}catch(e){return null}},Ce=e=>{var t,n;try{return(n=(t=M())==null?void 0:t.getItem(e))!=null?n:null}catch(r){return null}},T=(e,t)=>{var n;try{(n=M())==null||n.setItem(e,t)}catch(r){}},Me=e=>{var t;try{(t=M())==null||t.removeItem(e)}catch(n){}},$=()=>{try{if(typeof crypto!="undefined"&&typeof crypto.randomUUID=="function")return crypto.randomUUID()}catch(e){}return`ckrm_${Date.now().toString(36)}_${Math.random().toString(36).slice(2,12)}`},ke=()=>{let e=Ce(F);if(e)return e;let t=$();return T(F,t),t},Ie=()=>{let e={$lib:we,$lib_version:Se};try{typeof location!="undefined"&&(e.$current_url=location.href,e.$host=location.host,e.$pathname=location.pathname),typeof document!="undefined"&&(e.$referrer=document.referrer),typeof window!="undefined"&&window.screen&&(e.$screen_height=window.screen.height,e.$screen_width=window.screen.width),typeof window!="undefined"&&(e.$viewport_height=window.innerHeight,e.$viewport_width=window.innerWidth),typeof navigator!="undefined"&&Object.assign(e,X(navigator.userAgent,navigator.language))}catch(t){}return e},W=()=>{try{let e=typeof location!="undefined"?location.search:"",t=typeof document!="undefined"?document.referrer:"",n=typeof location!="undefined"?location.hostname:"",r=z(e);return{...r,$channel_type:V(t,r,n)}}catch(e){return{}}},Re=e=>e?((!o.sessionContext||o.sessionContext.sessionId!==e)&&(o.sessionContext={sessionId:e,props:W()}),o.sessionContext.props):W(),Y=()=>{if(!o.warnedNotInit){o.warnedNotInit=!0;try{console.warn("[clickroom] capture() called before init(); event dropped")}catch(e){}}},b=(e,t,n)=>{var r;if(!o.initialized||!o.transport){Y();return}try{let i=(r=o.session)==null?void 0:r.getSessionId(),c=i?{$session_id:i}:{},s={event:e,distinct_id:o.distinctId,timestamp:new Date().toISOString(),properties:{...Ie(),...c,...Re(i),...t!=null?t:{}}};n&&(s.elements_chain=n),o.transport.enqueue(s)}catch(i){}},J=e=>{let t={};for(let[n,r]of Object.entries(e))r.enabled&&(t[n]=r.variant!=null?r.variant:!0);return t},Pe=()=>{if(!o.flagClient)return;let e=J(o.flagClient.getCache());for(let t of o.flagListeners)try{t(e)}catch(n){}},C=()=>{o.flagClient&&o.flagClient.checkVersionAndMaybeFetch().then(()=>{o.flagsLoaded=!0,Pe()}).catch(()=>{})},q=(e,t)=>{try{if(!o.flagsLoaded)return;let n=String(t),r=`${e}:${n}`;if(o.emittedFlagCalls.has(r))return;o.emittedFlagCalls.add(r),b("$feature_flag_called",{$feature_flag:e,$feature_flag_response:n})}catch(n){}};function xe(e,t){var n,r,i;if(!o.initialized&&_e()){if(o.writeKey=e,o.config={api_host:(n=t==null?void 0:t.api_host)!=null?n:E.api_host,autocapture:(r=t==null?void 0:t.autocapture)!=null?r:E.autocapture,capture_pageview:(i=t==null?void 0:t.capture_pageview)!=null?i:E.capture_pageview},o.distinctId=ke(),o.session=K({now:()=>Date.now(),storage:Fe,genId:$}),o.transport=G(()=>o.config,()=>o.writeKey),o.flagClient=j({getApiHost:()=>o.config.api_host,getWriteKey:()=>o.writeKey,getDistinctId:()=>o.distinctId,getPersonProperties:()=>o.personProperties}),o.initialized=!0,C(),t!=null&&t.feature_flag_poll_interval){let c=Math.max(t.feature_flag_poll_interval,Ee);o.flagPollId=setInterval(()=>C(),c)}if(o.config.autocapture){let c={config:o.config,capture:b};try{o.teardownAutocapture=B(c)}catch(s){}}else o.config.capture_pageview&&b("$pageview")}}function Ae(e,t){b(e,t)}function Te(e,t){if(!o.initialized){Y();return}let n=o.distinctId;o.distinctId=e,T(F,e),t&&(o.personProperties={...o.personProperties,...t}),b("$identify",{$set:t!=null?t:{},...n!==e?{$anon_distinct_id:n}:{}}),o.emittedFlagCalls.clear(),C()}function $e(){var e,t;try{let n=M();if(n){let r=[];for(let i=0;i<n.length;i+=1){let c=n.key(i);c!=null&&c.startsWith(be)&&r.push(c)}for(let i of r)Me(i)}}catch(n){}o.distinctId=$(),T(F,o.distinctId),(e=o.session)==null||e.reset(),o.sessionContext=null,o.personProperties={},o.flagsLoaded=!1,o.emittedFlagCalls.clear(),(t=o.flagClient)==null||t.clear()}function Le(e){var n,r;let t=(r=(n=o.flagClient)==null?void 0:n.isFeatureEnabled(e))!=null?r:!1;return q(e,t),t}function Oe(e){var n,r;let t=(r=(n=o.flagClient)==null?void 0:n.getFeatureFlag(e))!=null?r:!1;return q(e,t),t}function Ne(e){var t,n;return(n=(t=o.flagClient)==null?void 0:t.getFeatureFlagPayload(e))!=null?n:null}function De(e){if(o.flagListeners.push(e),o.flagsLoaded&&o.flagClient)try{e(J(o.flagClient.getCache()))}catch(t){}}function He(){C()}function Be(){o.flagPollId!==null&&(clearInterval(o.flagPollId),o.flagPollId=null)}var Ue={init:xe,capture:Ae,identify:Te,reset:$e,isFeatureEnabled:Le,getFeatureFlag:Oe,getFeatureFlagPayload:Ne,onFeatureFlags:De,refreshFeatureFlags:He,stopFeatureFlagPolling:Be};var L=Ue;typeof window!="undefined"&&(window.clickroom=L);var st=L;})();
2
- //# sourceMappingURL=clickroom.js.map
1
+ "use strict";(()=>{var ee=new Set(["a","button","input","select","textarea","label"]),te="ch-no-capture",ne="data-ch-no-capture";function _(e){return e.replace(/\\/g,"\\\\").replace(/"/g,'\\"').replace(/;/g,"\\;").replace(/\|/g,"\\|").replace(/\r?\n/g," ").trim()}function re(e){let t=1,n=e.previousElementSibling;for(;n;)t+=1,n=n.previousElementSibling;return t}function oe(e){let t=1,n=e.tagName,r=e.previousElementSibling;for(;r;)r.tagName===n&&(t+=1),r=r.previousElementSibling;return t}function ie(e){var r,i;let n=((i=(r=e.innerText)!=null?r:e.textContent)!=null?i:"").replace(/\s+/g," ").trim();return n.length>255?n.slice(0,255):n}function ae(e){let t=e.tagName.toLowerCase(),n=[],r=e.getAttribute("id");r&&n.push(`attr__id="${_(r)}"`);let i=typeof e.className=="string"?e.className:"";if(i&&n.push(`attr__class="${_(i)}"`),t==="a"){let c=e.getAttribute("href");c&&n.push(`href="${_(c)}"`)}let l=ie(e);l&&n.push(`text="${_(l)}"`),n.push(`nth-child="${re(e)}"`),n.push(`nth-of-type="${oe(e)}"`);let s=e.attributes;for(let c=0;c<s.length;c+=1){let f=s.item(c);f!=null&&f.name.startsWith("data-")&&n.push(`attr__${f.name}="${_(f.value)}"`)}return`${t}:${n.join(";")}`}function N(e){let t=[],n=e,r=0;for(;n&&r<15;){let i=n.tagName.toLowerCase();if(i==="body"||i==="html")break;t.push(ae(n)),n=n.parentElement,r+=1}return t.join("|")}function se(e){var n;let t=e;for(;t;){if((n=t.classList)!=null&&n.contains(te)||t.hasAttribute(ne))return!0;t=t.parentElement}return!1}function le(e){let t=e.tagName.toLowerCase();return!!(ee.has(t)||e.hasAttribute("data-ch-capture")||e.getAttribute("role")||e.hasAttribute("onclick"))}function D(e){let t=e,n=0;for(;t&&n<15;){if(se(t))return null;if(le(t))return t;t=t.parentElement,n+=1}return null}function H(){return{$current_url:location.href,$host:location.host,$pathname:location.pathname}}function B(e){if(typeof document=="undefined"||typeof window=="undefined")return()=>{};let t=f=>{try{let u=f.target;if(!(u instanceof Element))return;let a=D(u);if(!a)return;let g=N(a);e.capture("$autocapture",{$event_type:f.type,...H()},g)}catch(u){}},n=["click","submit","change"];if(e.config.autocapture)for(let f of n)document.addEventListener(f,t,!0);let r="",i=()=>{try{if(!e.config.capture_pageview||location.href===r)return;r=location.href,e.capture("$pageview",{...H(),$referrer:document.referrer})}catch(f){}},l=history.pushState.bind(history),s=history.replaceState.bind(history),c=!1;return e.config.capture_pageview&&(history.pushState=(...f)=>{l(...f),i()},history.replaceState=(...f)=>{s(...f),i()},c=!0,window.addEventListener("popstate",i),window.addEventListener("hashchange",i),i()),()=>{if(e.config.autocapture)for(let f of n)document.removeEventListener(f,t,!0);c&&(history.pushState=l,history.replaceState=s,window.removeEventListener("popstate",i),window.removeEventListener("hashchange",i))}}var ce=/^(cpc|ppc|paid|display)$/i,ue=["google.","bing.com","duckduckgo.com","yahoo.","baidu.com","yandex.","ecosia.org","ask.com","aol.com"],fe=["facebook.com","fb.com","twitter.com","x.com","t.co","linkedin.com","instagram.com","tiktok.com","reddit.com","pinterest.","youtube.com","threads.net"],U=(e,t)=>t.some(n=>e.includes(n)),de=e=>{if(e)try{return new URL(e).hostname.toLowerCase()}catch(t){return}};function V(e,t,n){let r=de(e),i=t.$utm_medium,l=!!r,s=Object.keys(t).length>0;return!l&&!s?"direct":i&&ce.test(i)?"paid":r&&U(r,ue)?"organic search":r&&U(r,fe)?"social":i&&i.toLowerCase()==="email"?"email":r&&r!==n.toLowerCase()?"referral":"direct"}var ge=[{name:"Edge",re:/Edg(?:e|A|iOS)?\/(\d+)/},{name:"Opera",re:/(?:OPR|Opera)\/(\d+)/},{name:"Samsung Internet",re:/SamsungBrowser\/(\d+)/},{name:"Firefox",re:/(?:Firefox|FxiOS)\/(\d+)/},{name:"Chrome",re:/(?:Chrome|CriOS)\/(\d+)/},{name:"Safari",re:/Version\/(\d+).*Safari/}],pe=e=>{for(let t of ge){let n=e.match(t.re);if(n)return{name:t.name,version:n[1]}}return{}},me=e=>{if(/Windows/.test(e))return"Windows";if(/iPhone|iPad|iPod/.test(e))return"iOS";if(/Android/.test(e))return"Android";if(/CrOS/.test(e))return"Chrome OS";if(/Mac OS X|Macintosh/.test(e))return"macOS";if(/Linux/.test(e))return"Linux"},he=e=>/iPad/.test(e)?"Tablet":/Android/.test(e)?/Mobile/.test(e)?"Mobile":"Tablet":/iPhone|iPod/.test(e)||/Mobile/.test(e)?"Mobile":"Desktop";function X(e,t){let n={},r=e!=null?e:"";if(r){let i=pe(r);i.name&&(n.$browser=i.name),i.version&&(n.$browser_version=i.version);let l=me(r);l&&(n.$os=l),n.$device_type=he(r)}return t&&(n.$lang=t),n}var ze=Number(BigInt("0xfffffffffffffff"));var R="ckrm_flags",P=()=>{try{return typeof localStorage=="undefined"?null:localStorage}catch(e){return null}};function j(e){let t=n();function n(){var u;try{let a=(u=P())==null?void 0:u.getItem(R);if(!a)return{};let g=JSON.parse(a);if(g&&typeof g=="object")return g}catch(a){}return{}}let r=u=>{var a;try{(a=P())==null||a.setItem(R,JSON.stringify(u))}catch(g){}},i,l=()=>`${e.getApiHost().replace(/\/+$/,"")}/v1/flags`,s=()=>`${e.getApiHost().replace(/\/+$/,"")}/v1/flags/version`,c=async()=>{try{if(typeof fetch!="function")return t;let u=await fetch(l(),{method:"POST",headers:{"content-type":"application/json",authorization:`Bearer ${e.getWriteKey()}`},body:JSON.stringify({distinct_id:e.getDistinctId(),person_properties:e.getPersonProperties()})});if(!u.ok)return t;let a=await u.json(),g=a==null?void 0:a.flags;return g&&typeof g=="object"&&(t=g,r(t)),t}catch(u){return t}};return{getCache:()=>t,isFeatureEnabled:u=>{var a,g;return(g=(a=t[u])==null?void 0:a.enabled)!=null?g:!1},getFeatureFlag:u=>{let a=t[u];return a!=null&&a.enabled?a.variant!=null?a.variant:!0:!1},getFeatureFlagPayload:u=>{var a,g;return(g=(a=t[u])==null?void 0:a.payload)!=null?g:null},fetchFlags:c,checkVersionAndMaybeFetch:async()=>{let u=!0,a;try{if(typeof fetch=="function"){let w=await fetch(s(),{method:"GET",headers:{authorization:`Bearer ${e.getWriteKey()}`}});if(w.ok){let y=await w.json();a=y==null?void 0:y.version,u=i===void 0||a!==i}}}catch(w){}if(!u)return{changed:!1,flags:t};let g=await c();return a!==void 0&&(i=a),{changed:!0,flags:g}},clear:()=>{var u;t={};try{(u=P())==null||u.removeItem(R)}catch(a){}}}}var x="ckrm_sid",ye=e=>{if(!e)return null;try{let t=JSON.parse(e);if(t&&typeof t=="object"&&typeof t.id=="string"&&typeof t.start=="number"&&typeof t.last=="number")return t}catch(t){}return null};function G({now:e,storage:t,genId:n}){let r=()=>{var s,c;try{return ye((c=(s=t())==null?void 0:s.getItem(x))!=null?c:null)}catch(f){return null}},i=s=>{var c;try{(c=t())==null||c.setItem(x,JSON.stringify(s))}catch(f){}},l=s=>{let c={id:n(),start:s,last:s};return i(c),c};return{getSessionId(){let s=e(),c=r();return!c||s-c.last>18e5||s-c.start>864e5?l(s).id:(c.last=s,i(c),c.id)},reset(){var s;try{(s=t())==null||s.removeItem(x)}catch(c){}}}}var A=()=>typeof window!="undefined";function K(e,t){let n=[],r=[],i=0,l=null,s=null,c=()=>{let{api_host:d}=e();return`${d.replace(/\/+$/,"")}/v1/capture?token=${encodeURIComponent(t())}`},f=d=>JSON.stringify({batch:d}),u=()=>{if(l!==null||r.length===0)return;let d=Math.min(1e3*2**i,3e4);try{l=setTimeout(()=>{l=null,v(!1)},d)}catch(m){l=null}},a=d=>{r=r.concat(d),r.length>1e3&&(r=r.slice(r.length-1e3)),i=Math.min(i+1,5),u()},g=(d,m)=>{try{if(typeof navigator=="undefined"||typeof navigator.sendBeacon!="function")return!1;let p=new Blob([m],{type:"text/plain"});return navigator.sendBeacon(d,p)}catch(p){return!1}},w=async(d,m)=>{let p;try{p=await d.json()}catch(h){}console.warn(`[clickroom] capture() batch rejected by server (HTTP ${d.status}); dropping ${m.length} event(s)`,p)},y=(d,m,p)=>{try{if(typeof fetch!="function"){a(p);return}fetch(d,{method:"POST",keepalive:!0,headers:{"content-type":"application/json"},body:m}).then(h=>{if(h.ok){i=0,r.length>0&&u();return}if(h.status>=500){a(p);return}w(h,p)}).catch(()=>{a(p)})}catch(h){a(p)}};function v(d=!1){if(!A()){n=[];return}let m=r.concat(n);if(n=[],r=[],m.length!==0)try{let p=c();for(let h=0;h<m.length;h+=100){let k=m.slice(h,h+100),I=f(k);if(!(d&&g(p,I))){if(d){y(p,I,k);continue}y(p,I,k)}}}catch(p){}}let O=()=>{try{typeof document!="undefined"&&document.hidden&&v(!0)}catch(d){}},S=()=>{v(!0)},Z=d=>{if(A())try{n.push(d),n.length>=20&&v(!1)}catch(m){}},Q=()=>{try{s!==null&&(clearInterval(s),s=null),l!==null&&(clearTimeout(l),l=null),typeof document!="undefined"&&document.removeEventListener("visibilitychange",O),typeof window!="undefined"&&(window.removeEventListener("pagehide",S),window.removeEventListener("beforeunload",S))}catch(d){}};if(A())try{s=setInterval(()=>v(!1),3e3),typeof document!="undefined"&&document.addEventListener("visibilitychange",O),window.addEventListener("pagehide",S),window.addEventListener("beforeunload",S)}catch(d){}return{enqueue:Z,flush:v,teardown:Q}}var ve=["source","medium","campaign","term","content"];function z(e){let t={};if(!e)return t;try{let n=new URLSearchParams(e);for(let r of ve){let i=n.get(`utm_${r}`);i&&(t[`$utm_${r}`]=i)}}catch(n){}return t}var we="clickroom-js",_e="0.2.1",C="ckrm_did",be="ckrm_",E={api_host:"https://capture.clickroom.co",autocapture:!0,capture_pageview:!0},Se=()=>typeof window!="undefined",Ee=5e3,o={initialized:!1,warnedNotInit:!1,writeKey:"",config:{...E},distinctId:"",personProperties:{},transport:null,session:null,sessionContext:null,teardownAutocapture:null,flagListeners:[],flagClient:null,flagsLoaded:!1,emittedFlagCalls:new Set,flagPollId:null},M=()=>{try{return typeof localStorage=="undefined"?null:localStorage}catch(e){return null}},Ce=()=>{try{return typeof sessionStorage=="undefined"?null:sessionStorage}catch(e){return null}},Fe=e=>{var t,n;try{return(n=(t=M())==null?void 0:t.getItem(e))!=null?n:null}catch(r){return null}},T=(e,t)=>{var n;try{(n=M())==null||n.setItem(e,t)}catch(r){}},Me=e=>{var t;try{(t=M())==null||t.removeItem(e)}catch(n){}},$=()=>{try{if(typeof crypto!="undefined"&&typeof crypto.randomUUID=="function")return crypto.randomUUID()}catch(e){}return`ckrm_${Date.now().toString(36)}_${Math.random().toString(36).slice(2,12)}`},ke=()=>{let e=Fe(C);if(e)return e;let t=$();return T(C,t),t},Ie=()=>{let e={$lib:we,$lib_version:_e};try{typeof location!="undefined"&&(e.$current_url=location.href,e.$host=location.host,e.$pathname=location.pathname),typeof document!="undefined"&&(e.$referrer=document.referrer),typeof window!="undefined"&&window.screen&&(e.$screen_height=window.screen.height,e.$screen_width=window.screen.width),typeof window!="undefined"&&(e.$viewport_height=window.innerHeight,e.$viewport_width=window.innerWidth),typeof navigator!="undefined"&&Object.assign(e,X(navigator.userAgent,navigator.language))}catch(t){}return e},W=()=>{try{let e=typeof location!="undefined"?location.search:"",t=typeof document!="undefined"?document.referrer:"",n=typeof location!="undefined"?location.hostname:"",r=z(e);return{...r,$channel_type:V(t,r,n)}}catch(e){return{}}},Re=e=>e?((!o.sessionContext||o.sessionContext.sessionId!==e)&&(o.sessionContext={sessionId:e,props:W()}),o.sessionContext.props):W(),Y=()=>{if(!o.warnedNotInit){o.warnedNotInit=!0;try{console.warn("[clickroom] capture() called before init(); event dropped")}catch(e){}}},b=(e,t,n)=>{var r;if(!o.initialized||!o.transport){Y();return}try{let i=(r=o.session)==null?void 0:r.getSessionId(),l=i?{$session_id:i}:{},s={event:e,distinct_id:o.distinctId,timestamp:new Date().toISOString(),properties:{...Ie(),...l,...Re(i),...t!=null?t:{}}};n&&(s.elements_chain=n),o.transport.enqueue(s)}catch(i){}},J=e=>{let t={};for(let[n,r]of Object.entries(e))r.enabled&&(t[n]=r.variant!=null?r.variant:!0);return t},Pe=()=>{if(!o.flagClient)return;let e=J(o.flagClient.getCache());for(let t of o.flagListeners)try{t(e)}catch(n){}},F=()=>{o.flagClient&&o.flagClient.checkVersionAndMaybeFetch().then(()=>{o.flagsLoaded=!0,Pe()}).catch(()=>{})},q=(e,t)=>{try{if(!o.flagsLoaded)return;let n=String(t),r=`${e}:${n}`;if(o.emittedFlagCalls.has(r))return;o.emittedFlagCalls.add(r),b("$feature_flag_called",{$feature_flag:e,$feature_flag_response:n})}catch(n){}};function xe(e,t){var n,r,i;if(!o.initialized&&Se()){if(o.writeKey=e,o.config={api_host:(n=t==null?void 0:t.api_host)!=null?n:E.api_host,autocapture:(r=t==null?void 0:t.autocapture)!=null?r:E.autocapture,capture_pageview:(i=t==null?void 0:t.capture_pageview)!=null?i:E.capture_pageview},o.distinctId=ke(),o.session=G({now:()=>Date.now(),storage:Ce,genId:$}),o.transport=K(()=>o.config,()=>o.writeKey),o.flagClient=j({getApiHost:()=>o.config.api_host,getWriteKey:()=>o.writeKey,getDistinctId:()=>o.distinctId,getPersonProperties:()=>o.personProperties}),o.initialized=!0,F(),t!=null&&t.feature_flag_poll_interval){let l=Math.max(t.feature_flag_poll_interval,Ee);o.flagPollId=setInterval(()=>F(),l)}if(o.config.autocapture){let l={config:o.config,capture:b};try{o.teardownAutocapture=B(l)}catch(s){}}else o.config.capture_pageview&&b("$pageview")}}function Ae(e,t){b(e,t)}function Te(e,t){if(!o.initialized){Y();return}let n=o.distinctId;o.distinctId=e,T(C,e),t&&(o.personProperties={...o.personProperties,...t}),b("$identify",{$set:t!=null?t:{},...n!==e?{$anon_distinct_id:n}:{}}),o.emittedFlagCalls.clear(),F()}function $e(){var e,t;try{let n=M();if(n){let r=[];for(let i=0;i<n.length;i+=1){let l=n.key(i);l!=null&&l.startsWith(be)&&r.push(l)}for(let i of r)Me(i)}}catch(n){}o.distinctId=$(),T(C,o.distinctId),(e=o.session)==null||e.reset(),o.sessionContext=null,o.personProperties={},o.flagsLoaded=!1,o.emittedFlagCalls.clear(),(t=o.flagClient)==null||t.clear()}function Le(e){var n,r;let t=(r=(n=o.flagClient)==null?void 0:n.isFeatureEnabled(e))!=null?r:!1;return q(e,t),t}function Oe(e){var n,r;let t=(r=(n=o.flagClient)==null?void 0:n.getFeatureFlag(e))!=null?r:!1;return q(e,t),t}function Ne(e){var t,n;return(n=(t=o.flagClient)==null?void 0:t.getFeatureFlagPayload(e))!=null?n:null}function De(e){if(o.flagListeners.push(e),o.flagsLoaded&&o.flagClient)try{e(J(o.flagClient.getCache()))}catch(t){}}function He(){F()}function Be(){o.flagPollId!==null&&(clearInterval(o.flagPollId),o.flagPollId=null)}var Ue={init:xe,capture:Ae,identify:Te,reset:$e,isFeatureEnabled:Le,getFeatureFlag:Oe,getFeatureFlagPayload:Ne,onFeatureFlags:De,refreshFeatureFlags:He,stopFeatureFlagPolling:Be};var L=Ue;if(typeof window!="undefined"){let e=L;e.__loaded__=!0,window.clickroom=e}var st=L;})();
package/dist/global.d.ts CHANGED
@@ -1,7 +1,17 @@
1
1
  import clickroom from './index';
2
+ /**
3
+ * `__loaded__` is the flag the async loader stub (`loader.js`) checks before it
4
+ * injects `<script src="{api_host}/static/clickroom.js">`. Only the real bundle
5
+ * sets it — the stub sets `__stub__` instead — so a page that pastes the snippet
6
+ * twice (a common copy/paste accident, and unavoidable in some tag managers)
7
+ * loads and evaluates the bundle exactly once.
8
+ */
9
+ type ClickroomGlobal = typeof clickroom & {
10
+ __loaded__: true;
11
+ };
2
12
  declare global {
3
13
  interface Window {
4
- clickroom: typeof clickroom;
14
+ clickroom: ClickroomGlobal;
5
15
  }
6
16
  }
7
17
  export default clickroom;
package/dist/index.js CHANGED
@@ -659,11 +659,14 @@ function parseUtm(search) {
659
659
  // src/index.ts
660
660
  var PACKAGE_NAME = "@clickroom/sdk-js";
661
661
  var LIB_NAME = "clickroom-js";
662
- var LIB_VERSION = "0.1.0";
662
+ var LIB_VERSION = "0.2.1";
663
663
  var DID_KEY = "ckrm_did";
664
664
  var STORAGE_PREFIX = "ckrm_";
665
665
  var DEFAULT_CONFIG = {
666
- api_host: "https://sdk.clickroom.co",
666
+ // The ingest host events are actually POSTed to (infra/cdk/lib/config.ts `subdomains.capture`).
667
+ // Was `sdk.clickroom.co`, which has never been provisioned — a caller who omitted `api_host`
668
+ // silently dropped every event, since the transport swallows send failures by design.
669
+ api_host: "https://capture.clickroom.co",
667
670
  autocapture: true,
668
671
  capture_pageview: true
669
672
  };
package/dist/index.js.map CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/elements-chain.ts", "../src/autocapture.ts", "../src/channel.ts", "../src/device.ts", "../src/flags.ts", "../src/session.ts", "../src/transport.ts", "../src/utm.ts", "../src/index.ts"],
4
- "sourcesContent": ["// @clickroom/sdk-js \u2014 DOM `elements_chain` serialization (D-07).\n//\n// Serializes a click/submit/change target and its ancestors into a single\n// compact string that ClickHouse materializes into `elements_chain_text` /\n// `elements_chain_href` via `extract(elements_chain, 'text=\"([^\"]*)\"')` and the\n// equivalent `href=\"...\"`. The literal `text=\"` and `href=\"` tokens must appear\n// verbatim for those extractors to match, and values must not contain a raw `\"`\n// (the `[^\"]*` capture stops at the first quote) \u2014 so we backslash-escape.\n\n// Cap how far up the tree we walk, to bound the serialized size.\nconst MAX_DEPTH = 15\n// Truncate captured text to keep the chain compact on the wire.\nconst MAX_TEXT_LEN = 255\n\n// Elements that are inherently \"interesting\" click targets for autocapture.\nconst INTERESTING_TAGS = new Set(['a', 'button', 'input', 'select', 'textarea', 'label'])\n\n// Class / attribute opt-out markers: skip capture for these subtrees.\nconst OPT_OUT_CLASS = 'ch-no-capture'\nconst OPT_OUT_ATTR = 'data-ch-no-capture'\n\n// Escape a value so it survives inside `attr=\"<value>\"` and stays parseable:\n// backslash and double-quote are escaped; separators (`;` `|`) and newlines are\n// escaped so they never terminate a pair or an element boundary.\nfunction escapeValue(value: string): string {\n return value\n .replace(/\\\\/g, '\\\\\\\\')\n .replace(/\"/g, '\\\\\"')\n .replace(/;/g, '\\\\;')\n .replace(/\\|/g, '\\\\|')\n .replace(/\\r?\\n/g, ' ')\n .trim()\n}\n\n// 1-indexed position among *all* siblings.\nfunction nthChild(el: Element): number {\n let n = 1\n let sib = el.previousElementSibling\n while (sib) {\n n += 1\n sib = sib.previousElementSibling\n }\n return n\n}\n\n// 1-indexed position among siblings sharing the same tag name.\nfunction nthOfType(el: Element): number {\n let n = 1\n const tag = el.tagName\n let sib = el.previousElementSibling\n while (sib) {\n if (sib.tagName === tag) {\n n += 1\n }\n sib = sib.previousElementSibling\n }\n return n\n}\n\n// Best-effort trimmed text for an element, truncated to MAX_TEXT_LEN.\nfunction elementText(el: Element): string {\n const raw = (el as HTMLElement).innerText ?? el.textContent ?? ''\n const trimmed = raw.replace(/\\s+/g, ' ').trim()\n return trimmed.length > MAX_TEXT_LEN ? trimmed.slice(0, MAX_TEXT_LEN) : trimmed\n}\n\n// Serialize a single element into `tag:attr=\"v\";attr2=\"v2\"`.\nfunction serializeElement(el: Element): string {\n const tag = el.tagName.toLowerCase()\n const pairs: string[] = []\n\n const id = el.getAttribute('id')\n if (id) {\n pairs.push(`attr__id=\"${escapeValue(id)}\"`)\n }\n\n const className = typeof el.className === 'string' ? el.className : ''\n if (className) {\n pairs.push(`attr__class=\"${escapeValue(className)}\"`)\n }\n\n if (tag === 'a') {\n const href = el.getAttribute('href')\n if (href) {\n pairs.push(`href=\"${escapeValue(href)}\"`)\n }\n }\n\n const text = elementText(el)\n if (text) {\n pairs.push(`text=\"${escapeValue(text)}\"`)\n }\n\n pairs.push(`nth-child=\"${nthChild(el)}\"`)\n pairs.push(`nth-of-type=\"${nthOfType(el)}\"`)\n\n // data-* attributes as attr__data-*.\n const attrs = el.attributes\n for (let i = 0; i < attrs.length; i += 1) {\n const attr = attrs.item(i)\n if (attr?.name.startsWith('data-')) {\n pairs.push(`attr__${attr.name}=\"${escapeValue(attr.value)}\"`)\n }\n }\n\n return `${tag}:${pairs.join(';')}`\n}\n\n/**\n * Serialize `target` and its ancestors (up to <body>, capped at MAX_DEPTH) into\n * an `elements_chain` string: each element as `tag:attr=\"v\";attr2=\"v2\"`, joined\n * by `|` with the target element first and its parents following.\n */\nexport function serializeElementsChain(target: Element): string {\n const parts: string[] = []\n let el: Element | null = target\n let depth = 0\n\n while (el && depth < MAX_DEPTH) {\n const tag = el.tagName.toLowerCase()\n if (tag === 'body' || tag === 'html') {\n break\n }\n parts.push(serializeElement(el))\n el = el.parentElement\n depth += 1\n }\n\n return parts.join('|')\n}\n\n// True if any ancestor (inclusive) opts out of capture.\nfunction isOptedOut(el: Element): boolean {\n let node: Element | null = el\n while (node) {\n if (node.classList?.contains(OPT_OUT_CLASS) || node.hasAttribute(OPT_OUT_ATTR)) {\n return true\n }\n node = node.parentElement\n }\n return false\n}\n\n// True if the element is an inherently interesting click target.\nfunction isInteresting(el: Element): boolean {\n const tag = el.tagName.toLowerCase()\n if (INTERESTING_TAGS.has(tag)) {\n return true\n }\n if (el.hasAttribute('data-ch-capture')) {\n return true\n }\n if (el.getAttribute('role')) {\n return true\n }\n // Inline handler attribute (`onclick=\"...\"`).\n if (el.hasAttribute('onclick')) {\n return true\n }\n return false\n}\n\n/**\n * Climb from the raw event target to the nearest \"interesting\" element to\n * attribute the event to (a, button, input, select, textarea, label, or any\n * element with a role / onclick / [data-ch-capture]). Returns null when nothing\n * meaningful is found or the subtree is opted out of capture.\n */\nexport function getEventTarget(el: Element): Element | null {\n let node: Element | null = el\n let depth = 0\n while (node && depth < MAX_DEPTH) {\n if (isOptedOut(node)) {\n return null\n }\n if (isInteresting(node)) {\n return node\n }\n node = node.parentElement\n depth += 1\n }\n return null\n}\n", "// @clickroom/sdk-js \u2014 DOM autocapture + pageview / SPA navigation tracking.\n//\n// Attaches capture-phase listeners for click/submit/change, serializes the\n// target's DOM path into `elements_chain`, and emits `$autocapture`. Also emits\n// `$pageview` on load and on SPA navigation (history pushState/replaceState,\n// popstate, hashchange). Every handler is wrapped so a listener error never\n// breaks the host page. `initAutocapture` returns a teardown that removes all\n// listeners and restores the patched history methods.\n\nimport { getEventTarget, serializeElementsChain } from './elements-chain'\nimport type { AutocaptureContext } from './types'\n\ntype HistoryMethod = History['pushState']\n\n// Common URL/location properties attached to every autocapture + pageview event.\nfunction locationProps(): Record<string, unknown> {\n return {\n $current_url: location.href,\n $host: location.host,\n $pathname: location.pathname,\n }\n}\n\n/**\n * Wire up autocapture + pageview tracking. Returns a teardown function that\n * removes every listener and restores `history.pushState`/`replaceState`.\n * A no-op teardown is returned in non-DOM (SSR) environments.\n */\nexport function initAutocapture(ctx: AutocaptureContext): () => void {\n if (typeof document === 'undefined' || typeof window === 'undefined') {\n return () => {}\n }\n\n // ---- Autocapture (click / submit / change) -----------------------------\n\n const handleInteraction = (event: Event): void => {\n try {\n const raw = event.target\n if (!(raw instanceof Element)) {\n return\n }\n const target = getEventTarget(raw)\n if (!target) {\n return\n }\n const chain = serializeElementsChain(target)\n ctx.capture(\n '$autocapture',\n {\n $event_type: event.type,\n ...locationProps(),\n },\n chain,\n )\n } catch {\n // Never let a listener error surface to the host page.\n }\n }\n\n const interactionEvents: string[] = ['click', 'submit', 'change']\n\n if (ctx.config.autocapture) {\n for (const type of interactionEvents) {\n document.addEventListener(type, handleInteraction, true)\n }\n }\n\n // ---- Pageviews (initial + SPA navigation) ------------------------------\n\n let lastUrl = ''\n\n const emitPageview = (): void => {\n try {\n if (!ctx.config.capture_pageview) {\n return\n }\n if (location.href === lastUrl) {\n return\n }\n lastUrl = location.href\n ctx.capture('$pageview', {\n ...locationProps(),\n $referrer: document.referrer,\n })\n } catch {\n // Swallow: pageview tracking must never break navigation.\n }\n }\n\n const origPushState: HistoryMethod = history.pushState.bind(history)\n const origReplaceState: HistoryMethod = history.replaceState.bind(history)\n let patched = false\n\n if (ctx.config.capture_pageview) {\n history.pushState = (...args: Parameters<HistoryMethod>): void => {\n origPushState(...args)\n emitPageview()\n }\n history.replaceState = (...args: Parameters<HistoryMethod>): void => {\n origReplaceState(...args)\n emitPageview()\n }\n patched = true\n\n window.addEventListener('popstate', emitPageview)\n window.addEventListener('hashchange', emitPageview)\n\n // Initial pageview for the current document.\n emitPageview()\n }\n\n // ---- Teardown ----------------------------------------------------------\n\n return () => {\n if (ctx.config.autocapture) {\n for (const type of interactionEvents) {\n document.removeEventListener(type, handleInteraction, true)\n }\n }\n if (patched) {\n history.pushState = origPushState\n history.replaceState = origReplaceState\n window.removeEventListener('popstate', emitPageview)\n window.removeEventListener('hashchange', emitPageview)\n }\n }\n}\n", "// @clickroom/sdk-js \u2014 traffic channel classification.\n// PURE, zero-dependency: this package bundles to a CDN script, so no imports.\n// Classifies `document.referrer` + the session's UTM params into a\n// PostHog-style `$channel_type`, using small static domain lists \u2014 no\n// external lookup/API call, synchronous, same style as device.ts's UA parser.\n\n// utm_medium values that indicate paid traffic.\nconst PAID_MEDIUM_RE = /^(cpc|ppc|paid|display)$/i\n\n// Referring-domain substrings for well-known search engines. Matched with a\n// simple `includes` so subdomains/ccTLDs (e.g. `www.google.co.uk`) still hit.\nconst SEARCH_ENGINE_DOMAINS = [\n 'google.',\n 'bing.com',\n 'duckduckgo.com',\n 'yahoo.',\n 'baidu.com',\n 'yandex.',\n 'ecosia.org',\n 'ask.com',\n 'aol.com',\n]\n\n// Referring-domain substrings for well-known social platforms.\nconst SOCIAL_DOMAINS = [\n 'facebook.com',\n 'fb.com',\n 'twitter.com',\n 'x.com',\n 't.co',\n 'linkedin.com',\n 'instagram.com',\n 'tiktok.com',\n 'reddit.com',\n 'pinterest.',\n 'youtube.com',\n 'threads.net',\n]\n\nconst matchesDomain = (domain: string, list: string[]): boolean =>\n list.some((needle) => domain.includes(needle))\n\nconst extractDomain = (url: string): string | undefined => {\n if (!url) return undefined\n try {\n return new URL(url).hostname.toLowerCase()\n } catch {\n return undefined\n }\n}\n\n/**\n * Classify a visit into a `$channel_type` bucket from its referrer + UTM\n * params. Rule order (first match wins): direct, paid, organic search,\n * social, email, referral, fallback direct.\n *\n * @param referrer `document.referrer`.\n * @param utm The `$utm_*` properties already parsed by `parseUtm`.\n * @param currentHostname The current page's hostname (`location.hostname`),\n * used to avoid classifying same-site referrers as `referral`.\n */\nexport function classifyChannel(\n referrer: string,\n utm: Record<string, string>,\n currentHostname: string,\n): string {\n const referrerDomain = extractDomain(referrer)\n const utmMedium = utm.$utm_medium\n const hasReferrer = Boolean(referrerDomain)\n const hasUtm = Object.keys(utm).length > 0\n\n if (!hasReferrer && !hasUtm) return 'direct'\n if (utmMedium && PAID_MEDIUM_RE.test(utmMedium)) return 'paid'\n if (referrerDomain && matchesDomain(referrerDomain, SEARCH_ENGINE_DOMAINS))\n return 'organic search'\n if (referrerDomain && matchesDomain(referrerDomain, SOCIAL_DOMAINS)) return 'social'\n if (utmMedium && utmMedium.toLowerCase() === 'email') return 'email'\n if (referrerDomain && referrerDomain !== currentHostname.toLowerCase()) return 'referral'\n return 'direct'\n}\n", "// @clickroom/sdk-js \u2014 lightweight user-agent parsing (device/OS/browser).\n// PURE, zero-dependency: this package bundles to a CDN script, so no imports.\n// Emits PostHog-style `$` property keys. Only returns keys it can determine.\n\n// ---- browser detection --------------------------------------------------\n// Order matters: Edg/OPR/SamsungBrowser must be checked before Chrome, and\n// Chrome before Safari (Chrome UAs also contain a \"Safari\" token).\n\ninterface BrowserMatch {\n name: string\n // Regex whose first capture group is the major version (when present).\n re: RegExp\n}\n\nconst BROWSERS: BrowserMatch[] = [\n { name: 'Edge', re: /Edg(?:e|A|iOS)?\\/(\\d+)/ },\n { name: 'Opera', re: /(?:OPR|Opera)\\/(\\d+)/ },\n { name: 'Samsung Internet', re: /SamsungBrowser\\/(\\d+)/ },\n { name: 'Firefox', re: /(?:Firefox|FxiOS)\\/(\\d+)/ },\n { name: 'Chrome', re: /(?:Chrome|CriOS)\\/(\\d+)/ },\n { name: 'Safari', re: /Version\\/(\\d+).*Safari/ },\n]\n\nconst detectBrowser = (ua: string): { name?: string; version?: string } => {\n for (const b of BROWSERS) {\n const m = ua.match(b.re)\n if (m) return { name: b.name, version: m[1] }\n }\n return {}\n}\n\n// ---- OS detection -------------------------------------------------------\n\nconst detectOS = (ua: string): string | undefined => {\n if (/Windows/.test(ua)) return 'Windows'\n // iOS must precede macOS (iPad/iPhone UAs may mention \"Mac OS X\").\n if (/iPhone|iPad|iPod/.test(ua)) return 'iOS'\n if (/Android/.test(ua)) return 'Android'\n if (/CrOS/.test(ua)) return 'Chrome OS'\n if (/Mac OS X|Macintosh/.test(ua)) return 'macOS'\n if (/Linux/.test(ua)) return 'Linux'\n return undefined\n}\n\n// ---- device type --------------------------------------------------------\n// iPad -> Tablet; a \"Mobile\" token -> Mobile; Android without \"Mobile\" -> Tablet.\n\nconst detectDeviceType = (ua: string): string => {\n if (/iPad/.test(ua)) return 'Tablet'\n if (/Android/.test(ua)) return /Mobile/.test(ua) ? 'Mobile' : 'Tablet'\n if (/iPhone|iPod/.test(ua)) return 'Mobile'\n if (/Mobile/.test(ua)) return 'Mobile'\n return 'Desktop'\n}\n\n/**\n * Parse a user-agent + language into PostHog-style device properties.\n * Returns only the keys it can determine (never undefined values).\n */\nexport function parseDevice(\n userAgent: string,\n language: string | undefined,\n): Record<string, string> {\n const props: Record<string, string> = {}\n\n const ua = userAgent ?? ''\n if (ua) {\n const browser = detectBrowser(ua)\n if (browser.name) props.$browser = browser.name\n if (browser.version) props.$browser_version = browser.version\n\n const os = detectOS(ua)\n if (os) props.$os = os\n\n props.$device_type = detectDeviceType(ua)\n }\n\n if (language) props.$lang = language\n\n return props\n}\n", "// @clickroom/sdk-js \u2014 canonical feature-flag evaluation + remote fetch cache (Phase 10.A).\n// Semantics are the SHARED SOURCE OF TRUTH (D-05, CANONICAL-FLAGS.md). Any deviation\n// breaks SDK<->server parity (AC-5/6/7). Keep byte-for-byte agreement with Rust + tRPC.\n\nimport { sha1 } from './sha1'\n\n// ---- filter data model (\u00A71) --------------------------------------------\n\nexport type FlagOperator = 'exact' | 'is_not' | 'icontains' | 'gt' | 'lt' | 'is_set'\n\nexport interface PropFilter {\n key: string\n operator: FlagOperator\n value: unknown\n}\n\nexport interface Group {\n properties: PropFilter[]\n rollout_percentage: number\n variant?: string | null\n}\n\nexport interface Variant {\n key: string\n rollout_percentage: number\n}\n\nexport interface Multivariate {\n variants: Variant[]\n}\n\nexport interface Filters {\n groups: Group[]\n multivariate?: Multivariate\n payloads?: Record<string, unknown>\n}\n\nexport interface Flag {\n key: string\n active: boolean\n filters: Filters\n}\n\nexport interface FlagResult {\n enabled: boolean\n variant: string | null\n payload: unknown\n}\n\n// ---- hash (\u00A72) ----------------------------------------------------------\n\n// 15 hex f's == 1152921504606846975. Built via BigInt(string) rather than a `\u2026n`\n// literal so the es2018 CDN bundle target accepts it (identical numeric value).\nconst HASH_DENOM = Number(BigInt('0xfffffffffffffff'))\n\n/** Deterministic bucket in [0,1): int(sha1(key+\".\"+distinctId+salt)[0:15]) / 0xFFFFFFFFFFFFFFF. */\nexport function hashFlag(key: string, distinctId: string, salt: string): number {\n const hex = sha1(`${key}.${distinctId}${salt}`)\n // BigInt (not parseInt) \u2014 15 hex chars is up to 60 bits, beyond 2^53 precision.\n const n = Number(BigInt(`0x${hex.slice(0, 15)}`))\n return n / HASH_DENOM\n}\n\n// ---- operators (\u00A74) -----------------------------------------------------\n\n/** actual = person_properties[key]; value = filter value. */\nexport function propMatch(op: FlagOperator, actual: unknown, value: unknown): boolean {\n switch (op) {\n case 'exact':\n return String(actual) === String(value)\n case 'is_not':\n return String(actual) !== String(value)\n case 'icontains':\n return actual != null && String(actual).toLowerCase().includes(String(value).toLowerCase())\n case 'gt':\n return actual != null && Number(actual) > Number(value)\n case 'lt':\n return actual != null && Number(actual) < Number(value)\n case 'is_set':\n return actual !== undefined && actual !== null\n default:\n return false\n }\n}\n\nconst groupMatches = (props: PropFilter[], pp: Record<string, unknown>): boolean => {\n for (const p of props) {\n if (!propMatch(p.operator, pp[p.key], p.value)) return false\n }\n return true\n}\n\nconst pickVariant = (flag: Flag, distinctId: string): string | null => {\n const variants = flag.filters.multivariate?.variants ?? []\n const f = hashFlag(flag.key, distinctId, 'variant')\n let cumulative = 0\n for (const v of variants) {\n cumulative += v.rollout_percentage / 100\n if (f < cumulative) return v.key // strict <\n }\n return variants.length > 0 ? variants[variants.length - 1].key : null\n}\n\n// ---- evaluate (\u00A73) ------------------------------------------------------\n\n/** First matching group wins; in-group-off stops (no fallthrough). */\nexport function evaluate(\n flag: Flag,\n distinctId: string,\n personProps: Record<string, unknown> = {},\n): FlagResult {\n if (!flag.active) return { enabled: false, variant: null, payload: null }\n\n const filters = flag.filters ?? { groups: [] }\n for (const group of filters.groups ?? []) {\n if (!groupMatches(group.properties ?? [], personProps)) continue\n\n if (hashFlag(flag.key, distinctId, '') <= group.rollout_percentage / 100) {\n const variant = filters.multivariate ? pickVariant(flag, distinctId) : (group.variant ?? null)\n const payload = variant != null ? (filters.payloads?.[variant] ?? null) : null\n return { enabled: true, variant, payload }\n }\n // In-group but rolled out off \u2014 stop, no fallthrough.\n return { enabled: false, variant: null, payload: null }\n }\n\n return { enabled: false, variant: null, payload: null }\n}\n\n// ---- remote fetch cache client -----------------------------------------\n\nconst FLAGS_STORAGE_KEY = 'ckrm_flags'\n\nexport type FlagMap = Record<string, FlagResult>\n\nexport interface FlagsResponse {\n flags: FlagMap\n}\n\nconst safeLocalStorage = (): Storage | null => {\n try {\n if (typeof localStorage === 'undefined') return null\n return localStorage\n } catch {\n return null\n }\n}\n\nexport interface FlagClientDeps {\n getApiHost: () => string\n getWriteKey: () => string\n getDistinctId: () => string\n getPersonProperties: () => Record<string, unknown>\n}\n\nexport interface FlagClient {\n /** In-memory cache of the last evaluated flags (or restored from localStorage). */\n getCache(): FlagMap\n isFeatureEnabled(key: string): boolean\n getFeatureFlag(key: string): boolean | string\n getFeatureFlagPayload(key: string): unknown\n /** Fetch + cache flags for the current distinct id/props. Never throws. */\n fetchFlags(): Promise<FlagMap>\n /**\n * Cheap version check (GET `{apiHost}/flags/version`) before paying for a\n * full `fetchFlags()`. Only calls `fetchFlags()` (and updates the stored\n * `lastVersion`) when the returned version differs from the last one seen\n * \u2014 or on the very first check. Network/parse failure on the version check\n * fails open (treated as \"changed\") and falls through to a full fetch,\n * matching this client's existing never-throw/best-effort tolerance.\n * Never throws.\n */\n checkVersionAndMaybeFetch(): Promise<{ changed: boolean; flags: FlagMap }>\n /** Drop the in-memory + persisted cache. */\n clear(): void\n}\n\nexport function createFlagClient(deps: FlagClientDeps): FlagClient {\n let cache: FlagMap = restoreCache()\n\n function restoreCache(): FlagMap {\n try {\n const raw = safeLocalStorage()?.getItem(FLAGS_STORAGE_KEY)\n if (!raw) return {}\n const parsed = JSON.parse(raw) as FlagMap\n if (parsed && typeof parsed === 'object') return parsed\n } catch {\n // ignore corrupt cache\n }\n return {}\n }\n\n const persist = (map: FlagMap): void => {\n try {\n safeLocalStorage()?.setItem(FLAGS_STORAGE_KEY, JSON.stringify(map))\n } catch {\n // ignore quota / disabled storage\n }\n }\n\n let lastVersion: number | undefined\n\n const buildUrl = (): string => {\n const base = deps.getApiHost().replace(/\\/+$/, '')\n return `${base}/v1/flags`\n }\n\n const buildVersionUrl = (): string => {\n const base = deps.getApiHost().replace(/\\/+$/, '')\n return `${base}/v1/flags/version`\n }\n\n const fetchFlags = async (): Promise<FlagMap> => {\n try {\n if (typeof fetch !== 'function') return cache\n const res = await fetch(buildUrl(), {\n method: 'POST',\n headers: {\n 'content-type': 'application/json',\n authorization: `Bearer ${deps.getWriteKey()}`,\n },\n body: JSON.stringify({\n distinct_id: deps.getDistinctId(),\n person_properties: deps.getPersonProperties(),\n }),\n })\n if (!res.ok) return cache // fall back to existing cache\n const data = (await res.json()) as FlagsResponse\n const flags = data?.flags\n if (flags && typeof flags === 'object') {\n cache = flags\n persist(cache)\n }\n return cache\n } catch {\n // Network / parse failure \u2014 keep whatever cache we have (incl. localStorage).\n return cache\n }\n }\n\n const checkVersionAndMaybeFetch = async (): Promise<{ changed: boolean; flags: FlagMap }> => {\n let changed = true\n let newVersion: number | undefined\n\n try {\n if (typeof fetch === 'function') {\n const res = await fetch(buildVersionUrl(), {\n method: 'GET',\n headers: {\n authorization: `Bearer ${deps.getWriteKey()}`,\n },\n })\n if (res.ok) {\n const data = (await res.json()) as { version?: number }\n newVersion = data?.version\n changed = lastVersion === undefined || newVersion !== lastVersion\n }\n // !res.ok \u2014 fail open, `changed` stays true (default).\n }\n } catch {\n // Network / parse failure \u2014 fail open, `changed` stays true.\n }\n\n if (!changed) return { changed: false, flags: cache }\n\n const flags = await fetchFlags()\n if (newVersion !== undefined) lastVersion = newVersion\n return { changed: true, flags }\n }\n\n return {\n getCache: () => cache,\n isFeatureEnabled: (key) => cache[key]?.enabled ?? false,\n getFeatureFlag: (key) => {\n const r = cache[key]\n if (!r?.enabled) return false\n return r.variant != null ? r.variant : true\n },\n getFeatureFlagPayload: (key) => cache[key]?.payload ?? null,\n fetchFlags,\n checkVersionAndMaybeFetch,\n clear: () => {\n cache = {}\n try {\n safeLocalStorage()?.removeItem(FLAGS_STORAGE_KEY)\n } catch {\n // ignore\n }\n },\n }\n}\n", "// @clickroom/sdk-js \u2014 session-id generation + idle rotation (phase 19.C).\n//\n// A session groups a user's events into a contiguous visit. The id is persisted\n// in sessionStorage (per-tab) and rotated when the tab goes idle past a timeout\n// or a session outlives the hard 24h cap. Every captured event carries the\n// current id as `$session_id`; ClickHouse materializes `session_id` from it so\n// the event-detail page can render a true session timeline + session count.\n\n/** Idle window: no captured activity for this long starts a fresh session. */\nconst IDLE_MS = 30 * 60 * 1000\n/** Hard cap: a single session never lives longer than this, even if active. */\nconst MAX_MS = 24 * 60 * 60 * 1000\n\nconst SID_KEY = 'ckrm_sid'\n\ninterface StoredSession {\n /** Session id. */\n id: string\n /** Epoch ms the session started (for the MAX_MS cap). */\n start: number\n /** Epoch ms of the last captured activity (for the IDLE_MS rotation). */\n last: number\n}\n\nexport interface SessionManager {\n /** Current session id, rotating first if idle/expired. Bumps last-activity. */\n getSessionId(): string\n /** Forget the current session \u2014 the next getSessionId() starts a fresh one. */\n reset(): void\n}\n\nexport interface SessionDeps {\n /** Epoch ms clock (injectable for tests). */\n now: () => number\n /** sessionStorage accessor (SSR-safe; returns null when unavailable). */\n storage: () => Storage | null\n /** New session id generator (shares the SDK's uuid helper). */\n genId: () => string\n}\n\nconst parse = (raw: string | null): StoredSession | null => {\n if (!raw) return null\n try {\n const v = JSON.parse(raw)\n if (\n v &&\n typeof v === 'object' &&\n typeof v.id === 'string' &&\n typeof v.start === 'number' &&\n typeof v.last === 'number'\n ) {\n return v as StoredSession\n }\n } catch {\n // fall through \u2014 treat as no session\n }\n return null\n}\n\nexport function createSessionManager({ now, storage, genId }: SessionDeps): SessionManager {\n const load = (): StoredSession | null => {\n try {\n return parse(storage()?.getItem(SID_KEY) ?? null)\n } catch {\n return null\n }\n }\n\n const save = (s: StoredSession): void => {\n try {\n storage()?.setItem(SID_KEY, JSON.stringify(s))\n } catch {\n // ignore \u2014 best-effort persistence\n }\n }\n\n const start = (t: number): StoredSession => {\n const s: StoredSession = { id: genId(), start: t, last: t }\n save(s)\n return s\n }\n\n return {\n getSessionId(): string {\n const t = now()\n const existing = load()\n const expired = !existing || t - existing.last > IDLE_MS || t - existing.start > MAX_MS\n if (expired) {\n return start(t).id\n }\n existing.last = t\n save(existing)\n return existing.id\n },\n reset(): void {\n try {\n storage()?.removeItem(SID_KEY)\n } catch {\n // ignore\n }\n },\n }\n}\n", "// @clickroom/sdk-js \u2014 batching transport with beacon + fetch fallback and bounded retry.\n\nimport type { ClickroomConfig, EventPayload } from './types'\n\nconst FLUSH_SIZE = 20\nconst BATCH_MAX = 100\nconst FLUSH_INTERVAL_MS = 3000\n\nconst RETRY_BASE_MS = 1000\nconst RETRY_CAP_MS = 30_000\nconst RETRY_MAX_EVENTS = 1000\n\nexport interface Transport {\n enqueue(event: EventPayload): void\n flush(useBeacon?: boolean): void\n teardown(): void\n}\n\nconst isBrowser = (): boolean => typeof window !== 'undefined'\n\nexport function createTransport(\n getConfig: () => ClickroomConfig,\n getWriteKey: () => string,\n): Transport {\n let buffer: EventPayload[] = []\n let retryBuffer: EventPayload[] = []\n let retryAttempt = 0\n let retryTimer: ReturnType<typeof setTimeout> | null = null\n let interval: ReturnType<typeof setInterval> | null = null\n\n const buildUrl = (): string => {\n const { api_host } = getConfig()\n const base = api_host.replace(/\\/+$/, '')\n return `${base}/v1/capture?token=${encodeURIComponent(getWriteKey())}`\n }\n\n const buildBody = (batch: EventPayload[]): string => JSON.stringify({ batch })\n\n const scheduleRetry = (): void => {\n if (retryTimer !== null || retryBuffer.length === 0) return\n const delay = Math.min(RETRY_BASE_MS * 2 ** retryAttempt, RETRY_CAP_MS)\n try {\n retryTimer = setTimeout(() => {\n retryTimer = null\n flush(false)\n }, delay)\n } catch {\n retryTimer = null\n }\n }\n\n const requeue = (batch: EventPayload[]): void => {\n // Re-queue oldest-first, dropping oldest when exceeding the cap.\n retryBuffer = retryBuffer.concat(batch)\n if (retryBuffer.length > RETRY_MAX_EVENTS) {\n retryBuffer = retryBuffer.slice(retryBuffer.length - RETRY_MAX_EVENTS)\n }\n retryAttempt = Math.min(retryAttempt + 1, 5)\n scheduleRetry()\n }\n\n const sendBeacon = (url: string, body: string): boolean => {\n try {\n if (typeof navigator === 'undefined' || typeof navigator.sendBeacon !== 'function') {\n return false\n }\n // `text/plain`, not `application/json`: sendBeacon() is *always*\n // credentialed (no opt-out) and a non-safelisted content-type makes it\n // a real cross-origin request subject to CORS credential checks \u2014\n // servers that permissively echo `Access-Control-Allow-Origin: *` (or\n // any wildcard) get hard-rejected by the browser for a credentialed\n // request. `text/plain` keeps this a CORS-simple request, matching\n // posthog-js's same workaround. capture-rs parses the body as raw\n // bytes regardless of declared content-type, so this is a no-op\n // server-side.\n const blob = new Blob([body], { type: 'text/plain' })\n return navigator.sendBeacon(url, blob)\n } catch {\n return false\n }\n }\n\n // 4xx responses mean the server will never accept this batch as-is (e.g.\n // schema-invalid events) \u2014 retrying would just resend a request that's\n // permanently rejected. Surface it (matching the existing console.warn\n // idiom used elsewhere in this SDK \u2014 see index.ts's capture-before-init\n // warning) and drop the batch instead of requeuing it.\n const reportRejection = async (res: Response, batch: EventPayload[]): Promise<void> => {\n let detail: unknown\n try {\n detail = await res.json()\n } catch {\n // Non-JSON or empty body \u2014 surface status only.\n }\n console.warn(\n `[clickroom] capture() batch rejected by server (HTTP ${res.status}); dropping ${batch.length} event(s)`,\n detail,\n )\n }\n\n const sendFetch = (url: string, body: string, batch: EventPayload[]): void => {\n try {\n if (typeof fetch !== 'function') {\n requeue(batch)\n return\n }\n fetch(url, {\n method: 'POST',\n keepalive: true,\n headers: { 'content-type': 'application/json' },\n body,\n })\n .then((res) => {\n if (res.ok) {\n // Delivered \u2014 reset backoff.\n retryAttempt = 0\n if (retryBuffer.length > 0) scheduleRetry()\n return\n }\n if (res.status >= 500) {\n // Server-side/transient failure \u2014 retry with existing backoff.\n requeue(batch)\n return\n }\n // 4xx \u2014 permanent failure, do not retry.\n void reportRejection(res, batch)\n })\n .catch(() => {\n // Network error \u2014 retry with existing backoff.\n requeue(batch)\n })\n } catch {\n requeue(batch)\n }\n }\n\n function flush(useBeacon = false): void {\n if (!isBrowser()) {\n buffer = []\n return\n }\n // Drain pending + retry buffers into batches.\n const pending = retryBuffer.concat(buffer)\n buffer = []\n retryBuffer = []\n if (pending.length === 0) return\n\n try {\n const url = buildUrl()\n for (let i = 0; i < pending.length; i += BATCH_MAX) {\n const batch = pending.slice(i, i + BATCH_MAX)\n const body = buildBody(batch)\n if (useBeacon && sendBeacon(url, body)) continue\n if (useBeacon) {\n // Beacon unavailable during unload \u2014 best-effort fetch fallback.\n sendFetch(url, body, batch)\n continue\n }\n sendFetch(url, body, batch)\n }\n } catch {\n // Never throw from flush.\n }\n }\n\n const onVisibilityChange = (): void => {\n try {\n if (typeof document !== 'undefined' && document.hidden) flush(true)\n } catch {\n // ignore\n }\n }\n\n const onPageHide = (): void => {\n flush(true)\n }\n\n const enqueue = (event: EventPayload): void => {\n if (!isBrowser()) return\n try {\n buffer.push(event)\n if (buffer.length >= FLUSH_SIZE) flush(false)\n } catch {\n // ignore\n }\n }\n\n const teardown = (): void => {\n try {\n if (interval !== null) {\n clearInterval(interval)\n interval = null\n }\n if (retryTimer !== null) {\n clearTimeout(retryTimer)\n retryTimer = null\n }\n if (typeof document !== 'undefined') {\n document.removeEventListener('visibilitychange', onVisibilityChange)\n }\n if (typeof window !== 'undefined') {\n window.removeEventListener('pagehide', onPageHide)\n window.removeEventListener('beforeunload', onPageHide)\n }\n } catch {\n // ignore\n }\n }\n\n // Wire up timers + lifecycle listeners.\n if (isBrowser()) {\n try {\n interval = setInterval(() => flush(false), FLUSH_INTERVAL_MS)\n if (typeof document !== 'undefined') {\n document.addEventListener('visibilitychange', onVisibilityChange)\n }\n window.addEventListener('pagehide', onPageHide)\n window.addEventListener('beforeunload', onPageHide)\n } catch {\n // ignore\n }\n }\n\n return { enqueue, flush, teardown }\n}\n", "// @clickroom/sdk-js \u2014 UTM campaign parameter parsing.\n// PURE, zero-dependency: this package bundles to a CDN script, so no imports.\n// Parses `utm_*` query params off a URL's search string into PostHog-style\n// `$utm_*` properties. Only emits keys actually present in the query string \u2014\n// never an empty string or a key for an absent param.\n\nconst UTM_KEYS = ['source', 'medium', 'campaign', 'term', 'content'] as const\n\n/**\n * Parse `utm_source`/`utm_medium`/`utm_campaign`/`utm_term`/`utm_content` off\n * a `location.search`-style string into `$utm_*` properties.\n */\nexport function parseUtm(search: string): Record<string, string> {\n const props: Record<string, string> = {}\n if (!search) return props\n\n try {\n const params = new URLSearchParams(search)\n for (const key of UTM_KEYS) {\n const value = params.get(`utm_${key}`)\n if (value) props[`$utm_${key}`] = value\n }\n } catch {\n // Malformed query string \u2014 return whatever was parsed before the throw.\n }\n\n return props\n}\n", "// @clickroom/sdk-js \u2014 clickroom-js browser SDK core (autocapture + flags). Phase 06.A.\n\nimport { initAutocapture } from './autocapture'\nimport { classifyChannel } from './channel'\nimport { parseDevice } from './device'\nimport { createFlagClient, type FlagClient, type FlagMap } from './flags'\nimport { createSessionManager, type SessionManager } from './session'\nimport { createTransport, type Transport } from './transport'\nimport type { AutocaptureContext, ClickroomConfig, ClickroomOptions, EventPayload } from './types'\nimport { parseUtm } from './utm'\n\nexport const PACKAGE_NAME = '@clickroom/sdk-js'\n\nconst LIB_NAME = 'clickroom-js'\nconst LIB_VERSION = '0.1.0'\n\nconst DID_KEY = 'ckrm_did'\nconst STORAGE_PREFIX = 'ckrm_'\n\nconst DEFAULT_CONFIG: ClickroomConfig = {\n api_host: 'https://sdk.clickroom.co',\n autocapture: true,\n capture_pageview: true,\n}\n\nconst isBrowser = (): boolean => typeof window !== 'undefined'\n\n// Floor for `feature_flag_poll_interval` \u2014 keeps a misconfigured caller from\n// hammering the flags endpoint (mirrors the intent of sdk-node's mandatory\n// `pollInterval`, just defensive instead of required).\nconst MIN_FLAG_POLL_INTERVAL_MS = 5_000\n\ntype FlagListener = (flags: Record<string, boolean | string>) => void\n\ninterface State {\n initialized: boolean\n warnedNotInit: boolean\n writeKey: string\n config: ClickroomConfig\n distinctId: string\n personProperties: Record<string, unknown>\n transport: Transport | null\n session: SessionManager | null\n // Referrer/channel/UTM context, computed once per session (on the first\n // captured event of that session id) and reused for every subsequent event\n // \u2014 mirrors how `$session_id` itself is scoped per session, not per event.\n sessionContext: { sessionId: string; props: Record<string, unknown> } | null\n teardownAutocapture: (() => void) | null\n flagListeners: FlagListener[]\n flagClient: FlagClient | null\n flagsLoaded: boolean\n emittedFlagCalls: Set<string>\n flagPollId: ReturnType<typeof setInterval> | null\n}\n\nconst state: State = {\n initialized: false,\n warnedNotInit: false,\n writeKey: '',\n config: { ...DEFAULT_CONFIG },\n distinctId: '',\n personProperties: {},\n transport: null,\n session: null,\n sessionContext: null,\n teardownAutocapture: null,\n flagListeners: [],\n flagClient: null,\n flagsLoaded: false,\n emittedFlagCalls: new Set(),\n flagPollId: null,\n}\n\n// ---- storage helpers (SSR-safe) ----------------------------------------\n\nconst safeLocalStorage = (): Storage | null => {\n try {\n if (typeof localStorage === 'undefined') return null\n return localStorage\n } catch {\n return null\n }\n}\n\nconst safeSessionStorage = (): Storage | null => {\n try {\n if (typeof sessionStorage === 'undefined') return null\n return sessionStorage\n } catch {\n return null\n }\n}\n\nconst storageGet = (key: string): string | null => {\n try {\n return safeLocalStorage()?.getItem(key) ?? null\n } catch {\n return null\n }\n}\n\nconst storageSet = (key: string, value: string): void => {\n try {\n safeLocalStorage()?.setItem(key, value)\n } catch {\n // ignore\n }\n}\n\nconst storageRemove = (key: string): void => {\n try {\n safeLocalStorage()?.removeItem(key)\n } catch {\n // ignore\n }\n}\n\nconst generateId = (): string => {\n try {\n if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {\n return crypto.randomUUID()\n }\n } catch {\n // fall through\n }\n // Fallback: not RFC-compliant but unique enough for an anonymous id.\n return `ckrm_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 12)}`\n}\n\nconst loadOrCreateDistinctId = (): string => {\n const existing = storageGet(DID_KEY)\n if (existing) return existing\n const fresh = generateId()\n storageSet(DID_KEY, fresh)\n return fresh\n}\n\n// ---- property enrichment ------------------------------------------------\n\nconst baseProperties = (): Record<string, unknown> => {\n const props: Record<string, unknown> = {\n $lib: LIB_NAME,\n $lib_version: LIB_VERSION,\n }\n try {\n if (typeof location !== 'undefined') {\n props.$current_url = location.href\n props.$host = location.host\n props.$pathname = location.pathname\n }\n if (typeof document !== 'undefined') {\n props.$referrer = document.referrer\n }\n if (typeof window !== 'undefined' && window.screen) {\n props.$screen_height = window.screen.height\n props.$screen_width = window.screen.width\n }\n if (typeof window !== 'undefined') {\n props.$viewport_height = window.innerHeight\n props.$viewport_width = window.innerWidth\n }\n if (typeof navigator !== 'undefined') {\n Object.assign(props, parseDevice(navigator.userAgent, navigator.language))\n }\n } catch {\n // ignore \u2014 best-effort enrichment\n }\n return props\n}\n\n// Parses `document.referrer` + `location.search`'s utm_* params into\n// `$utm_*`/`$channel_type` properties. Computed once per session (see\n// `getSessionContextProps` below) rather than on every event.\nconst buildSessionContextProps = (): Record<string, unknown> => {\n try {\n const search = typeof location !== 'undefined' ? location.search : ''\n const referrer = typeof document !== 'undefined' ? document.referrer : ''\n const hostname = typeof location !== 'undefined' ? location.hostname : ''\n const utm = parseUtm(search)\n return { ...utm, $channel_type: classifyChannel(referrer, utm, hostname) }\n } catch {\n return {}\n }\n}\n\n// Returns the current session's cached channel/UTM props, recomputing only\n// when the session id has rotated (new session -> re-parse the current URL).\nconst getSessionContextProps = (sessionId: string | undefined): Record<string, unknown> => {\n if (!sessionId) return buildSessionContextProps()\n if (!state.sessionContext || state.sessionContext.sessionId !== sessionId) {\n state.sessionContext = { sessionId, props: buildSessionContextProps() }\n }\n return state.sessionContext.props\n}\n\n// ---- core capture -------------------------------------------------------\n\nconst warnNotInit = (): void => {\n if (state.warnedNotInit) return\n state.warnedNotInit = true\n try {\n console.warn('[clickroom] capture() called before init(); event dropped')\n } catch {\n // ignore\n }\n}\n\n// Internal capture \u2014 accepts an optional elements_chain (used by autocapture).\nconst captureInternal = (\n event: string,\n properties?: Record<string, unknown>,\n elementsChain?: string,\n): void => {\n if (!state.initialized || !state.transport) {\n warnNotInit()\n return\n }\n try {\n const sessionId = state.session?.getSessionId()\n const sessionProps = sessionId ? { $session_id: sessionId } : {}\n const payload: EventPayload = {\n event,\n distinct_id: state.distinctId,\n timestamp: new Date().toISOString(),\n properties: {\n ...baseProperties(),\n ...sessionProps,\n ...getSessionContextProps(sessionId),\n ...(properties ?? {}),\n },\n }\n if (elementsChain) payload.elements_chain = elementsChain\n state.transport.enqueue(payload)\n } catch {\n // never throw\n }\n}\n\n// ---- feature flags ------------------------------------------------------\n\n// Flatten the cached flag results into the listener-facing map: `{ key: variant|enabled }`.\nconst flagFlatMap = (cache: FlagMap): Record<string, boolean | string> => {\n const flat: Record<string, boolean | string> = {}\n for (const [key, r] of Object.entries(cache)) {\n if (!r.enabled) continue\n flat[key] = r.variant != null ? r.variant : true\n }\n return flat\n}\n\nconst fireFlagListeners = (): void => {\n if (!state.flagClient) return\n const flat = flagFlatMap(state.flagClient.getCache())\n for (const cb of state.flagListeners) {\n try {\n cb(flat)\n } catch {\n // a listener must not break the others\n }\n }\n}\n\n// Version-gated flag refresh: cheap version check first, only paying for a\n// full fetch (+ exposure) when the version actually changed. Always notifies\n// listeners afterward \u2014 matches the pre-existing \"always notify after a\n// refresh call finishes\" contract, incl. fresh subscribers via onFeatureFlags.\n// Never throws.\nconst refreshFlags = (): void => {\n if (!state.flagClient) return\n state.flagClient\n .checkVersionAndMaybeFetch()\n .then(() => {\n state.flagsLoaded = true\n fireFlagListeners()\n })\n .catch(() => {\n // checkVersionAndMaybeFetch already swallows errors; belt-and-suspenders.\n })\n}\n\n// Emit a `$feature_flag_called` exposure event at most once per (key, response)\n// per identity. Never throws \u2014 feature-flag reads must stay SSR-safe.\nconst maybeEmitFlagCall = (key: string, response: boolean | string): void => {\n try {\n // No real value yet \u2014 don't emit a misleading `false` before flags load.\n if (!state.flagsLoaded) return\n const resp = String(response)\n const dedup = `${key}:${resp}`\n if (state.emittedFlagCalls.has(dedup)) return\n state.emittedFlagCalls.add(dedup)\n captureInternal('$feature_flag_called', {\n $feature_flag: key,\n $feature_flag_response: resp,\n })\n } catch {\n // never throw from a flag read\n }\n}\n\n// ---- public API ---------------------------------------------------------\n\nfunction init(writeKey: string, opts?: ClickroomOptions): void {\n if (state.initialized) return\n if (!isBrowser()) {\n // SSR: no-op init. Public methods remain safe no-ops.\n return\n }\n\n state.writeKey = writeKey\n state.config = {\n api_host: opts?.api_host ?? DEFAULT_CONFIG.api_host,\n autocapture: opts?.autocapture ?? DEFAULT_CONFIG.autocapture,\n capture_pageview: opts?.capture_pageview ?? DEFAULT_CONFIG.capture_pageview,\n }\n\n state.distinctId = loadOrCreateDistinctId()\n state.session = createSessionManager({\n now: () => Date.now(),\n storage: safeSessionStorage,\n genId: generateId,\n })\n state.transport = createTransport(\n () => state.config,\n () => state.writeKey,\n )\n state.flagClient = createFlagClient({\n getApiHost: () => state.config.api_host,\n getWriteKey: () => state.writeKey,\n getDistinctId: () => state.distinctId,\n getPersonProperties: () => state.personProperties,\n })\n state.initialized = true\n\n // Non-blocking: load flags for this distinct id, then notify listeners.\n refreshFlags()\n\n // Opt-in poll: re-fetch on an interval so long-lived pages (dashboards,\n // SPAs that rarely re-identify) see flag changes without a page reload.\n // Off by default \u2014 identical to pre-poll behavior when unset.\n if (opts?.feature_flag_poll_interval) {\n const intervalMs = Math.max(opts.feature_flag_poll_interval, MIN_FLAG_POLL_INTERVAL_MS)\n state.flagPollId = setInterval(() => refreshFlags(), intervalMs)\n }\n\n if (state.config.autocapture) {\n const ctx: AutocaptureContext = {\n config: state.config,\n capture: captureInternal,\n }\n try {\n state.teardownAutocapture = initAutocapture(ctx)\n } catch {\n // autocapture is best-effort; never break init\n }\n } else if (state.config.capture_pageview) {\n // Autocapture owns $pageview when enabled (incl. the first).\n // Only emit the initial pageview here when autocapture is off.\n captureInternal('$pageview')\n }\n}\n\nfunction capture(event: string, properties?: Record<string, unknown>): void {\n captureInternal(event, properties)\n}\n\nfunction identify(distinctId: string, personProps?: Record<string, unknown>): void {\n if (!state.initialized) {\n warnNotInit()\n return\n }\n const previousId = state.distinctId\n state.distinctId = distinctId\n storageSet(DID_KEY, distinctId)\n if (personProps) state.personProperties = { ...state.personProperties, ...personProps }\n captureInternal('$identify', {\n $set: personProps ?? {},\n ...(previousId !== distinctId ? { $anon_distinct_id: previousId } : {}),\n })\n // New identity re-exposes flags \u2014 drop the dedup set.\n state.emittedFlagCalls.clear()\n // Distinct id / person props changed \u2014 re-evaluate flags and re-notify.\n refreshFlags()\n}\n\nfunction reset(): void {\n try {\n // Clear all cached clickroom keys (flags, did, etc.).\n const ls = safeLocalStorage()\n if (ls) {\n const toRemove: string[] = []\n for (let i = 0; i < ls.length; i += 1) {\n const key = ls.key(i)\n if (key?.startsWith(STORAGE_PREFIX)) toRemove.push(key)\n }\n for (const key of toRemove) storageRemove(key)\n }\n } catch {\n // ignore\n }\n state.distinctId = generateId()\n storageSet(DID_KEY, state.distinctId)\n // New identity starts a fresh session.\n state.session?.reset()\n state.sessionContext = null\n // Drop cached flags + person props for the previous identity.\n state.personProperties = {}\n state.flagsLoaded = false\n state.emittedFlagCalls.clear()\n state.flagClient?.clear()\n}\n\n// ---- feature flags (public getters) -------------------------------------\n\nfunction isFeatureEnabled(key: string): boolean {\n const result = state.flagClient?.isFeatureEnabled(key) ?? false\n maybeEmitFlagCall(key, result)\n return result\n}\n\nfunction getFeatureFlag(key: string): boolean | string {\n const result = state.flagClient?.getFeatureFlag(key) ?? false\n maybeEmitFlagCall(key, result)\n return result\n}\n\nfunction getFeatureFlagPayload(key: string): unknown {\n return state.flagClient?.getFeatureFlagPayload(key) ?? null\n}\n\nfunction onFeatureFlags(cb: (flags: Record<string, boolean | string>) => void): void {\n state.flagListeners.push(cb)\n // If flags are already loaded, fire immediately with the current map.\n if (state.flagsLoaded && state.flagClient) {\n try {\n cb(flagFlatMap(state.flagClient.getCache()))\n } catch {\n // ignore listener errors\n }\n }\n}\n\n/** Manually re-fetch flag definitions and notify `onFeatureFlags` listeners.\n * Same trigger `identify()` already uses internally \u2014 exposed directly so a\n * consumer can refresh on its own signal (a manual \"reload flags\" button, a\n * websocket/SSE event from elsewhere, etc.) without faking an identify call. */\nfunction refreshFeatureFlags(): void {\n refreshFlags()\n}\n\n/** Stop the `feature_flag_poll_interval` loop started in `init()`, if any. */\nfunction stopFeatureFlagPolling(): void {\n if (state.flagPollId !== null) {\n clearInterval(state.flagPollId)\n state.flagPollId = null\n }\n}\n\n// ---- singleton + named exports ------------------------------------------\n\nconst clickroom = {\n init,\n capture,\n identify,\n reset,\n isFeatureEnabled,\n getFeatureFlag,\n getFeatureFlagPayload,\n onFeatureFlags,\n refreshFeatureFlags,\n stopFeatureFlagPolling,\n}\n\nexport type { AutocaptureContext, ClickroomConfig, ClickroomOptions, EventPayload }\nexport {\n capture,\n getFeatureFlag,\n getFeatureFlagPayload,\n identify,\n init,\n isFeatureEnabled,\n onFeatureFlags,\n refreshFeatureFlags,\n reset,\n stopFeatureFlagPolling,\n}\nexport default clickroom\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["// @clickroom/sdk-js \u2014 DOM `elements_chain` serialization (D-07).\n//\n// Serializes a click/submit/change target and its ancestors into a single\n// compact string that ClickHouse materializes into `elements_chain_text` /\n// `elements_chain_href` via `extract(elements_chain, 'text=\"([^\"]*)\"')` and the\n// equivalent `href=\"...\"`. The literal `text=\"` and `href=\"` tokens must appear\n// verbatim for those extractors to match, and values must not contain a raw `\"`\n// (the `[^\"]*` capture stops at the first quote) \u2014 so we backslash-escape.\n\n// Cap how far up the tree we walk, to bound the serialized size.\nconst MAX_DEPTH = 15\n// Truncate captured text to keep the chain compact on the wire.\nconst MAX_TEXT_LEN = 255\n\n// Elements that are inherently \"interesting\" click targets for autocapture.\nconst INTERESTING_TAGS = new Set(['a', 'button', 'input', 'select', 'textarea', 'label'])\n\n// Class / attribute opt-out markers: skip capture for these subtrees.\nconst OPT_OUT_CLASS = 'ch-no-capture'\nconst OPT_OUT_ATTR = 'data-ch-no-capture'\n\n// Escape a value so it survives inside `attr=\"<value>\"` and stays parseable:\n// backslash and double-quote are escaped; separators (`;` `|`) and newlines are\n// escaped so they never terminate a pair or an element boundary.\nfunction escapeValue(value: string): string {\n return value\n .replace(/\\\\/g, '\\\\\\\\')\n .replace(/\"/g, '\\\\\"')\n .replace(/;/g, '\\\\;')\n .replace(/\\|/g, '\\\\|')\n .replace(/\\r?\\n/g, ' ')\n .trim()\n}\n\n// 1-indexed position among *all* siblings.\nfunction nthChild(el: Element): number {\n let n = 1\n let sib = el.previousElementSibling\n while (sib) {\n n += 1\n sib = sib.previousElementSibling\n }\n return n\n}\n\n// 1-indexed position among siblings sharing the same tag name.\nfunction nthOfType(el: Element): number {\n let n = 1\n const tag = el.tagName\n let sib = el.previousElementSibling\n while (sib) {\n if (sib.tagName === tag) {\n n += 1\n }\n sib = sib.previousElementSibling\n }\n return n\n}\n\n// Best-effort trimmed text for an element, truncated to MAX_TEXT_LEN.\nfunction elementText(el: Element): string {\n const raw = (el as HTMLElement).innerText ?? el.textContent ?? ''\n const trimmed = raw.replace(/\\s+/g, ' ').trim()\n return trimmed.length > MAX_TEXT_LEN ? trimmed.slice(0, MAX_TEXT_LEN) : trimmed\n}\n\n// Serialize a single element into `tag:attr=\"v\";attr2=\"v2\"`.\nfunction serializeElement(el: Element): string {\n const tag = el.tagName.toLowerCase()\n const pairs: string[] = []\n\n const id = el.getAttribute('id')\n if (id) {\n pairs.push(`attr__id=\"${escapeValue(id)}\"`)\n }\n\n const className = typeof el.className === 'string' ? el.className : ''\n if (className) {\n pairs.push(`attr__class=\"${escapeValue(className)}\"`)\n }\n\n if (tag === 'a') {\n const href = el.getAttribute('href')\n if (href) {\n pairs.push(`href=\"${escapeValue(href)}\"`)\n }\n }\n\n const text = elementText(el)\n if (text) {\n pairs.push(`text=\"${escapeValue(text)}\"`)\n }\n\n pairs.push(`nth-child=\"${nthChild(el)}\"`)\n pairs.push(`nth-of-type=\"${nthOfType(el)}\"`)\n\n // data-* attributes as attr__data-*.\n const attrs = el.attributes\n for (let i = 0; i < attrs.length; i += 1) {\n const attr = attrs.item(i)\n if (attr?.name.startsWith('data-')) {\n pairs.push(`attr__${attr.name}=\"${escapeValue(attr.value)}\"`)\n }\n }\n\n return `${tag}:${pairs.join(';')}`\n}\n\n/**\n * Serialize `target` and its ancestors (up to <body>, capped at MAX_DEPTH) into\n * an `elements_chain` string: each element as `tag:attr=\"v\";attr2=\"v2\"`, joined\n * by `|` with the target element first and its parents following.\n */\nexport function serializeElementsChain(target: Element): string {\n const parts: string[] = []\n let el: Element | null = target\n let depth = 0\n\n while (el && depth < MAX_DEPTH) {\n const tag = el.tagName.toLowerCase()\n if (tag === 'body' || tag === 'html') {\n break\n }\n parts.push(serializeElement(el))\n el = el.parentElement\n depth += 1\n }\n\n return parts.join('|')\n}\n\n// True if any ancestor (inclusive) opts out of capture.\nfunction isOptedOut(el: Element): boolean {\n let node: Element | null = el\n while (node) {\n if (node.classList?.contains(OPT_OUT_CLASS) || node.hasAttribute(OPT_OUT_ATTR)) {\n return true\n }\n node = node.parentElement\n }\n return false\n}\n\n// True if the element is an inherently interesting click target.\nfunction isInteresting(el: Element): boolean {\n const tag = el.tagName.toLowerCase()\n if (INTERESTING_TAGS.has(tag)) {\n return true\n }\n if (el.hasAttribute('data-ch-capture')) {\n return true\n }\n if (el.getAttribute('role')) {\n return true\n }\n // Inline handler attribute (`onclick=\"...\"`).\n if (el.hasAttribute('onclick')) {\n return true\n }\n return false\n}\n\n/**\n * Climb from the raw event target to the nearest \"interesting\" element to\n * attribute the event to (a, button, input, select, textarea, label, or any\n * element with a role / onclick / [data-ch-capture]). Returns null when nothing\n * meaningful is found or the subtree is opted out of capture.\n */\nexport function getEventTarget(el: Element): Element | null {\n let node: Element | null = el\n let depth = 0\n while (node && depth < MAX_DEPTH) {\n if (isOptedOut(node)) {\n return null\n }\n if (isInteresting(node)) {\n return node\n }\n node = node.parentElement\n depth += 1\n }\n return null\n}\n", "// @clickroom/sdk-js \u2014 DOM autocapture + pageview / SPA navigation tracking.\n//\n// Attaches capture-phase listeners for click/submit/change, serializes the\n// target's DOM path into `elements_chain`, and emits `$autocapture`. Also emits\n// `$pageview` on load and on SPA navigation (history pushState/replaceState,\n// popstate, hashchange). Every handler is wrapped so a listener error never\n// breaks the host page. `initAutocapture` returns a teardown that removes all\n// listeners and restores the patched history methods.\n\nimport { getEventTarget, serializeElementsChain } from './elements-chain'\nimport type { AutocaptureContext } from './types'\n\ntype HistoryMethod = History['pushState']\n\n// Common URL/location properties attached to every autocapture + pageview event.\nfunction locationProps(): Record<string, unknown> {\n return {\n $current_url: location.href,\n $host: location.host,\n $pathname: location.pathname,\n }\n}\n\n/**\n * Wire up autocapture + pageview tracking. Returns a teardown function that\n * removes every listener and restores `history.pushState`/`replaceState`.\n * A no-op teardown is returned in non-DOM (SSR) environments.\n */\nexport function initAutocapture(ctx: AutocaptureContext): () => void {\n if (typeof document === 'undefined' || typeof window === 'undefined') {\n return () => {}\n }\n\n // ---- Autocapture (click / submit / change) -----------------------------\n\n const handleInteraction = (event: Event): void => {\n try {\n const raw = event.target\n if (!(raw instanceof Element)) {\n return\n }\n const target = getEventTarget(raw)\n if (!target) {\n return\n }\n const chain = serializeElementsChain(target)\n ctx.capture(\n '$autocapture',\n {\n $event_type: event.type,\n ...locationProps(),\n },\n chain,\n )\n } catch {\n // Never let a listener error surface to the host page.\n }\n }\n\n const interactionEvents: string[] = ['click', 'submit', 'change']\n\n if (ctx.config.autocapture) {\n for (const type of interactionEvents) {\n document.addEventListener(type, handleInteraction, true)\n }\n }\n\n // ---- Pageviews (initial + SPA navigation) ------------------------------\n\n let lastUrl = ''\n\n const emitPageview = (): void => {\n try {\n if (!ctx.config.capture_pageview) {\n return\n }\n if (location.href === lastUrl) {\n return\n }\n lastUrl = location.href\n ctx.capture('$pageview', {\n ...locationProps(),\n $referrer: document.referrer,\n })\n } catch {\n // Swallow: pageview tracking must never break navigation.\n }\n }\n\n const origPushState: HistoryMethod = history.pushState.bind(history)\n const origReplaceState: HistoryMethod = history.replaceState.bind(history)\n let patched = false\n\n if (ctx.config.capture_pageview) {\n history.pushState = (...args: Parameters<HistoryMethod>): void => {\n origPushState(...args)\n emitPageview()\n }\n history.replaceState = (...args: Parameters<HistoryMethod>): void => {\n origReplaceState(...args)\n emitPageview()\n }\n patched = true\n\n window.addEventListener('popstate', emitPageview)\n window.addEventListener('hashchange', emitPageview)\n\n // Initial pageview for the current document.\n emitPageview()\n }\n\n // ---- Teardown ----------------------------------------------------------\n\n return () => {\n if (ctx.config.autocapture) {\n for (const type of interactionEvents) {\n document.removeEventListener(type, handleInteraction, true)\n }\n }\n if (patched) {\n history.pushState = origPushState\n history.replaceState = origReplaceState\n window.removeEventListener('popstate', emitPageview)\n window.removeEventListener('hashchange', emitPageview)\n }\n }\n}\n", "// @clickroom/sdk-js \u2014 traffic channel classification.\n// PURE, zero-dependency: this package bundles to a CDN script, so no imports.\n// Classifies `document.referrer` + the session's UTM params into a\n// PostHog-style `$channel_type`, using small static domain lists \u2014 no\n// external lookup/API call, synchronous, same style as device.ts's UA parser.\n\n// utm_medium values that indicate paid traffic.\nconst PAID_MEDIUM_RE = /^(cpc|ppc|paid|display)$/i\n\n// Referring-domain substrings for well-known search engines. Matched with a\n// simple `includes` so subdomains/ccTLDs (e.g. `www.google.co.uk`) still hit.\nconst SEARCH_ENGINE_DOMAINS = [\n 'google.',\n 'bing.com',\n 'duckduckgo.com',\n 'yahoo.',\n 'baidu.com',\n 'yandex.',\n 'ecosia.org',\n 'ask.com',\n 'aol.com',\n]\n\n// Referring-domain substrings for well-known social platforms.\nconst SOCIAL_DOMAINS = [\n 'facebook.com',\n 'fb.com',\n 'twitter.com',\n 'x.com',\n 't.co',\n 'linkedin.com',\n 'instagram.com',\n 'tiktok.com',\n 'reddit.com',\n 'pinterest.',\n 'youtube.com',\n 'threads.net',\n]\n\nconst matchesDomain = (domain: string, list: string[]): boolean =>\n list.some((needle) => domain.includes(needle))\n\nconst extractDomain = (url: string): string | undefined => {\n if (!url) return undefined\n try {\n return new URL(url).hostname.toLowerCase()\n } catch {\n return undefined\n }\n}\n\n/**\n * Classify a visit into a `$channel_type` bucket from its referrer + UTM\n * params. Rule order (first match wins): direct, paid, organic search,\n * social, email, referral, fallback direct.\n *\n * @param referrer `document.referrer`.\n * @param utm The `$utm_*` properties already parsed by `parseUtm`.\n * @param currentHostname The current page's hostname (`location.hostname`),\n * used to avoid classifying same-site referrers as `referral`.\n */\nexport function classifyChannel(\n referrer: string,\n utm: Record<string, string>,\n currentHostname: string,\n): string {\n const referrerDomain = extractDomain(referrer)\n const utmMedium = utm.$utm_medium\n const hasReferrer = Boolean(referrerDomain)\n const hasUtm = Object.keys(utm).length > 0\n\n if (!hasReferrer && !hasUtm) return 'direct'\n if (utmMedium && PAID_MEDIUM_RE.test(utmMedium)) return 'paid'\n if (referrerDomain && matchesDomain(referrerDomain, SEARCH_ENGINE_DOMAINS))\n return 'organic search'\n if (referrerDomain && matchesDomain(referrerDomain, SOCIAL_DOMAINS)) return 'social'\n if (utmMedium && utmMedium.toLowerCase() === 'email') return 'email'\n if (referrerDomain && referrerDomain !== currentHostname.toLowerCase()) return 'referral'\n return 'direct'\n}\n", "// @clickroom/sdk-js \u2014 lightweight user-agent parsing (device/OS/browser).\n// PURE, zero-dependency: this package bundles to a CDN script, so no imports.\n// Emits PostHog-style `$` property keys. Only returns keys it can determine.\n\n// ---- browser detection --------------------------------------------------\n// Order matters: Edg/OPR/SamsungBrowser must be checked before Chrome, and\n// Chrome before Safari (Chrome UAs also contain a \"Safari\" token).\n\ninterface BrowserMatch {\n name: string\n // Regex whose first capture group is the major version (when present).\n re: RegExp\n}\n\nconst BROWSERS: BrowserMatch[] = [\n { name: 'Edge', re: /Edg(?:e|A|iOS)?\\/(\\d+)/ },\n { name: 'Opera', re: /(?:OPR|Opera)\\/(\\d+)/ },\n { name: 'Samsung Internet', re: /SamsungBrowser\\/(\\d+)/ },\n { name: 'Firefox', re: /(?:Firefox|FxiOS)\\/(\\d+)/ },\n { name: 'Chrome', re: /(?:Chrome|CriOS)\\/(\\d+)/ },\n { name: 'Safari', re: /Version\\/(\\d+).*Safari/ },\n]\n\nconst detectBrowser = (ua: string): { name?: string; version?: string } => {\n for (const b of BROWSERS) {\n const m = ua.match(b.re)\n if (m) return { name: b.name, version: m[1] }\n }\n return {}\n}\n\n// ---- OS detection -------------------------------------------------------\n\nconst detectOS = (ua: string): string | undefined => {\n if (/Windows/.test(ua)) return 'Windows'\n // iOS must precede macOS (iPad/iPhone UAs may mention \"Mac OS X\").\n if (/iPhone|iPad|iPod/.test(ua)) return 'iOS'\n if (/Android/.test(ua)) return 'Android'\n if (/CrOS/.test(ua)) return 'Chrome OS'\n if (/Mac OS X|Macintosh/.test(ua)) return 'macOS'\n if (/Linux/.test(ua)) return 'Linux'\n return undefined\n}\n\n// ---- device type --------------------------------------------------------\n// iPad -> Tablet; a \"Mobile\" token -> Mobile; Android without \"Mobile\" -> Tablet.\n\nconst detectDeviceType = (ua: string): string => {\n if (/iPad/.test(ua)) return 'Tablet'\n if (/Android/.test(ua)) return /Mobile/.test(ua) ? 'Mobile' : 'Tablet'\n if (/iPhone|iPod/.test(ua)) return 'Mobile'\n if (/Mobile/.test(ua)) return 'Mobile'\n return 'Desktop'\n}\n\n/**\n * Parse a user-agent + language into PostHog-style device properties.\n * Returns only the keys it can determine (never undefined values).\n */\nexport function parseDevice(\n userAgent: string,\n language: string | undefined,\n): Record<string, string> {\n const props: Record<string, string> = {}\n\n const ua = userAgent ?? ''\n if (ua) {\n const browser = detectBrowser(ua)\n if (browser.name) props.$browser = browser.name\n if (browser.version) props.$browser_version = browser.version\n\n const os = detectOS(ua)\n if (os) props.$os = os\n\n props.$device_type = detectDeviceType(ua)\n }\n\n if (language) props.$lang = language\n\n return props\n}\n", "// @clickroom/sdk-js \u2014 canonical feature-flag evaluation + remote fetch cache (Phase 10.A).\n// Semantics are the SHARED SOURCE OF TRUTH (D-05, CANONICAL-FLAGS.md). Any deviation\n// breaks SDK<->server parity (AC-5/6/7). Keep byte-for-byte agreement with Rust + tRPC.\n\nimport { sha1 } from './sha1'\n\n// ---- filter data model (\u00A71) --------------------------------------------\n\nexport type FlagOperator = 'exact' | 'is_not' | 'icontains' | 'gt' | 'lt' | 'is_set'\n\nexport interface PropFilter {\n key: string\n operator: FlagOperator\n value: unknown\n}\n\nexport interface Group {\n properties: PropFilter[]\n rollout_percentage: number\n variant?: string | null\n}\n\nexport interface Variant {\n key: string\n rollout_percentage: number\n}\n\nexport interface Multivariate {\n variants: Variant[]\n}\n\nexport interface Filters {\n groups: Group[]\n multivariate?: Multivariate\n payloads?: Record<string, unknown>\n}\n\nexport interface Flag {\n key: string\n active: boolean\n filters: Filters\n}\n\nexport interface FlagResult {\n enabled: boolean\n variant: string | null\n payload: unknown\n}\n\n// ---- hash (\u00A72) ----------------------------------------------------------\n\n// 15 hex f's == 1152921504606846975. Built via BigInt(string) rather than a `\u2026n`\n// literal so the es2018 CDN bundle target accepts it (identical numeric value).\nconst HASH_DENOM = Number(BigInt('0xfffffffffffffff'))\n\n/** Deterministic bucket in [0,1): int(sha1(key+\".\"+distinctId+salt)[0:15]) / 0xFFFFFFFFFFFFFFF. */\nexport function hashFlag(key: string, distinctId: string, salt: string): number {\n const hex = sha1(`${key}.${distinctId}${salt}`)\n // BigInt (not parseInt) \u2014 15 hex chars is up to 60 bits, beyond 2^53 precision.\n const n = Number(BigInt(`0x${hex.slice(0, 15)}`))\n return n / HASH_DENOM\n}\n\n// ---- operators (\u00A74) -----------------------------------------------------\n\n/** actual = person_properties[key]; value = filter value. */\nexport function propMatch(op: FlagOperator, actual: unknown, value: unknown): boolean {\n switch (op) {\n case 'exact':\n return String(actual) === String(value)\n case 'is_not':\n return String(actual) !== String(value)\n case 'icontains':\n return actual != null && String(actual).toLowerCase().includes(String(value).toLowerCase())\n case 'gt':\n return actual != null && Number(actual) > Number(value)\n case 'lt':\n return actual != null && Number(actual) < Number(value)\n case 'is_set':\n return actual !== undefined && actual !== null\n default:\n return false\n }\n}\n\nconst groupMatches = (props: PropFilter[], pp: Record<string, unknown>): boolean => {\n for (const p of props) {\n if (!propMatch(p.operator, pp[p.key], p.value)) return false\n }\n return true\n}\n\nconst pickVariant = (flag: Flag, distinctId: string): string | null => {\n const variants = flag.filters.multivariate?.variants ?? []\n const f = hashFlag(flag.key, distinctId, 'variant')\n let cumulative = 0\n for (const v of variants) {\n cumulative += v.rollout_percentage / 100\n if (f < cumulative) return v.key // strict <\n }\n return variants.length > 0 ? variants[variants.length - 1].key : null\n}\n\n// ---- evaluate (\u00A73) ------------------------------------------------------\n\n/** First matching group wins; in-group-off stops (no fallthrough). */\nexport function evaluate(\n flag: Flag,\n distinctId: string,\n personProps: Record<string, unknown> = {},\n): FlagResult {\n if (!flag.active) return { enabled: false, variant: null, payload: null }\n\n const filters = flag.filters ?? { groups: [] }\n for (const group of filters.groups ?? []) {\n if (!groupMatches(group.properties ?? [], personProps)) continue\n\n if (hashFlag(flag.key, distinctId, '') <= group.rollout_percentage / 100) {\n const variant = filters.multivariate ? pickVariant(flag, distinctId) : (group.variant ?? null)\n const payload = variant != null ? (filters.payloads?.[variant] ?? null) : null\n return { enabled: true, variant, payload }\n }\n // In-group but rolled out off \u2014 stop, no fallthrough.\n return { enabled: false, variant: null, payload: null }\n }\n\n return { enabled: false, variant: null, payload: null }\n}\n\n// ---- remote fetch cache client -----------------------------------------\n\nconst FLAGS_STORAGE_KEY = 'ckrm_flags'\n\nexport type FlagMap = Record<string, FlagResult>\n\nexport interface FlagsResponse {\n flags: FlagMap\n}\n\nconst safeLocalStorage = (): Storage | null => {\n try {\n if (typeof localStorage === 'undefined') return null\n return localStorage\n } catch {\n return null\n }\n}\n\nexport interface FlagClientDeps {\n getApiHost: () => string\n getWriteKey: () => string\n getDistinctId: () => string\n getPersonProperties: () => Record<string, unknown>\n}\n\nexport interface FlagClient {\n /** In-memory cache of the last evaluated flags (or restored from localStorage). */\n getCache(): FlagMap\n isFeatureEnabled(key: string): boolean\n getFeatureFlag(key: string): boolean | string\n getFeatureFlagPayload(key: string): unknown\n /** Fetch + cache flags for the current distinct id/props. Never throws. */\n fetchFlags(): Promise<FlagMap>\n /**\n * Cheap version check (GET `{apiHost}/flags/version`) before paying for a\n * full `fetchFlags()`. Only calls `fetchFlags()` (and updates the stored\n * `lastVersion`) when the returned version differs from the last one seen\n * \u2014 or on the very first check. Network/parse failure on the version check\n * fails open (treated as \"changed\") and falls through to a full fetch,\n * matching this client's existing never-throw/best-effort tolerance.\n * Never throws.\n */\n checkVersionAndMaybeFetch(): Promise<{ changed: boolean; flags: FlagMap }>\n /** Drop the in-memory + persisted cache. */\n clear(): void\n}\n\nexport function createFlagClient(deps: FlagClientDeps): FlagClient {\n let cache: FlagMap = restoreCache()\n\n function restoreCache(): FlagMap {\n try {\n const raw = safeLocalStorage()?.getItem(FLAGS_STORAGE_KEY)\n if (!raw) return {}\n const parsed = JSON.parse(raw) as FlagMap\n if (parsed && typeof parsed === 'object') return parsed\n } catch {\n // ignore corrupt cache\n }\n return {}\n }\n\n const persist = (map: FlagMap): void => {\n try {\n safeLocalStorage()?.setItem(FLAGS_STORAGE_KEY, JSON.stringify(map))\n } catch {\n // ignore quota / disabled storage\n }\n }\n\n let lastVersion: number | undefined\n\n const buildUrl = (): string => {\n const base = deps.getApiHost().replace(/\\/+$/, '')\n return `${base}/v1/flags`\n }\n\n const buildVersionUrl = (): string => {\n const base = deps.getApiHost().replace(/\\/+$/, '')\n return `${base}/v1/flags/version`\n }\n\n const fetchFlags = async (): Promise<FlagMap> => {\n try {\n if (typeof fetch !== 'function') return cache\n const res = await fetch(buildUrl(), {\n method: 'POST',\n headers: {\n 'content-type': 'application/json',\n authorization: `Bearer ${deps.getWriteKey()}`,\n },\n body: JSON.stringify({\n distinct_id: deps.getDistinctId(),\n person_properties: deps.getPersonProperties(),\n }),\n })\n if (!res.ok) return cache // fall back to existing cache\n const data = (await res.json()) as FlagsResponse\n const flags = data?.flags\n if (flags && typeof flags === 'object') {\n cache = flags\n persist(cache)\n }\n return cache\n } catch {\n // Network / parse failure \u2014 keep whatever cache we have (incl. localStorage).\n return cache\n }\n }\n\n const checkVersionAndMaybeFetch = async (): Promise<{ changed: boolean; flags: FlagMap }> => {\n let changed = true\n let newVersion: number | undefined\n\n try {\n if (typeof fetch === 'function') {\n const res = await fetch(buildVersionUrl(), {\n method: 'GET',\n headers: {\n authorization: `Bearer ${deps.getWriteKey()}`,\n },\n })\n if (res.ok) {\n const data = (await res.json()) as { version?: number }\n newVersion = data?.version\n changed = lastVersion === undefined || newVersion !== lastVersion\n }\n // !res.ok \u2014 fail open, `changed` stays true (default).\n }\n } catch {\n // Network / parse failure \u2014 fail open, `changed` stays true.\n }\n\n if (!changed) return { changed: false, flags: cache }\n\n const flags = await fetchFlags()\n if (newVersion !== undefined) lastVersion = newVersion\n return { changed: true, flags }\n }\n\n return {\n getCache: () => cache,\n isFeatureEnabled: (key) => cache[key]?.enabled ?? false,\n getFeatureFlag: (key) => {\n const r = cache[key]\n if (!r?.enabled) return false\n return r.variant != null ? r.variant : true\n },\n getFeatureFlagPayload: (key) => cache[key]?.payload ?? null,\n fetchFlags,\n checkVersionAndMaybeFetch,\n clear: () => {\n cache = {}\n try {\n safeLocalStorage()?.removeItem(FLAGS_STORAGE_KEY)\n } catch {\n // ignore\n }\n },\n }\n}\n", "// @clickroom/sdk-js \u2014 session-id generation + idle rotation (phase 19.C).\n//\n// A session groups a user's events into a contiguous visit. The id is persisted\n// in sessionStorage (per-tab) and rotated when the tab goes idle past a timeout\n// or a session outlives the hard 24h cap. Every captured event carries the\n// current id as `$session_id`; ClickHouse materializes `session_id` from it so\n// the event-detail page can render a true session timeline + session count.\n\n/** Idle window: no captured activity for this long starts a fresh session. */\nconst IDLE_MS = 30 * 60 * 1000\n/** Hard cap: a single session never lives longer than this, even if active. */\nconst MAX_MS = 24 * 60 * 60 * 1000\n\nconst SID_KEY = 'ckrm_sid'\n\ninterface StoredSession {\n /** Session id. */\n id: string\n /** Epoch ms the session started (for the MAX_MS cap). */\n start: number\n /** Epoch ms of the last captured activity (for the IDLE_MS rotation). */\n last: number\n}\n\nexport interface SessionManager {\n /** Current session id, rotating first if idle/expired. Bumps last-activity. */\n getSessionId(): string\n /** Forget the current session \u2014 the next getSessionId() starts a fresh one. */\n reset(): void\n}\n\nexport interface SessionDeps {\n /** Epoch ms clock (injectable for tests). */\n now: () => number\n /** sessionStorage accessor (SSR-safe; returns null when unavailable). */\n storage: () => Storage | null\n /** New session id generator (shares the SDK's uuid helper). */\n genId: () => string\n}\n\nconst parse = (raw: string | null): StoredSession | null => {\n if (!raw) return null\n try {\n const v = JSON.parse(raw)\n if (\n v &&\n typeof v === 'object' &&\n typeof v.id === 'string' &&\n typeof v.start === 'number' &&\n typeof v.last === 'number'\n ) {\n return v as StoredSession\n }\n } catch {\n // fall through \u2014 treat as no session\n }\n return null\n}\n\nexport function createSessionManager({ now, storage, genId }: SessionDeps): SessionManager {\n const load = (): StoredSession | null => {\n try {\n return parse(storage()?.getItem(SID_KEY) ?? null)\n } catch {\n return null\n }\n }\n\n const save = (s: StoredSession): void => {\n try {\n storage()?.setItem(SID_KEY, JSON.stringify(s))\n } catch {\n // ignore \u2014 best-effort persistence\n }\n }\n\n const start = (t: number): StoredSession => {\n const s: StoredSession = { id: genId(), start: t, last: t }\n save(s)\n return s\n }\n\n return {\n getSessionId(): string {\n const t = now()\n const existing = load()\n const expired = !existing || t - existing.last > IDLE_MS || t - existing.start > MAX_MS\n if (expired) {\n return start(t).id\n }\n existing.last = t\n save(existing)\n return existing.id\n },\n reset(): void {\n try {\n storage()?.removeItem(SID_KEY)\n } catch {\n // ignore\n }\n },\n }\n}\n", "// @clickroom/sdk-js \u2014 batching transport with beacon + fetch fallback and bounded retry.\n\nimport type { ClickroomConfig, EventPayload } from './types'\n\nconst FLUSH_SIZE = 20\nconst BATCH_MAX = 100\nconst FLUSH_INTERVAL_MS = 3000\n\nconst RETRY_BASE_MS = 1000\nconst RETRY_CAP_MS = 30_000\nconst RETRY_MAX_EVENTS = 1000\n\nexport interface Transport {\n enqueue(event: EventPayload): void\n flush(useBeacon?: boolean): void\n teardown(): void\n}\n\nconst isBrowser = (): boolean => typeof window !== 'undefined'\n\nexport function createTransport(\n getConfig: () => ClickroomConfig,\n getWriteKey: () => string,\n): Transport {\n let buffer: EventPayload[] = []\n let retryBuffer: EventPayload[] = []\n let retryAttempt = 0\n let retryTimer: ReturnType<typeof setTimeout> | null = null\n let interval: ReturnType<typeof setInterval> | null = null\n\n const buildUrl = (): string => {\n const { api_host } = getConfig()\n const base = api_host.replace(/\\/+$/, '')\n return `${base}/v1/capture?token=${encodeURIComponent(getWriteKey())}`\n }\n\n const buildBody = (batch: EventPayload[]): string => JSON.stringify({ batch })\n\n const scheduleRetry = (): void => {\n if (retryTimer !== null || retryBuffer.length === 0) return\n const delay = Math.min(RETRY_BASE_MS * 2 ** retryAttempt, RETRY_CAP_MS)\n try {\n retryTimer = setTimeout(() => {\n retryTimer = null\n flush(false)\n }, delay)\n } catch {\n retryTimer = null\n }\n }\n\n const requeue = (batch: EventPayload[]): void => {\n // Re-queue oldest-first, dropping oldest when exceeding the cap.\n retryBuffer = retryBuffer.concat(batch)\n if (retryBuffer.length > RETRY_MAX_EVENTS) {\n retryBuffer = retryBuffer.slice(retryBuffer.length - RETRY_MAX_EVENTS)\n }\n retryAttempt = Math.min(retryAttempt + 1, 5)\n scheduleRetry()\n }\n\n const sendBeacon = (url: string, body: string): boolean => {\n try {\n if (typeof navigator === 'undefined' || typeof navigator.sendBeacon !== 'function') {\n return false\n }\n // `text/plain`, not `application/json`: sendBeacon() is *always*\n // credentialed (no opt-out) and a non-safelisted content-type makes it\n // a real cross-origin request subject to CORS credential checks \u2014\n // servers that permissively echo `Access-Control-Allow-Origin: *` (or\n // any wildcard) get hard-rejected by the browser for a credentialed\n // request. `text/plain` keeps this a CORS-simple request, matching\n // posthog-js's same workaround. capture-rs parses the body as raw\n // bytes regardless of declared content-type, so this is a no-op\n // server-side.\n const blob = new Blob([body], { type: 'text/plain' })\n return navigator.sendBeacon(url, blob)\n } catch {\n return false\n }\n }\n\n // 4xx responses mean the server will never accept this batch as-is (e.g.\n // schema-invalid events) \u2014 retrying would just resend a request that's\n // permanently rejected. Surface it (matching the existing console.warn\n // idiom used elsewhere in this SDK \u2014 see index.ts's capture-before-init\n // warning) and drop the batch instead of requeuing it.\n const reportRejection = async (res: Response, batch: EventPayload[]): Promise<void> => {\n let detail: unknown\n try {\n detail = await res.json()\n } catch {\n // Non-JSON or empty body \u2014 surface status only.\n }\n console.warn(\n `[clickroom] capture() batch rejected by server (HTTP ${res.status}); dropping ${batch.length} event(s)`,\n detail,\n )\n }\n\n const sendFetch = (url: string, body: string, batch: EventPayload[]): void => {\n try {\n if (typeof fetch !== 'function') {\n requeue(batch)\n return\n }\n fetch(url, {\n method: 'POST',\n keepalive: true,\n headers: { 'content-type': 'application/json' },\n body,\n })\n .then((res) => {\n if (res.ok) {\n // Delivered \u2014 reset backoff.\n retryAttempt = 0\n if (retryBuffer.length > 0) scheduleRetry()\n return\n }\n if (res.status >= 500) {\n // Server-side/transient failure \u2014 retry with existing backoff.\n requeue(batch)\n return\n }\n // 4xx \u2014 permanent failure, do not retry.\n void reportRejection(res, batch)\n })\n .catch(() => {\n // Network error \u2014 retry with existing backoff.\n requeue(batch)\n })\n } catch {\n requeue(batch)\n }\n }\n\n function flush(useBeacon = false): void {\n if (!isBrowser()) {\n buffer = []\n return\n }\n // Drain pending + retry buffers into batches.\n const pending = retryBuffer.concat(buffer)\n buffer = []\n retryBuffer = []\n if (pending.length === 0) return\n\n try {\n const url = buildUrl()\n for (let i = 0; i < pending.length; i += BATCH_MAX) {\n const batch = pending.slice(i, i + BATCH_MAX)\n const body = buildBody(batch)\n if (useBeacon && sendBeacon(url, body)) continue\n if (useBeacon) {\n // Beacon unavailable during unload \u2014 best-effort fetch fallback.\n sendFetch(url, body, batch)\n continue\n }\n sendFetch(url, body, batch)\n }\n } catch {\n // Never throw from flush.\n }\n }\n\n const onVisibilityChange = (): void => {\n try {\n if (typeof document !== 'undefined' && document.hidden) flush(true)\n } catch {\n // ignore\n }\n }\n\n const onPageHide = (): void => {\n flush(true)\n }\n\n const enqueue = (event: EventPayload): void => {\n if (!isBrowser()) return\n try {\n buffer.push(event)\n if (buffer.length >= FLUSH_SIZE) flush(false)\n } catch {\n // ignore\n }\n }\n\n const teardown = (): void => {\n try {\n if (interval !== null) {\n clearInterval(interval)\n interval = null\n }\n if (retryTimer !== null) {\n clearTimeout(retryTimer)\n retryTimer = null\n }\n if (typeof document !== 'undefined') {\n document.removeEventListener('visibilitychange', onVisibilityChange)\n }\n if (typeof window !== 'undefined') {\n window.removeEventListener('pagehide', onPageHide)\n window.removeEventListener('beforeunload', onPageHide)\n }\n } catch {\n // ignore\n }\n }\n\n // Wire up timers + lifecycle listeners.\n if (isBrowser()) {\n try {\n interval = setInterval(() => flush(false), FLUSH_INTERVAL_MS)\n if (typeof document !== 'undefined') {\n document.addEventListener('visibilitychange', onVisibilityChange)\n }\n window.addEventListener('pagehide', onPageHide)\n window.addEventListener('beforeunload', onPageHide)\n } catch {\n // ignore\n }\n }\n\n return { enqueue, flush, teardown }\n}\n", "// @clickroom/sdk-js \u2014 UTM campaign parameter parsing.\n// PURE, zero-dependency: this package bundles to a CDN script, so no imports.\n// Parses `utm_*` query params off a URL's search string into PostHog-style\n// `$utm_*` properties. Only emits keys actually present in the query string \u2014\n// never an empty string or a key for an absent param.\n\nconst UTM_KEYS = ['source', 'medium', 'campaign', 'term', 'content'] as const\n\n/**\n * Parse `utm_source`/`utm_medium`/`utm_campaign`/`utm_term`/`utm_content` off\n * a `location.search`-style string into `$utm_*` properties.\n */\nexport function parseUtm(search: string): Record<string, string> {\n const props: Record<string, string> = {}\n if (!search) return props\n\n try {\n const params = new URLSearchParams(search)\n for (const key of UTM_KEYS) {\n const value = params.get(`utm_${key}`)\n if (value) props[`$utm_${key}`] = value\n }\n } catch {\n // Malformed query string \u2014 return whatever was parsed before the throw.\n }\n\n return props\n}\n", "// @clickroom/sdk-js \u2014 clickroom-js browser SDK core (autocapture + flags). Phase 06.A.\n\nimport { initAutocapture } from './autocapture'\nimport { classifyChannel } from './channel'\nimport { parseDevice } from './device'\nimport { createFlagClient, type FlagClient, type FlagMap } from './flags'\nimport { createSessionManager, type SessionManager } from './session'\nimport { createTransport, type Transport } from './transport'\nimport type { AutocaptureContext, ClickroomConfig, ClickroomOptions, EventPayload } from './types'\nimport { parseUtm } from './utm'\n\nexport const PACKAGE_NAME = '@clickroom/sdk-js'\n\nconst LIB_NAME = 'clickroom-js'\n// Stamped on every event as `$lib_version` \u2014 keep in lockstep with package.json.\nconst LIB_VERSION = '0.2.1'\n\nconst DID_KEY = 'ckrm_did'\nconst STORAGE_PREFIX = 'ckrm_'\n\nconst DEFAULT_CONFIG: ClickroomConfig = {\n // The ingest host events are actually POSTed to (infra/cdk/lib/config.ts `subdomains.capture`).\n // Was `sdk.clickroom.co`, which has never been provisioned \u2014 a caller who omitted `api_host`\n // silently dropped every event, since the transport swallows send failures by design.\n api_host: 'https://capture.clickroom.co',\n autocapture: true,\n capture_pageview: true,\n}\n\nconst isBrowser = (): boolean => typeof window !== 'undefined'\n\n// Floor for `feature_flag_poll_interval` \u2014 keeps a misconfigured caller from\n// hammering the flags endpoint (mirrors the intent of sdk-node's mandatory\n// `pollInterval`, just defensive instead of required).\nconst MIN_FLAG_POLL_INTERVAL_MS = 5_000\n\ntype FlagListener = (flags: Record<string, boolean | string>) => void\n\ninterface State {\n initialized: boolean\n warnedNotInit: boolean\n writeKey: string\n config: ClickroomConfig\n distinctId: string\n personProperties: Record<string, unknown>\n transport: Transport | null\n session: SessionManager | null\n // Referrer/channel/UTM context, computed once per session (on the first\n // captured event of that session id) and reused for every subsequent event\n // \u2014 mirrors how `$session_id` itself is scoped per session, not per event.\n sessionContext: { sessionId: string; props: Record<string, unknown> } | null\n teardownAutocapture: (() => void) | null\n flagListeners: FlagListener[]\n flagClient: FlagClient | null\n flagsLoaded: boolean\n emittedFlagCalls: Set<string>\n flagPollId: ReturnType<typeof setInterval> | null\n}\n\nconst state: State = {\n initialized: false,\n warnedNotInit: false,\n writeKey: '',\n config: { ...DEFAULT_CONFIG },\n distinctId: '',\n personProperties: {},\n transport: null,\n session: null,\n sessionContext: null,\n teardownAutocapture: null,\n flagListeners: [],\n flagClient: null,\n flagsLoaded: false,\n emittedFlagCalls: new Set(),\n flagPollId: null,\n}\n\n// ---- storage helpers (SSR-safe) ----------------------------------------\n\nconst safeLocalStorage = (): Storage | null => {\n try {\n if (typeof localStorage === 'undefined') return null\n return localStorage\n } catch {\n return null\n }\n}\n\nconst safeSessionStorage = (): Storage | null => {\n try {\n if (typeof sessionStorage === 'undefined') return null\n return sessionStorage\n } catch {\n return null\n }\n}\n\nconst storageGet = (key: string): string | null => {\n try {\n return safeLocalStorage()?.getItem(key) ?? null\n } catch {\n return null\n }\n}\n\nconst storageSet = (key: string, value: string): void => {\n try {\n safeLocalStorage()?.setItem(key, value)\n } catch {\n // ignore\n }\n}\n\nconst storageRemove = (key: string): void => {\n try {\n safeLocalStorage()?.removeItem(key)\n } catch {\n // ignore\n }\n}\n\nconst generateId = (): string => {\n try {\n if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {\n return crypto.randomUUID()\n }\n } catch {\n // fall through\n }\n // Fallback: not RFC-compliant but unique enough for an anonymous id.\n return `ckrm_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 12)}`\n}\n\nconst loadOrCreateDistinctId = (): string => {\n const existing = storageGet(DID_KEY)\n if (existing) return existing\n const fresh = generateId()\n storageSet(DID_KEY, fresh)\n return fresh\n}\n\n// ---- property enrichment ------------------------------------------------\n\nconst baseProperties = (): Record<string, unknown> => {\n const props: Record<string, unknown> = {\n $lib: LIB_NAME,\n $lib_version: LIB_VERSION,\n }\n try {\n if (typeof location !== 'undefined') {\n props.$current_url = location.href\n props.$host = location.host\n props.$pathname = location.pathname\n }\n if (typeof document !== 'undefined') {\n props.$referrer = document.referrer\n }\n if (typeof window !== 'undefined' && window.screen) {\n props.$screen_height = window.screen.height\n props.$screen_width = window.screen.width\n }\n if (typeof window !== 'undefined') {\n props.$viewport_height = window.innerHeight\n props.$viewport_width = window.innerWidth\n }\n if (typeof navigator !== 'undefined') {\n Object.assign(props, parseDevice(navigator.userAgent, navigator.language))\n }\n } catch {\n // ignore \u2014 best-effort enrichment\n }\n return props\n}\n\n// Parses `document.referrer` + `location.search`'s utm_* params into\n// `$utm_*`/`$channel_type` properties. Computed once per session (see\n// `getSessionContextProps` below) rather than on every event.\nconst buildSessionContextProps = (): Record<string, unknown> => {\n try {\n const search = typeof location !== 'undefined' ? location.search : ''\n const referrer = typeof document !== 'undefined' ? document.referrer : ''\n const hostname = typeof location !== 'undefined' ? location.hostname : ''\n const utm = parseUtm(search)\n return { ...utm, $channel_type: classifyChannel(referrer, utm, hostname) }\n } catch {\n return {}\n }\n}\n\n// Returns the current session's cached channel/UTM props, recomputing only\n// when the session id has rotated (new session -> re-parse the current URL).\nconst getSessionContextProps = (sessionId: string | undefined): Record<string, unknown> => {\n if (!sessionId) return buildSessionContextProps()\n if (!state.sessionContext || state.sessionContext.sessionId !== sessionId) {\n state.sessionContext = { sessionId, props: buildSessionContextProps() }\n }\n return state.sessionContext.props\n}\n\n// ---- core capture -------------------------------------------------------\n\nconst warnNotInit = (): void => {\n if (state.warnedNotInit) return\n state.warnedNotInit = true\n try {\n console.warn('[clickroom] capture() called before init(); event dropped')\n } catch {\n // ignore\n }\n}\n\n// Internal capture \u2014 accepts an optional elements_chain (used by autocapture).\nconst captureInternal = (\n event: string,\n properties?: Record<string, unknown>,\n elementsChain?: string,\n): void => {\n if (!state.initialized || !state.transport) {\n warnNotInit()\n return\n }\n try {\n const sessionId = state.session?.getSessionId()\n const sessionProps = sessionId ? { $session_id: sessionId } : {}\n const payload: EventPayload = {\n event,\n distinct_id: state.distinctId,\n timestamp: new Date().toISOString(),\n properties: {\n ...baseProperties(),\n ...sessionProps,\n ...getSessionContextProps(sessionId),\n ...(properties ?? {}),\n },\n }\n if (elementsChain) payload.elements_chain = elementsChain\n state.transport.enqueue(payload)\n } catch {\n // never throw\n }\n}\n\n// ---- feature flags ------------------------------------------------------\n\n// Flatten the cached flag results into the listener-facing map: `{ key: variant|enabled }`.\nconst flagFlatMap = (cache: FlagMap): Record<string, boolean | string> => {\n const flat: Record<string, boolean | string> = {}\n for (const [key, r] of Object.entries(cache)) {\n if (!r.enabled) continue\n flat[key] = r.variant != null ? r.variant : true\n }\n return flat\n}\n\nconst fireFlagListeners = (): void => {\n if (!state.flagClient) return\n const flat = flagFlatMap(state.flagClient.getCache())\n for (const cb of state.flagListeners) {\n try {\n cb(flat)\n } catch {\n // a listener must not break the others\n }\n }\n}\n\n// Version-gated flag refresh: cheap version check first, only paying for a\n// full fetch (+ exposure) when the version actually changed. Always notifies\n// listeners afterward \u2014 matches the pre-existing \"always notify after a\n// refresh call finishes\" contract, incl. fresh subscribers via onFeatureFlags.\n// Never throws.\nconst refreshFlags = (): void => {\n if (!state.flagClient) return\n state.flagClient\n .checkVersionAndMaybeFetch()\n .then(() => {\n state.flagsLoaded = true\n fireFlagListeners()\n })\n .catch(() => {\n // checkVersionAndMaybeFetch already swallows errors; belt-and-suspenders.\n })\n}\n\n// Emit a `$feature_flag_called` exposure event at most once per (key, response)\n// per identity. Never throws \u2014 feature-flag reads must stay SSR-safe.\nconst maybeEmitFlagCall = (key: string, response: boolean | string): void => {\n try {\n // No real value yet \u2014 don't emit a misleading `false` before flags load.\n if (!state.flagsLoaded) return\n const resp = String(response)\n const dedup = `${key}:${resp}`\n if (state.emittedFlagCalls.has(dedup)) return\n state.emittedFlagCalls.add(dedup)\n captureInternal('$feature_flag_called', {\n $feature_flag: key,\n $feature_flag_response: resp,\n })\n } catch {\n // never throw from a flag read\n }\n}\n\n// ---- public API ---------------------------------------------------------\n\nfunction init(writeKey: string, opts?: ClickroomOptions): void {\n if (state.initialized) return\n if (!isBrowser()) {\n // SSR: no-op init. Public methods remain safe no-ops.\n return\n }\n\n state.writeKey = writeKey\n state.config = {\n api_host: opts?.api_host ?? DEFAULT_CONFIG.api_host,\n autocapture: opts?.autocapture ?? DEFAULT_CONFIG.autocapture,\n capture_pageview: opts?.capture_pageview ?? DEFAULT_CONFIG.capture_pageview,\n }\n\n state.distinctId = loadOrCreateDistinctId()\n state.session = createSessionManager({\n now: () => Date.now(),\n storage: safeSessionStorage,\n genId: generateId,\n })\n state.transport = createTransport(\n () => state.config,\n () => state.writeKey,\n )\n state.flagClient = createFlagClient({\n getApiHost: () => state.config.api_host,\n getWriteKey: () => state.writeKey,\n getDistinctId: () => state.distinctId,\n getPersonProperties: () => state.personProperties,\n })\n state.initialized = true\n\n // Non-blocking: load flags for this distinct id, then notify listeners.\n refreshFlags()\n\n // Opt-in poll: re-fetch on an interval so long-lived pages (dashboards,\n // SPAs that rarely re-identify) see flag changes without a page reload.\n // Off by default \u2014 identical to pre-poll behavior when unset.\n if (opts?.feature_flag_poll_interval) {\n const intervalMs = Math.max(opts.feature_flag_poll_interval, MIN_FLAG_POLL_INTERVAL_MS)\n state.flagPollId = setInterval(() => refreshFlags(), intervalMs)\n }\n\n if (state.config.autocapture) {\n const ctx: AutocaptureContext = {\n config: state.config,\n capture: captureInternal,\n }\n try {\n state.teardownAutocapture = initAutocapture(ctx)\n } catch {\n // autocapture is best-effort; never break init\n }\n } else if (state.config.capture_pageview) {\n // Autocapture owns $pageview when enabled (incl. the first).\n // Only emit the initial pageview here when autocapture is off.\n captureInternal('$pageview')\n }\n}\n\nfunction capture(event: string, properties?: Record<string, unknown>): void {\n captureInternal(event, properties)\n}\n\nfunction identify(distinctId: string, personProps?: Record<string, unknown>): void {\n if (!state.initialized) {\n warnNotInit()\n return\n }\n const previousId = state.distinctId\n state.distinctId = distinctId\n storageSet(DID_KEY, distinctId)\n if (personProps) state.personProperties = { ...state.personProperties, ...personProps }\n captureInternal('$identify', {\n $set: personProps ?? {},\n ...(previousId !== distinctId ? { $anon_distinct_id: previousId } : {}),\n })\n // New identity re-exposes flags \u2014 drop the dedup set.\n state.emittedFlagCalls.clear()\n // Distinct id / person props changed \u2014 re-evaluate flags and re-notify.\n refreshFlags()\n}\n\nfunction reset(): void {\n try {\n // Clear all cached clickroom keys (flags, did, etc.).\n const ls = safeLocalStorage()\n if (ls) {\n const toRemove: string[] = []\n for (let i = 0; i < ls.length; i += 1) {\n const key = ls.key(i)\n if (key?.startsWith(STORAGE_PREFIX)) toRemove.push(key)\n }\n for (const key of toRemove) storageRemove(key)\n }\n } catch {\n // ignore\n }\n state.distinctId = generateId()\n storageSet(DID_KEY, state.distinctId)\n // New identity starts a fresh session.\n state.session?.reset()\n state.sessionContext = null\n // Drop cached flags + person props for the previous identity.\n state.personProperties = {}\n state.flagsLoaded = false\n state.emittedFlagCalls.clear()\n state.flagClient?.clear()\n}\n\n// ---- feature flags (public getters) -------------------------------------\n\nfunction isFeatureEnabled(key: string): boolean {\n const result = state.flagClient?.isFeatureEnabled(key) ?? false\n maybeEmitFlagCall(key, result)\n return result\n}\n\nfunction getFeatureFlag(key: string): boolean | string {\n const result = state.flagClient?.getFeatureFlag(key) ?? false\n maybeEmitFlagCall(key, result)\n return result\n}\n\nfunction getFeatureFlagPayload(key: string): unknown {\n return state.flagClient?.getFeatureFlagPayload(key) ?? null\n}\n\nfunction onFeatureFlags(cb: (flags: Record<string, boolean | string>) => void): void {\n state.flagListeners.push(cb)\n // If flags are already loaded, fire immediately with the current map.\n if (state.flagsLoaded && state.flagClient) {\n try {\n cb(flagFlatMap(state.flagClient.getCache()))\n } catch {\n // ignore listener errors\n }\n }\n}\n\n/** Manually re-fetch flag definitions and notify `onFeatureFlags` listeners.\n * Same trigger `identify()` already uses internally \u2014 exposed directly so a\n * consumer can refresh on its own signal (a manual \"reload flags\" button, a\n * websocket/SSE event from elsewhere, etc.) without faking an identify call. */\nfunction refreshFeatureFlags(): void {\n refreshFlags()\n}\n\n/** Stop the `feature_flag_poll_interval` loop started in `init()`, if any. */\nfunction stopFeatureFlagPolling(): void {\n if (state.flagPollId !== null) {\n clearInterval(state.flagPollId)\n state.flagPollId = null\n }\n}\n\n// ---- singleton + named exports ------------------------------------------\n\nconst clickroom = {\n init,\n capture,\n identify,\n reset,\n isFeatureEnabled,\n getFeatureFlag,\n getFeatureFlagPayload,\n onFeatureFlags,\n refreshFeatureFlags,\n stopFeatureFlagPolling,\n}\n\nexport type { AutocaptureContext, ClickroomConfig, ClickroomOptions, EventPayload }\nexport {\n capture,\n getFeatureFlag,\n getFeatureFlagPayload,\n identify,\n init,\n isFeatureEnabled,\n onFeatureFlags,\n refreshFeatureFlags,\n reset,\n stopFeatureFlagPolling,\n}\nexport default clickroom\n"],
5
+ "mappings": ";AAUA,IAAM,YAAY;AAElB,IAAM,eAAe;AAGrB,IAAM,mBAAmB,oBAAI,IAAI,CAAC,KAAK,UAAU,SAAS,UAAU,YAAY,OAAO,CAAC;AAGxF,IAAM,gBAAgB;AACtB,IAAM,eAAe;AAKrB,SAAS,YAAY,OAAuB;AAC1C,SAAO,MACJ,QAAQ,OAAO,MAAM,EACrB,QAAQ,MAAM,KAAK,EACnB,QAAQ,MAAM,KAAK,EACnB,QAAQ,OAAO,KAAK,EACpB,QAAQ,UAAU,GAAG,EACrB,KAAK;AACV;AAGA,SAAS,SAAS,IAAqB;AACrC,MAAI,IAAI;AACR,MAAI,MAAM,GAAG;AACb,SAAO,KAAK;AACV,SAAK;AACL,UAAM,IAAI;AAAA,EACZ;AACA,SAAO;AACT;AAGA,SAAS,UAAU,IAAqB;AACtC,MAAI,IAAI;AACR,QAAM,MAAM,GAAG;AACf,MAAI,MAAM,GAAG;AACb,SAAO,KAAK;AACV,QAAI,IAAI,YAAY,KAAK;AACvB,WAAK;AAAA,IACP;AACA,UAAM,IAAI;AAAA,EACZ;AACA,SAAO;AACT;AAGA,SAAS,YAAY,IAAqB;AACxC,QAAM,MAAO,GAAmB,aAAa,GAAG,eAAe;AAC/D,QAAM,UAAU,IAAI,QAAQ,QAAQ,GAAG,EAAE,KAAK;AAC9C,SAAO,QAAQ,SAAS,eAAe,QAAQ,MAAM,GAAG,YAAY,IAAI;AAC1E;AAGA,SAAS,iBAAiB,IAAqB;AAC7C,QAAM,MAAM,GAAG,QAAQ,YAAY;AACnC,QAAM,QAAkB,CAAC;AAEzB,QAAM,KAAK,GAAG,aAAa,IAAI;AAC/B,MAAI,IAAI;AACN,UAAM,KAAK,aAAa,YAAY,EAAE,CAAC,GAAG;AAAA,EAC5C;AAEA,QAAM,YAAY,OAAO,GAAG,cAAc,WAAW,GAAG,YAAY;AACpE,MAAI,WAAW;AACb,UAAM,KAAK,gBAAgB,YAAY,SAAS,CAAC,GAAG;AAAA,EACtD;AAEA,MAAI,QAAQ,KAAK;AACf,UAAM,OAAO,GAAG,aAAa,MAAM;AACnC,QAAI,MAAM;AACR,YAAM,KAAK,SAAS,YAAY,IAAI,CAAC,GAAG;AAAA,IAC1C;AAAA,EACF;AAEA,QAAM,OAAO,YAAY,EAAE;AAC3B,MAAI,MAAM;AACR,UAAM,KAAK,SAAS,YAAY,IAAI,CAAC,GAAG;AAAA,EAC1C;AAEA,QAAM,KAAK,cAAc,SAAS,EAAE,CAAC,GAAG;AACxC,QAAM,KAAK,gBAAgB,UAAU,EAAE,CAAC,GAAG;AAG3C,QAAM,QAAQ,GAAG;AACjB,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK,GAAG;AACxC,UAAM,OAAO,MAAM,KAAK,CAAC;AACzB,QAAI,MAAM,KAAK,WAAW,OAAO,GAAG;AAClC,YAAM,KAAK,SAAS,KAAK,IAAI,KAAK,YAAY,KAAK,KAAK,CAAC,GAAG;AAAA,IAC9D;AAAA,EACF;AAEA,SAAO,GAAG,GAAG,IAAI,MAAM,KAAK,GAAG,CAAC;AAClC;AAOO,SAAS,uBAAuB,QAAyB;AAC9D,QAAM,QAAkB,CAAC;AACzB,MAAI,KAAqB;AACzB,MAAI,QAAQ;AAEZ,SAAO,MAAM,QAAQ,WAAW;AAC9B,UAAM,MAAM,GAAG,QAAQ,YAAY;AACnC,QAAI,QAAQ,UAAU,QAAQ,QAAQ;AACpC;AAAA,IACF;AACA,UAAM,KAAK,iBAAiB,EAAE,CAAC;AAC/B,SAAK,GAAG;AACR,aAAS;AAAA,EACX;AAEA,SAAO,MAAM,KAAK,GAAG;AACvB;AAGA,SAAS,WAAW,IAAsB;AACxC,MAAI,OAAuB;AAC3B,SAAO,MAAM;AACX,QAAI,KAAK,WAAW,SAAS,aAAa,KAAK,KAAK,aAAa,YAAY,GAAG;AAC9E,aAAO;AAAA,IACT;AACA,WAAO,KAAK;AAAA,EACd;AACA,SAAO;AACT;AAGA,SAAS,cAAc,IAAsB;AAC3C,QAAM,MAAM,GAAG,QAAQ,YAAY;AACnC,MAAI,iBAAiB,IAAI,GAAG,GAAG;AAC7B,WAAO;AAAA,EACT;AACA,MAAI,GAAG,aAAa,iBAAiB,GAAG;AACtC,WAAO;AAAA,EACT;AACA,MAAI,GAAG,aAAa,MAAM,GAAG;AAC3B,WAAO;AAAA,EACT;AAEA,MAAI,GAAG,aAAa,SAAS,GAAG;AAC9B,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAQO,SAAS,eAAe,IAA6B;AAC1D,MAAI,OAAuB;AAC3B,MAAI,QAAQ;AACZ,SAAO,QAAQ,QAAQ,WAAW;AAChC,QAAI,WAAW,IAAI,GAAG;AACpB,aAAO;AAAA,IACT;AACA,QAAI,cAAc,IAAI,GAAG;AACvB,aAAO;AAAA,IACT;AACA,WAAO,KAAK;AACZ,aAAS;AAAA,EACX;AACA,SAAO;AACT;;;ACvKA,SAAS,gBAAyC;AAChD,SAAO;AAAA,IACL,cAAc,SAAS;AAAA,IACvB,OAAO,SAAS;AAAA,IAChB,WAAW,SAAS;AAAA,EACtB;AACF;AAOO,SAAS,gBAAgB,KAAqC;AACnE,MAAI,OAAO,aAAa,eAAe,OAAO,WAAW,aAAa;AACpE,WAAO,MAAM;AAAA,IAAC;AAAA,EAChB;AAIA,QAAM,oBAAoB,CAAC,UAAuB;AAChD,QAAI;AACF,YAAM,MAAM,MAAM;AAClB,UAAI,EAAE,eAAe,UAAU;AAC7B;AAAA,MACF;AACA,YAAM,SAAS,eAAe,GAAG;AACjC,UAAI,CAAC,QAAQ;AACX;AAAA,MACF;AACA,YAAM,QAAQ,uBAAuB,MAAM;AAC3C,UAAI;AAAA,QACF;AAAA,QACA;AAAA,UACE,aAAa,MAAM;AAAA,UACnB,GAAG,cAAc;AAAA,QACnB;AAAA,QACA;AAAA,MACF;AAAA,IACF,QAAQ;AAAA,IAER;AAAA,EACF;AAEA,QAAM,oBAA8B,CAAC,SAAS,UAAU,QAAQ;AAEhE,MAAI,IAAI,OAAO,aAAa;AAC1B,eAAW,QAAQ,mBAAmB;AACpC,eAAS,iBAAiB,MAAM,mBAAmB,IAAI;AAAA,IACzD;AAAA,EACF;AAIA,MAAI,UAAU;AAEd,QAAM,eAAe,MAAY;AAC/B,QAAI;AACF,UAAI,CAAC,IAAI,OAAO,kBAAkB;AAChC;AAAA,MACF;AACA,UAAI,SAAS,SAAS,SAAS;AAC7B;AAAA,MACF;AACA,gBAAU,SAAS;AACnB,UAAI,QAAQ,aAAa;AAAA,QACvB,GAAG,cAAc;AAAA,QACjB,WAAW,SAAS;AAAA,MACtB,CAAC;AAAA,IACH,QAAQ;AAAA,IAER;AAAA,EACF;AAEA,QAAM,gBAA+B,QAAQ,UAAU,KAAK,OAAO;AACnE,QAAM,mBAAkC,QAAQ,aAAa,KAAK,OAAO;AACzE,MAAI,UAAU;AAEd,MAAI,IAAI,OAAO,kBAAkB;AAC/B,YAAQ,YAAY,IAAI,SAA0C;AAChE,oBAAc,GAAG,IAAI;AACrB,mBAAa;AAAA,IACf;AACA,YAAQ,eAAe,IAAI,SAA0C;AACnE,uBAAiB,GAAG,IAAI;AACxB,mBAAa;AAAA,IACf;AACA,cAAU;AAEV,WAAO,iBAAiB,YAAY,YAAY;AAChD,WAAO,iBAAiB,cAAc,YAAY;AAGlD,iBAAa;AAAA,EACf;AAIA,SAAO,MAAM;AACX,QAAI,IAAI,OAAO,aAAa;AAC1B,iBAAW,QAAQ,mBAAmB;AACpC,iBAAS,oBAAoB,MAAM,mBAAmB,IAAI;AAAA,MAC5D;AAAA,IACF;AACA,QAAI,SAAS;AACX,cAAQ,YAAY;AACpB,cAAQ,eAAe;AACvB,aAAO,oBAAoB,YAAY,YAAY;AACnD,aAAO,oBAAoB,cAAc,YAAY;AAAA,IACvD;AAAA,EACF;AACF;;;ACvHA,IAAM,iBAAiB;AAIvB,IAAM,wBAAwB;AAAA,EAC5B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAGA,IAAM,iBAAiB;AAAA,EACrB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEA,IAAM,gBAAgB,CAAC,QAAgB,SACrC,KAAK,KAAK,CAAC,WAAW,OAAO,SAAS,MAAM,CAAC;AAE/C,IAAM,gBAAgB,CAAC,QAAoC;AACzD,MAAI,CAAC,IAAK,QAAO;AACjB,MAAI;AACF,WAAO,IAAI,IAAI,GAAG,EAAE,SAAS,YAAY;AAAA,EAC3C,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAYO,SAAS,gBACd,UACA,KACA,iBACQ;AACR,QAAM,iBAAiB,cAAc,QAAQ;AAC7C,QAAM,YAAY,IAAI;AACtB,QAAM,cAAc,QAAQ,cAAc;AAC1C,QAAM,SAAS,OAAO,KAAK,GAAG,EAAE,SAAS;AAEzC,MAAI,CAAC,eAAe,CAAC,OAAQ,QAAO;AACpC,MAAI,aAAa,eAAe,KAAK,SAAS,EAAG,QAAO;AACxD,MAAI,kBAAkB,cAAc,gBAAgB,qBAAqB;AACvE,WAAO;AACT,MAAI,kBAAkB,cAAc,gBAAgB,cAAc,EAAG,QAAO;AAC5E,MAAI,aAAa,UAAU,YAAY,MAAM,QAAS,QAAO;AAC7D,MAAI,kBAAkB,mBAAmB,gBAAgB,YAAY,EAAG,QAAO;AAC/E,SAAO;AACT;;;ACjEA,IAAM,WAA2B;AAAA,EAC/B,EAAE,MAAM,QAAQ,IAAI,yBAAyB;AAAA,EAC7C,EAAE,MAAM,SAAS,IAAI,uBAAuB;AAAA,EAC5C,EAAE,MAAM,oBAAoB,IAAI,wBAAwB;AAAA,EACxD,EAAE,MAAM,WAAW,IAAI,2BAA2B;AAAA,EAClD,EAAE,MAAM,UAAU,IAAI,0BAA0B;AAAA,EAChD,EAAE,MAAM,UAAU,IAAI,yBAAyB;AACjD;AAEA,IAAM,gBAAgB,CAAC,OAAoD;AACzE,aAAW,KAAK,UAAU;AACxB,UAAM,IAAI,GAAG,MAAM,EAAE,EAAE;AACvB,QAAI,EAAG,QAAO,EAAE,MAAM,EAAE,MAAM,SAAS,EAAE,CAAC,EAAE;AAAA,EAC9C;AACA,SAAO,CAAC;AACV;AAIA,IAAM,WAAW,CAAC,OAAmC;AACnD,MAAI,UAAU,KAAK,EAAE,EAAG,QAAO;AAE/B,MAAI,mBAAmB,KAAK,EAAE,EAAG,QAAO;AACxC,MAAI,UAAU,KAAK,EAAE,EAAG,QAAO;AAC/B,MAAI,OAAO,KAAK,EAAE,EAAG,QAAO;AAC5B,MAAI,qBAAqB,KAAK,EAAE,EAAG,QAAO;AAC1C,MAAI,QAAQ,KAAK,EAAE,EAAG,QAAO;AAC7B,SAAO;AACT;AAKA,IAAM,mBAAmB,CAAC,OAAuB;AAC/C,MAAI,OAAO,KAAK,EAAE,EAAG,QAAO;AAC5B,MAAI,UAAU,KAAK,EAAE,EAAG,QAAO,SAAS,KAAK,EAAE,IAAI,WAAW;AAC9D,MAAI,cAAc,KAAK,EAAE,EAAG,QAAO;AACnC,MAAI,SAAS,KAAK,EAAE,EAAG,QAAO;AAC9B,SAAO;AACT;AAMO,SAAS,YACd,WACA,UACwB;AACxB,QAAM,QAAgC,CAAC;AAEvC,QAAM,KAAK,aAAa;AACxB,MAAI,IAAI;AACN,UAAM,UAAU,cAAc,EAAE;AAChC,QAAI,QAAQ,KAAM,OAAM,WAAW,QAAQ;AAC3C,QAAI,QAAQ,QAAS,OAAM,mBAAmB,QAAQ;AAEtD,UAAM,KAAK,SAAS,EAAE;AACtB,QAAI,GAAI,OAAM,MAAM;AAEpB,UAAM,eAAe,iBAAiB,EAAE;AAAA,EAC1C;AAEA,MAAI,SAAU,OAAM,QAAQ;AAE5B,SAAO;AACT;;;AC3BA,IAAM,aAAa,OAAO,OAAO,mBAAmB,CAAC;AA8ErD,IAAM,oBAAoB;AAQ1B,IAAM,mBAAmB,MAAsB;AAC7C,MAAI;AACF,QAAI,OAAO,iBAAiB,YAAa,QAAO;AAChD,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AA+BO,SAAS,iBAAiB,MAAkC;AACjE,MAAI,QAAiB,aAAa;AAElC,WAAS,eAAwB;AAC/B,QAAI;AACF,YAAM,MAAM,iBAAiB,GAAG,QAAQ,iBAAiB;AACzD,UAAI,CAAC,IAAK,QAAO,CAAC;AAClB,YAAM,SAAS,KAAK,MAAM,GAAG;AAC7B,UAAI,UAAU,OAAO,WAAW,SAAU,QAAO;AAAA,IACnD,QAAQ;AAAA,IAER;AACA,WAAO,CAAC;AAAA,EACV;AAEA,QAAM,UAAU,CAAC,QAAuB;AACtC,QAAI;AACF,uBAAiB,GAAG,QAAQ,mBAAmB,KAAK,UAAU,GAAG,CAAC;AAAA,IACpE,QAAQ;AAAA,IAER;AAAA,EACF;AAEA,MAAI;AAEJ,QAAM,WAAW,MAAc;AAC7B,UAAM,OAAO,KAAK,WAAW,EAAE,QAAQ,QAAQ,EAAE;AACjD,WAAO,GAAG,IAAI;AAAA,EAChB;AAEA,QAAM,kBAAkB,MAAc;AACpC,UAAM,OAAO,KAAK,WAAW,EAAE,QAAQ,QAAQ,EAAE;AACjD,WAAO,GAAG,IAAI;AAAA,EAChB;AAEA,QAAM,aAAa,YAA8B;AAC/C,QAAI;AACF,UAAI,OAAO,UAAU,WAAY,QAAO;AACxC,YAAM,MAAM,MAAM,MAAM,SAAS,GAAG;AAAA,QAClC,QAAQ;AAAA,QACR,SAAS;AAAA,UACP,gBAAgB;AAAA,UAChB,eAAe,UAAU,KAAK,YAAY,CAAC;AAAA,QAC7C;AAAA,QACA,MAAM,KAAK,UAAU;AAAA,UACnB,aAAa,KAAK,cAAc;AAAA,UAChC,mBAAmB,KAAK,oBAAoB;AAAA,QAC9C,CAAC;AAAA,MACH,CAAC;AACD,UAAI,CAAC,IAAI,GAAI,QAAO;AACpB,YAAM,OAAQ,MAAM,IAAI,KAAK;AAC7B,YAAM,QAAQ,MAAM;AACpB,UAAI,SAAS,OAAO,UAAU,UAAU;AACtC,gBAAQ;AACR,gBAAQ,KAAK;AAAA,MACf;AACA,aAAO;AAAA,IACT,QAAQ;AAEN,aAAO;AAAA,IACT;AAAA,EACF;AAEA,QAAM,4BAA4B,YAA2D;AAC3F,QAAI,UAAU;AACd,QAAI;AAEJ,QAAI;AACF,UAAI,OAAO,UAAU,YAAY;AAC/B,cAAM,MAAM,MAAM,MAAM,gBAAgB,GAAG;AAAA,UACzC,QAAQ;AAAA,UACR,SAAS;AAAA,YACP,eAAe,UAAU,KAAK,YAAY,CAAC;AAAA,UAC7C;AAAA,QACF,CAAC;AACD,YAAI,IAAI,IAAI;AACV,gBAAM,OAAQ,MAAM,IAAI,KAAK;AAC7B,uBAAa,MAAM;AACnB,oBAAU,gBAAgB,UAAa,eAAe;AAAA,QACxD;AAAA,MAEF;AAAA,IACF,QAAQ;AAAA,IAER;AAEA,QAAI,CAAC,QAAS,QAAO,EAAE,SAAS,OAAO,OAAO,MAAM;AAEpD,UAAM,QAAQ,MAAM,WAAW;AAC/B,QAAI,eAAe,OAAW,eAAc;AAC5C,WAAO,EAAE,SAAS,MAAM,MAAM;AAAA,EAChC;AAEA,SAAO;AAAA,IACL,UAAU,MAAM;AAAA,IAChB,kBAAkB,CAAC,QAAQ,MAAM,GAAG,GAAG,WAAW;AAAA,IAClD,gBAAgB,CAAC,QAAQ;AACvB,YAAM,IAAI,MAAM,GAAG;AACnB,UAAI,CAAC,GAAG,QAAS,QAAO;AACxB,aAAO,EAAE,WAAW,OAAO,EAAE,UAAU;AAAA,IACzC;AAAA,IACA,uBAAuB,CAAC,QAAQ,MAAM,GAAG,GAAG,WAAW;AAAA,IACvD;AAAA,IACA;AAAA,IACA,OAAO,MAAM;AACX,cAAQ,CAAC;AACT,UAAI;AACF,yBAAiB,GAAG,WAAW,iBAAiB;AAAA,MAClD,QAAQ;AAAA,MAER;AAAA,IACF;AAAA,EACF;AACF;;;ACzRA,IAAM,UAAU,KAAK,KAAK;AAE1B,IAAM,SAAS,KAAK,KAAK,KAAK;AAE9B,IAAM,UAAU;AA2BhB,IAAM,QAAQ,CAAC,QAA6C;AAC1D,MAAI,CAAC,IAAK,QAAO;AACjB,MAAI;AACF,UAAM,IAAI,KAAK,MAAM,GAAG;AACxB,QACE,KACA,OAAO,MAAM,YACb,OAAO,EAAE,OAAO,YAChB,OAAO,EAAE,UAAU,YACnB,OAAO,EAAE,SAAS,UAClB;AACA,aAAO;AAAA,IACT;AAAA,EACF,QAAQ;AAAA,EAER;AACA,SAAO;AACT;AAEO,SAAS,qBAAqB,EAAE,KAAK,SAAS,MAAM,GAAgC;AACzF,QAAM,OAAO,MAA4B;AACvC,QAAI;AACF,aAAO,MAAM,QAAQ,GAAG,QAAQ,OAAO,KAAK,IAAI;AAAA,IAClD,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AAEA,QAAM,OAAO,CAAC,MAA2B;AACvC,QAAI;AACF,cAAQ,GAAG,QAAQ,SAAS,KAAK,UAAU,CAAC,CAAC;AAAA,IAC/C,QAAQ;AAAA,IAER;AAAA,EACF;AAEA,QAAM,QAAQ,CAAC,MAA6B;AAC1C,UAAM,IAAmB,EAAE,IAAI,MAAM,GAAG,OAAO,GAAG,MAAM,EAAE;AAC1D,SAAK,CAAC;AACN,WAAO;AAAA,EACT;AAEA,SAAO;AAAA,IACL,eAAuB;AACrB,YAAM,IAAI,IAAI;AACd,YAAM,WAAW,KAAK;AACtB,YAAM,UAAU,CAAC,YAAY,IAAI,SAAS,OAAO,WAAW,IAAI,SAAS,QAAQ;AACjF,UAAI,SAAS;AACX,eAAO,MAAM,CAAC,EAAE;AAAA,MAClB;AACA,eAAS,OAAO;AAChB,WAAK,QAAQ;AACb,aAAO,SAAS;AAAA,IAClB;AAAA,IACA,QAAc;AACZ,UAAI;AACF,gBAAQ,GAAG,WAAW,OAAO;AAAA,MAC/B,QAAQ;AAAA,MAER;AAAA,IACF;AAAA,EACF;AACF;;;AClGA,IAAM,aAAa;AACnB,IAAM,YAAY;AAClB,IAAM,oBAAoB;AAE1B,IAAM,gBAAgB;AACtB,IAAM,eAAe;AACrB,IAAM,mBAAmB;AAQzB,IAAM,YAAY,MAAe,OAAO,WAAW;AAE5C,SAAS,gBACd,WACA,aACW;AACX,MAAI,SAAyB,CAAC;AAC9B,MAAI,cAA8B,CAAC;AACnC,MAAI,eAAe;AACnB,MAAI,aAAmD;AACvD,MAAI,WAAkD;AAEtD,QAAM,WAAW,MAAc;AAC7B,UAAM,EAAE,SAAS,IAAI,UAAU;AAC/B,UAAM,OAAO,SAAS,QAAQ,QAAQ,EAAE;AACxC,WAAO,GAAG,IAAI,qBAAqB,mBAAmB,YAAY,CAAC,CAAC;AAAA,EACtE;AAEA,QAAM,YAAY,CAAC,UAAkC,KAAK,UAAU,EAAE,MAAM,CAAC;AAE7E,QAAM,gBAAgB,MAAY;AAChC,QAAI,eAAe,QAAQ,YAAY,WAAW,EAAG;AACrD,UAAM,QAAQ,KAAK,IAAI,gBAAgB,KAAK,cAAc,YAAY;AACtE,QAAI;AACF,mBAAa,WAAW,MAAM;AAC5B,qBAAa;AACb,cAAM,KAAK;AAAA,MACb,GAAG,KAAK;AAAA,IACV,QAAQ;AACN,mBAAa;AAAA,IACf;AAAA,EACF;AAEA,QAAM,UAAU,CAAC,UAAgC;AAE/C,kBAAc,YAAY,OAAO,KAAK;AACtC,QAAI,YAAY,SAAS,kBAAkB;AACzC,oBAAc,YAAY,MAAM,YAAY,SAAS,gBAAgB;AAAA,IACvE;AACA,mBAAe,KAAK,IAAI,eAAe,GAAG,CAAC;AAC3C,kBAAc;AAAA,EAChB;AAEA,QAAM,aAAa,CAAC,KAAa,SAA0B;AACzD,QAAI;AACF,UAAI,OAAO,cAAc,eAAe,OAAO,UAAU,eAAe,YAAY;AAClF,eAAO;AAAA,MACT;AAUA,YAAM,OAAO,IAAI,KAAK,CAAC,IAAI,GAAG,EAAE,MAAM,aAAa,CAAC;AACpD,aAAO,UAAU,WAAW,KAAK,IAAI;AAAA,IACvC,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AAOA,QAAM,kBAAkB,OAAO,KAAe,UAAyC;AACrF,QAAI;AACJ,QAAI;AACF,eAAS,MAAM,IAAI,KAAK;AAAA,IAC1B,QAAQ;AAAA,IAER;AACA,YAAQ;AAAA,MACN,wDAAwD,IAAI,MAAM,eAAe,MAAM,MAAM;AAAA,MAC7F;AAAA,IACF;AAAA,EACF;AAEA,QAAM,YAAY,CAAC,KAAa,MAAc,UAAgC;AAC5E,QAAI;AACF,UAAI,OAAO,UAAU,YAAY;AAC/B,gBAAQ,KAAK;AACb;AAAA,MACF;AACA,YAAM,KAAK;AAAA,QACT,QAAQ;AAAA,QACR,WAAW;AAAA,QACX,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,QAC9C;AAAA,MACF,CAAC,EACE,KAAK,CAAC,QAAQ;AACb,YAAI,IAAI,IAAI;AAEV,yBAAe;AACf,cAAI,YAAY,SAAS,EAAG,eAAc;AAC1C;AAAA,QACF;AACA,YAAI,IAAI,UAAU,KAAK;AAErB,kBAAQ,KAAK;AACb;AAAA,QACF;AAEA,aAAK,gBAAgB,KAAK,KAAK;AAAA,MACjC,CAAC,EACA,MAAM,MAAM;AAEX,gBAAQ,KAAK;AAAA,MACf,CAAC;AAAA,IACL,QAAQ;AACN,cAAQ,KAAK;AAAA,IACf;AAAA,EACF;AAEA,WAAS,MAAM,YAAY,OAAa;AACtC,QAAI,CAAC,UAAU,GAAG;AAChB,eAAS,CAAC;AACV;AAAA,IACF;AAEA,UAAM,UAAU,YAAY,OAAO,MAAM;AACzC,aAAS,CAAC;AACV,kBAAc,CAAC;AACf,QAAI,QAAQ,WAAW,EAAG;AAE1B,QAAI;AACF,YAAM,MAAM,SAAS;AACrB,eAAS,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK,WAAW;AAClD,cAAM,QAAQ,QAAQ,MAAM,GAAG,IAAI,SAAS;AAC5C,cAAM,OAAO,UAAU,KAAK;AAC5B,YAAI,aAAa,WAAW,KAAK,IAAI,EAAG;AACxC,YAAI,WAAW;AAEb,oBAAU,KAAK,MAAM,KAAK;AAC1B;AAAA,QACF;AACA,kBAAU,KAAK,MAAM,KAAK;AAAA,MAC5B;AAAA,IACF,QAAQ;AAAA,IAER;AAAA,EACF;AAEA,QAAM,qBAAqB,MAAY;AACrC,QAAI;AACF,UAAI,OAAO,aAAa,eAAe,SAAS,OAAQ,OAAM,IAAI;AAAA,IACpE,QAAQ;AAAA,IAER;AAAA,EACF;AAEA,QAAM,aAAa,MAAY;AAC7B,UAAM,IAAI;AAAA,EACZ;AAEA,QAAM,UAAU,CAAC,UAA8B;AAC7C,QAAI,CAAC,UAAU,EAAG;AAClB,QAAI;AACF,aAAO,KAAK,KAAK;AACjB,UAAI,OAAO,UAAU,WAAY,OAAM,KAAK;AAAA,IAC9C,QAAQ;AAAA,IAER;AAAA,EACF;AAEA,QAAM,WAAW,MAAY;AAC3B,QAAI;AACF,UAAI,aAAa,MAAM;AACrB,sBAAc,QAAQ;AACtB,mBAAW;AAAA,MACb;AACA,UAAI,eAAe,MAAM;AACvB,qBAAa,UAAU;AACvB,qBAAa;AAAA,MACf;AACA,UAAI,OAAO,aAAa,aAAa;AACnC,iBAAS,oBAAoB,oBAAoB,kBAAkB;AAAA,MACrE;AACA,UAAI,OAAO,WAAW,aAAa;AACjC,eAAO,oBAAoB,YAAY,UAAU;AACjD,eAAO,oBAAoB,gBAAgB,UAAU;AAAA,MACvD;AAAA,IACF,QAAQ;AAAA,IAER;AAAA,EACF;AAGA,MAAI,UAAU,GAAG;AACf,QAAI;AACF,iBAAW,YAAY,MAAM,MAAM,KAAK,GAAG,iBAAiB;AAC5D,UAAI,OAAO,aAAa,aAAa;AACnC,iBAAS,iBAAiB,oBAAoB,kBAAkB;AAAA,MAClE;AACA,aAAO,iBAAiB,YAAY,UAAU;AAC9C,aAAO,iBAAiB,gBAAgB,UAAU;AAAA,IACpD,QAAQ;AAAA,IAER;AAAA,EACF;AAEA,SAAO,EAAE,SAAS,OAAO,SAAS;AACpC;;;AC1NA,IAAM,WAAW,CAAC,UAAU,UAAU,YAAY,QAAQ,SAAS;AAM5D,SAAS,SAAS,QAAwC;AAC/D,QAAM,QAAgC,CAAC;AACvC,MAAI,CAAC,OAAQ,QAAO;AAEpB,MAAI;AACF,UAAM,SAAS,IAAI,gBAAgB,MAAM;AACzC,eAAW,OAAO,UAAU;AAC1B,YAAM,QAAQ,OAAO,IAAI,OAAO,GAAG,EAAE;AACrC,UAAI,MAAO,OAAM,QAAQ,GAAG,EAAE,IAAI;AAAA,IACpC;AAAA,EACF,QAAQ;AAAA,EAER;AAEA,SAAO;AACT;;;AChBO,IAAM,eAAe;AAE5B,IAAM,WAAW;AAEjB,IAAM,cAAc;AAEpB,IAAM,UAAU;AAChB,IAAM,iBAAiB;AAEvB,IAAM,iBAAkC;AAAA;AAAA;AAAA;AAAA,EAItC,UAAU;AAAA,EACV,aAAa;AAAA,EACb,kBAAkB;AACpB;AAEA,IAAMA,aAAY,MAAe,OAAO,WAAW;AAKnD,IAAM,4BAA4B;AAyBlC,IAAM,QAAe;AAAA,EACnB,aAAa;AAAA,EACb,eAAe;AAAA,EACf,UAAU;AAAA,EACV,QAAQ,EAAE,GAAG,eAAe;AAAA,EAC5B,YAAY;AAAA,EACZ,kBAAkB,CAAC;AAAA,EACnB,WAAW;AAAA,EACX,SAAS;AAAA,EACT,gBAAgB;AAAA,EAChB,qBAAqB;AAAA,EACrB,eAAe,CAAC;AAAA,EAChB,YAAY;AAAA,EACZ,aAAa;AAAA,EACb,kBAAkB,oBAAI,IAAI;AAAA,EAC1B,YAAY;AACd;AAIA,IAAMC,oBAAmB,MAAsB;AAC7C,MAAI;AACF,QAAI,OAAO,iBAAiB,YAAa,QAAO;AAChD,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,IAAM,qBAAqB,MAAsB;AAC/C,MAAI;AACF,QAAI,OAAO,mBAAmB,YAAa,QAAO;AAClD,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,IAAM,aAAa,CAAC,QAA+B;AACjD,MAAI;AACF,WAAOA,kBAAiB,GAAG,QAAQ,GAAG,KAAK;AAAA,EAC7C,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,IAAM,aAAa,CAAC,KAAa,UAAwB;AACvD,MAAI;AACF,IAAAA,kBAAiB,GAAG,QAAQ,KAAK,KAAK;AAAA,EACxC,QAAQ;AAAA,EAER;AACF;AAEA,IAAM,gBAAgB,CAAC,QAAsB;AAC3C,MAAI;AACF,IAAAA,kBAAiB,GAAG,WAAW,GAAG;AAAA,EACpC,QAAQ;AAAA,EAER;AACF;AAEA,IAAM,aAAa,MAAc;AAC/B,MAAI;AACF,QAAI,OAAO,WAAW,eAAe,OAAO,OAAO,eAAe,YAAY;AAC5E,aAAO,OAAO,WAAW;AAAA,IAC3B;AAAA,EACF,QAAQ;AAAA,EAER;AAEA,SAAO,QAAQ,KAAK,IAAI,EAAE,SAAS,EAAE,CAAC,IAAI,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,MAAM,GAAG,EAAE,CAAC;AACnF;AAEA,IAAM,yBAAyB,MAAc;AAC3C,QAAM,WAAW,WAAW,OAAO;AACnC,MAAI,SAAU,QAAO;AACrB,QAAM,QAAQ,WAAW;AACzB,aAAW,SAAS,KAAK;AACzB,SAAO;AACT;AAIA,IAAM,iBAAiB,MAA+B;AACpD,QAAM,QAAiC;AAAA,IACrC,MAAM;AAAA,IACN,cAAc;AAAA,EAChB;AACA,MAAI;AACF,QAAI,OAAO,aAAa,aAAa;AACnC,YAAM,eAAe,SAAS;AAC9B,YAAM,QAAQ,SAAS;AACvB,YAAM,YAAY,SAAS;AAAA,IAC7B;AACA,QAAI,OAAO,aAAa,aAAa;AACnC,YAAM,YAAY,SAAS;AAAA,IAC7B;AACA,QAAI,OAAO,WAAW,eAAe,OAAO,QAAQ;AAClD,YAAM,iBAAiB,OAAO,OAAO;AACrC,YAAM,gBAAgB,OAAO,OAAO;AAAA,IACtC;AACA,QAAI,OAAO,WAAW,aAAa;AACjC,YAAM,mBAAmB,OAAO;AAChC,YAAM,kBAAkB,OAAO;AAAA,IACjC;AACA,QAAI,OAAO,cAAc,aAAa;AACpC,aAAO,OAAO,OAAO,YAAY,UAAU,WAAW,UAAU,QAAQ,CAAC;AAAA,IAC3E;AAAA,EACF,QAAQ;AAAA,EAER;AACA,SAAO;AACT;AAKA,IAAM,2BAA2B,MAA+B;AAC9D,MAAI;AACF,UAAM,SAAS,OAAO,aAAa,cAAc,SAAS,SAAS;AACnE,UAAM,WAAW,OAAO,aAAa,cAAc,SAAS,WAAW;AACvE,UAAM,WAAW,OAAO,aAAa,cAAc,SAAS,WAAW;AACvE,UAAM,MAAM,SAAS,MAAM;AAC3B,WAAO,EAAE,GAAG,KAAK,eAAe,gBAAgB,UAAU,KAAK,QAAQ,EAAE;AAAA,EAC3E,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACF;AAIA,IAAM,yBAAyB,CAAC,cAA2D;AACzF,MAAI,CAAC,UAAW,QAAO,yBAAyB;AAChD,MAAI,CAAC,MAAM,kBAAkB,MAAM,eAAe,cAAc,WAAW;AACzE,UAAM,iBAAiB,EAAE,WAAW,OAAO,yBAAyB,EAAE;AAAA,EACxE;AACA,SAAO,MAAM,eAAe;AAC9B;AAIA,IAAM,cAAc,MAAY;AAC9B,MAAI,MAAM,cAAe;AACzB,QAAM,gBAAgB;AACtB,MAAI;AACF,YAAQ,KAAK,2DAA2D;AAAA,EAC1E,QAAQ;AAAA,EAER;AACF;AAGA,IAAM,kBAAkB,CACtB,OACA,YACA,kBACS;AACT,MAAI,CAAC,MAAM,eAAe,CAAC,MAAM,WAAW;AAC1C,gBAAY;AACZ;AAAA,EACF;AACA,MAAI;AACF,UAAM,YAAY,MAAM,SAAS,aAAa;AAC9C,UAAM,eAAe,YAAY,EAAE,aAAa,UAAU,IAAI,CAAC;AAC/D,UAAM,UAAwB;AAAA,MAC5B;AAAA,MACA,aAAa,MAAM;AAAA,MACnB,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,MAClC,YAAY;AAAA,QACV,GAAG,eAAe;AAAA,QAClB,GAAG;AAAA,QACH,GAAG,uBAAuB,SAAS;AAAA,QACnC,GAAI,cAAc,CAAC;AAAA,MACrB;AAAA,IACF;AACA,QAAI,cAAe,SAAQ,iBAAiB;AAC5C,UAAM,UAAU,QAAQ,OAAO;AAAA,EACjC,QAAQ;AAAA,EAER;AACF;AAKA,IAAM,cAAc,CAAC,UAAqD;AACxE,QAAM,OAAyC,CAAC;AAChD,aAAW,CAAC,KAAK,CAAC,KAAK,OAAO,QAAQ,KAAK,GAAG;AAC5C,QAAI,CAAC,EAAE,QAAS;AAChB,SAAK,GAAG,IAAI,EAAE,WAAW,OAAO,EAAE,UAAU;AAAA,EAC9C;AACA,SAAO;AACT;AAEA,IAAM,oBAAoB,MAAY;AACpC,MAAI,CAAC,MAAM,WAAY;AACvB,QAAM,OAAO,YAAY,MAAM,WAAW,SAAS,CAAC;AACpD,aAAW,MAAM,MAAM,eAAe;AACpC,QAAI;AACF,SAAG,IAAI;AAAA,IACT,QAAQ;AAAA,IAER;AAAA,EACF;AACF;AAOA,IAAM,eAAe,MAAY;AAC/B,MAAI,CAAC,MAAM,WAAY;AACvB,QAAM,WACH,0BAA0B,EAC1B,KAAK,MAAM;AACV,UAAM,cAAc;AACpB,sBAAkB;AAAA,EACpB,CAAC,EACA,MAAM,MAAM;AAAA,EAEb,CAAC;AACL;AAIA,IAAM,oBAAoB,CAAC,KAAa,aAAqC;AAC3E,MAAI;AAEF,QAAI,CAAC,MAAM,YAAa;AACxB,UAAM,OAAO,OAAO,QAAQ;AAC5B,UAAM,QAAQ,GAAG,GAAG,IAAI,IAAI;AAC5B,QAAI,MAAM,iBAAiB,IAAI,KAAK,EAAG;AACvC,UAAM,iBAAiB,IAAI,KAAK;AAChC,oBAAgB,wBAAwB;AAAA,MACtC,eAAe;AAAA,MACf,wBAAwB;AAAA,IAC1B,CAAC;AAAA,EACH,QAAQ;AAAA,EAER;AACF;AAIA,SAAS,KAAK,UAAkB,MAA+B;AAC7D,MAAI,MAAM,YAAa;AACvB,MAAI,CAACD,WAAU,GAAG;AAEhB;AAAA,EACF;AAEA,QAAM,WAAW;AACjB,QAAM,SAAS;AAAA,IACb,UAAU,MAAM,YAAY,eAAe;AAAA,IAC3C,aAAa,MAAM,eAAe,eAAe;AAAA,IACjD,kBAAkB,MAAM,oBAAoB,eAAe;AAAA,EAC7D;AAEA,QAAM,aAAa,uBAAuB;AAC1C,QAAM,UAAU,qBAAqB;AAAA,IACnC,KAAK,MAAM,KAAK,IAAI;AAAA,IACpB,SAAS;AAAA,IACT,OAAO;AAAA,EACT,CAAC;AACD,QAAM,YAAY;AAAA,IAChB,MAAM,MAAM;AAAA,IACZ,MAAM,MAAM;AAAA,EACd;AACA,QAAM,aAAa,iBAAiB;AAAA,IAClC,YAAY,MAAM,MAAM,OAAO;AAAA,IAC/B,aAAa,MAAM,MAAM;AAAA,IACzB,eAAe,MAAM,MAAM;AAAA,IAC3B,qBAAqB,MAAM,MAAM;AAAA,EACnC,CAAC;AACD,QAAM,cAAc;AAGpB,eAAa;AAKb,MAAI,MAAM,4BAA4B;AACpC,UAAM,aAAa,KAAK,IAAI,KAAK,4BAA4B,yBAAyB;AACtF,UAAM,aAAa,YAAY,MAAM,aAAa,GAAG,UAAU;AAAA,EACjE;AAEA,MAAI,MAAM,OAAO,aAAa;AAC5B,UAAM,MAA0B;AAAA,MAC9B,QAAQ,MAAM;AAAA,MACd,SAAS;AAAA,IACX;AACA,QAAI;AACF,YAAM,sBAAsB,gBAAgB,GAAG;AAAA,IACjD,QAAQ;AAAA,IAER;AAAA,EACF,WAAW,MAAM,OAAO,kBAAkB;AAGxC,oBAAgB,WAAW;AAAA,EAC7B;AACF;AAEA,SAAS,QAAQ,OAAe,YAA4C;AAC1E,kBAAgB,OAAO,UAAU;AACnC;AAEA,SAAS,SAAS,YAAoB,aAA6C;AACjF,MAAI,CAAC,MAAM,aAAa;AACtB,gBAAY;AACZ;AAAA,EACF;AACA,QAAM,aAAa,MAAM;AACzB,QAAM,aAAa;AACnB,aAAW,SAAS,UAAU;AAC9B,MAAI,YAAa,OAAM,mBAAmB,EAAE,GAAG,MAAM,kBAAkB,GAAG,YAAY;AACtF,kBAAgB,aAAa;AAAA,IAC3B,MAAM,eAAe,CAAC;AAAA,IACtB,GAAI,eAAe,aAAa,EAAE,mBAAmB,WAAW,IAAI,CAAC;AAAA,EACvE,CAAC;AAED,QAAM,iBAAiB,MAAM;AAE7B,eAAa;AACf;AAEA,SAAS,QAAc;AACrB,MAAI;AAEF,UAAM,KAAKC,kBAAiB;AAC5B,QAAI,IAAI;AACN,YAAM,WAAqB,CAAC;AAC5B,eAAS,IAAI,GAAG,IAAI,GAAG,QAAQ,KAAK,GAAG;AACrC,cAAM,MAAM,GAAG,IAAI,CAAC;AACpB,YAAI,KAAK,WAAW,cAAc,EAAG,UAAS,KAAK,GAAG;AAAA,MACxD;AACA,iBAAW,OAAO,SAAU,eAAc,GAAG;AAAA,IAC/C;AAAA,EACF,QAAQ;AAAA,EAER;AACA,QAAM,aAAa,WAAW;AAC9B,aAAW,SAAS,MAAM,UAAU;AAEpC,QAAM,SAAS,MAAM;AACrB,QAAM,iBAAiB;AAEvB,QAAM,mBAAmB,CAAC;AAC1B,QAAM,cAAc;AACpB,QAAM,iBAAiB,MAAM;AAC7B,QAAM,YAAY,MAAM;AAC1B;AAIA,SAAS,iBAAiB,KAAsB;AAC9C,QAAM,SAAS,MAAM,YAAY,iBAAiB,GAAG,KAAK;AAC1D,oBAAkB,KAAK,MAAM;AAC7B,SAAO;AACT;AAEA,SAAS,eAAe,KAA+B;AACrD,QAAM,SAAS,MAAM,YAAY,eAAe,GAAG,KAAK;AACxD,oBAAkB,KAAK,MAAM;AAC7B,SAAO;AACT;AAEA,SAAS,sBAAsB,KAAsB;AACnD,SAAO,MAAM,YAAY,sBAAsB,GAAG,KAAK;AACzD;AAEA,SAAS,eAAe,IAA6D;AACnF,QAAM,cAAc,KAAK,EAAE;AAE3B,MAAI,MAAM,eAAe,MAAM,YAAY;AACzC,QAAI;AACF,SAAG,YAAY,MAAM,WAAW,SAAS,CAAC,CAAC;AAAA,IAC7C,QAAQ;AAAA,IAER;AAAA,EACF;AACF;AAMA,SAAS,sBAA4B;AACnC,eAAa;AACf;AAGA,SAAS,yBAA+B;AACtC,MAAI,MAAM,eAAe,MAAM;AAC7B,kBAAc,MAAM,UAAU;AAC9B,UAAM,aAAa;AAAA,EACrB;AACF;AAIA,IAAM,YAAY;AAAA,EAChB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAeA,IAAO,gBAAQ;",
6
6
  "names": ["isBrowser", "safeLocalStorage"]
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clickroom/sdk-js",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "Browser SDK for Clickroom — product analytics, feature flags and experiments.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://github.com/room-co/clickroom/tree/main/packages/sdk-js#readme",
@@ -50,7 +50,7 @@
50
50
  "@clickroom/config": "0.0.0"
51
51
  },
52
52
  "scripts": {
53
- "build": "node esbuild.config.mjs && tsc --emitDeclarationOnly",
53
+ "build": "node esbuild.config.mjs && tsc -p tsconfig.build.json --emitDeclarationOnly",
54
54
  "dev": "node esbuild.config.mjs --watch",
55
55
  "typecheck": "tsc --noEmit",
56
56
  "test": "vitest run",
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../src/elements-chain.ts", "../src/autocapture.ts", "../src/channel.ts", "../src/device.ts", "../src/flags.ts", "../src/session.ts", "../src/transport.ts", "../src/utm.ts", "../src/index.ts", "../src/global.ts"],
4
- "sourcesContent": ["// @clickroom/sdk-js \u2014 DOM `elements_chain` serialization (D-07).\n//\n// Serializes a click/submit/change target and its ancestors into a single\n// compact string that ClickHouse materializes into `elements_chain_text` /\n// `elements_chain_href` via `extract(elements_chain, 'text=\"([^\"]*)\"')` and the\n// equivalent `href=\"...\"`. The literal `text=\"` and `href=\"` tokens must appear\n// verbatim for those extractors to match, and values must not contain a raw `\"`\n// (the `[^\"]*` capture stops at the first quote) \u2014 so we backslash-escape.\n\n// Cap how far up the tree we walk, to bound the serialized size.\nconst MAX_DEPTH = 15\n// Truncate captured text to keep the chain compact on the wire.\nconst MAX_TEXT_LEN = 255\n\n// Elements that are inherently \"interesting\" click targets for autocapture.\nconst INTERESTING_TAGS = new Set(['a', 'button', 'input', 'select', 'textarea', 'label'])\n\n// Class / attribute opt-out markers: skip capture for these subtrees.\nconst OPT_OUT_CLASS = 'ch-no-capture'\nconst OPT_OUT_ATTR = 'data-ch-no-capture'\n\n// Escape a value so it survives inside `attr=\"<value>\"` and stays parseable:\n// backslash and double-quote are escaped; separators (`;` `|`) and newlines are\n// escaped so they never terminate a pair or an element boundary.\nfunction escapeValue(value: string): string {\n return value\n .replace(/\\\\/g, '\\\\\\\\')\n .replace(/\"/g, '\\\\\"')\n .replace(/;/g, '\\\\;')\n .replace(/\\|/g, '\\\\|')\n .replace(/\\r?\\n/g, ' ')\n .trim()\n}\n\n// 1-indexed position among *all* siblings.\nfunction nthChild(el: Element): number {\n let n = 1\n let sib = el.previousElementSibling\n while (sib) {\n n += 1\n sib = sib.previousElementSibling\n }\n return n\n}\n\n// 1-indexed position among siblings sharing the same tag name.\nfunction nthOfType(el: Element): number {\n let n = 1\n const tag = el.tagName\n let sib = el.previousElementSibling\n while (sib) {\n if (sib.tagName === tag) {\n n += 1\n }\n sib = sib.previousElementSibling\n }\n return n\n}\n\n// Best-effort trimmed text for an element, truncated to MAX_TEXT_LEN.\nfunction elementText(el: Element): string {\n const raw = (el as HTMLElement).innerText ?? el.textContent ?? ''\n const trimmed = raw.replace(/\\s+/g, ' ').trim()\n return trimmed.length > MAX_TEXT_LEN ? trimmed.slice(0, MAX_TEXT_LEN) : trimmed\n}\n\n// Serialize a single element into `tag:attr=\"v\";attr2=\"v2\"`.\nfunction serializeElement(el: Element): string {\n const tag = el.tagName.toLowerCase()\n const pairs: string[] = []\n\n const id = el.getAttribute('id')\n if (id) {\n pairs.push(`attr__id=\"${escapeValue(id)}\"`)\n }\n\n const className = typeof el.className === 'string' ? el.className : ''\n if (className) {\n pairs.push(`attr__class=\"${escapeValue(className)}\"`)\n }\n\n if (tag === 'a') {\n const href = el.getAttribute('href')\n if (href) {\n pairs.push(`href=\"${escapeValue(href)}\"`)\n }\n }\n\n const text = elementText(el)\n if (text) {\n pairs.push(`text=\"${escapeValue(text)}\"`)\n }\n\n pairs.push(`nth-child=\"${nthChild(el)}\"`)\n pairs.push(`nth-of-type=\"${nthOfType(el)}\"`)\n\n // data-* attributes as attr__data-*.\n const attrs = el.attributes\n for (let i = 0; i < attrs.length; i += 1) {\n const attr = attrs.item(i)\n if (attr?.name.startsWith('data-')) {\n pairs.push(`attr__${attr.name}=\"${escapeValue(attr.value)}\"`)\n }\n }\n\n return `${tag}:${pairs.join(';')}`\n}\n\n/**\n * Serialize `target` and its ancestors (up to <body>, capped at MAX_DEPTH) into\n * an `elements_chain` string: each element as `tag:attr=\"v\";attr2=\"v2\"`, joined\n * by `|` with the target element first and its parents following.\n */\nexport function serializeElementsChain(target: Element): string {\n const parts: string[] = []\n let el: Element | null = target\n let depth = 0\n\n while (el && depth < MAX_DEPTH) {\n const tag = el.tagName.toLowerCase()\n if (tag === 'body' || tag === 'html') {\n break\n }\n parts.push(serializeElement(el))\n el = el.parentElement\n depth += 1\n }\n\n return parts.join('|')\n}\n\n// True if any ancestor (inclusive) opts out of capture.\nfunction isOptedOut(el: Element): boolean {\n let node: Element | null = el\n while (node) {\n if (node.classList?.contains(OPT_OUT_CLASS) || node.hasAttribute(OPT_OUT_ATTR)) {\n return true\n }\n node = node.parentElement\n }\n return false\n}\n\n// True if the element is an inherently interesting click target.\nfunction isInteresting(el: Element): boolean {\n const tag = el.tagName.toLowerCase()\n if (INTERESTING_TAGS.has(tag)) {\n return true\n }\n if (el.hasAttribute('data-ch-capture')) {\n return true\n }\n if (el.getAttribute('role')) {\n return true\n }\n // Inline handler attribute (`onclick=\"...\"`).\n if (el.hasAttribute('onclick')) {\n return true\n }\n return false\n}\n\n/**\n * Climb from the raw event target to the nearest \"interesting\" element to\n * attribute the event to (a, button, input, select, textarea, label, or any\n * element with a role / onclick / [data-ch-capture]). Returns null when nothing\n * meaningful is found or the subtree is opted out of capture.\n */\nexport function getEventTarget(el: Element): Element | null {\n let node: Element | null = el\n let depth = 0\n while (node && depth < MAX_DEPTH) {\n if (isOptedOut(node)) {\n return null\n }\n if (isInteresting(node)) {\n return node\n }\n node = node.parentElement\n depth += 1\n }\n return null\n}\n", "// @clickroom/sdk-js \u2014 DOM autocapture + pageview / SPA navigation tracking.\n//\n// Attaches capture-phase listeners for click/submit/change, serializes the\n// target's DOM path into `elements_chain`, and emits `$autocapture`. Also emits\n// `$pageview` on load and on SPA navigation (history pushState/replaceState,\n// popstate, hashchange). Every handler is wrapped so a listener error never\n// breaks the host page. `initAutocapture` returns a teardown that removes all\n// listeners and restores the patched history methods.\n\nimport { getEventTarget, serializeElementsChain } from './elements-chain'\nimport type { AutocaptureContext } from './types'\n\ntype HistoryMethod = History['pushState']\n\n// Common URL/location properties attached to every autocapture + pageview event.\nfunction locationProps(): Record<string, unknown> {\n return {\n $current_url: location.href,\n $host: location.host,\n $pathname: location.pathname,\n }\n}\n\n/**\n * Wire up autocapture + pageview tracking. Returns a teardown function that\n * removes every listener and restores `history.pushState`/`replaceState`.\n * A no-op teardown is returned in non-DOM (SSR) environments.\n */\nexport function initAutocapture(ctx: AutocaptureContext): () => void {\n if (typeof document === 'undefined' || typeof window === 'undefined') {\n return () => {}\n }\n\n // ---- Autocapture (click / submit / change) -----------------------------\n\n const handleInteraction = (event: Event): void => {\n try {\n const raw = event.target\n if (!(raw instanceof Element)) {\n return\n }\n const target = getEventTarget(raw)\n if (!target) {\n return\n }\n const chain = serializeElementsChain(target)\n ctx.capture(\n '$autocapture',\n {\n $event_type: event.type,\n ...locationProps(),\n },\n chain,\n )\n } catch {\n // Never let a listener error surface to the host page.\n }\n }\n\n const interactionEvents: string[] = ['click', 'submit', 'change']\n\n if (ctx.config.autocapture) {\n for (const type of interactionEvents) {\n document.addEventListener(type, handleInteraction, true)\n }\n }\n\n // ---- Pageviews (initial + SPA navigation) ------------------------------\n\n let lastUrl = ''\n\n const emitPageview = (): void => {\n try {\n if (!ctx.config.capture_pageview) {\n return\n }\n if (location.href === lastUrl) {\n return\n }\n lastUrl = location.href\n ctx.capture('$pageview', {\n ...locationProps(),\n $referrer: document.referrer,\n })\n } catch {\n // Swallow: pageview tracking must never break navigation.\n }\n }\n\n const origPushState: HistoryMethod = history.pushState.bind(history)\n const origReplaceState: HistoryMethod = history.replaceState.bind(history)\n let patched = false\n\n if (ctx.config.capture_pageview) {\n history.pushState = (...args: Parameters<HistoryMethod>): void => {\n origPushState(...args)\n emitPageview()\n }\n history.replaceState = (...args: Parameters<HistoryMethod>): void => {\n origReplaceState(...args)\n emitPageview()\n }\n patched = true\n\n window.addEventListener('popstate', emitPageview)\n window.addEventListener('hashchange', emitPageview)\n\n // Initial pageview for the current document.\n emitPageview()\n }\n\n // ---- Teardown ----------------------------------------------------------\n\n return () => {\n if (ctx.config.autocapture) {\n for (const type of interactionEvents) {\n document.removeEventListener(type, handleInteraction, true)\n }\n }\n if (patched) {\n history.pushState = origPushState\n history.replaceState = origReplaceState\n window.removeEventListener('popstate', emitPageview)\n window.removeEventListener('hashchange', emitPageview)\n }\n }\n}\n", "// @clickroom/sdk-js \u2014 traffic channel classification.\n// PURE, zero-dependency: this package bundles to a CDN script, so no imports.\n// Classifies `document.referrer` + the session's UTM params into a\n// PostHog-style `$channel_type`, using small static domain lists \u2014 no\n// external lookup/API call, synchronous, same style as device.ts's UA parser.\n\n// utm_medium values that indicate paid traffic.\nconst PAID_MEDIUM_RE = /^(cpc|ppc|paid|display)$/i\n\n// Referring-domain substrings for well-known search engines. Matched with a\n// simple `includes` so subdomains/ccTLDs (e.g. `www.google.co.uk`) still hit.\nconst SEARCH_ENGINE_DOMAINS = [\n 'google.',\n 'bing.com',\n 'duckduckgo.com',\n 'yahoo.',\n 'baidu.com',\n 'yandex.',\n 'ecosia.org',\n 'ask.com',\n 'aol.com',\n]\n\n// Referring-domain substrings for well-known social platforms.\nconst SOCIAL_DOMAINS = [\n 'facebook.com',\n 'fb.com',\n 'twitter.com',\n 'x.com',\n 't.co',\n 'linkedin.com',\n 'instagram.com',\n 'tiktok.com',\n 'reddit.com',\n 'pinterest.',\n 'youtube.com',\n 'threads.net',\n]\n\nconst matchesDomain = (domain: string, list: string[]): boolean =>\n list.some((needle) => domain.includes(needle))\n\nconst extractDomain = (url: string): string | undefined => {\n if (!url) return undefined\n try {\n return new URL(url).hostname.toLowerCase()\n } catch {\n return undefined\n }\n}\n\n/**\n * Classify a visit into a `$channel_type` bucket from its referrer + UTM\n * params. Rule order (first match wins): direct, paid, organic search,\n * social, email, referral, fallback direct.\n *\n * @param referrer `document.referrer`.\n * @param utm The `$utm_*` properties already parsed by `parseUtm`.\n * @param currentHostname The current page's hostname (`location.hostname`),\n * used to avoid classifying same-site referrers as `referral`.\n */\nexport function classifyChannel(\n referrer: string,\n utm: Record<string, string>,\n currentHostname: string,\n): string {\n const referrerDomain = extractDomain(referrer)\n const utmMedium = utm.$utm_medium\n const hasReferrer = Boolean(referrerDomain)\n const hasUtm = Object.keys(utm).length > 0\n\n if (!hasReferrer && !hasUtm) return 'direct'\n if (utmMedium && PAID_MEDIUM_RE.test(utmMedium)) return 'paid'\n if (referrerDomain && matchesDomain(referrerDomain, SEARCH_ENGINE_DOMAINS))\n return 'organic search'\n if (referrerDomain && matchesDomain(referrerDomain, SOCIAL_DOMAINS)) return 'social'\n if (utmMedium && utmMedium.toLowerCase() === 'email') return 'email'\n if (referrerDomain && referrerDomain !== currentHostname.toLowerCase()) return 'referral'\n return 'direct'\n}\n", "// @clickroom/sdk-js \u2014 lightweight user-agent parsing (device/OS/browser).\n// PURE, zero-dependency: this package bundles to a CDN script, so no imports.\n// Emits PostHog-style `$` property keys. Only returns keys it can determine.\n\n// ---- browser detection --------------------------------------------------\n// Order matters: Edg/OPR/SamsungBrowser must be checked before Chrome, and\n// Chrome before Safari (Chrome UAs also contain a \"Safari\" token).\n\ninterface BrowserMatch {\n name: string\n // Regex whose first capture group is the major version (when present).\n re: RegExp\n}\n\nconst BROWSERS: BrowserMatch[] = [\n { name: 'Edge', re: /Edg(?:e|A|iOS)?\\/(\\d+)/ },\n { name: 'Opera', re: /(?:OPR|Opera)\\/(\\d+)/ },\n { name: 'Samsung Internet', re: /SamsungBrowser\\/(\\d+)/ },\n { name: 'Firefox', re: /(?:Firefox|FxiOS)\\/(\\d+)/ },\n { name: 'Chrome', re: /(?:Chrome|CriOS)\\/(\\d+)/ },\n { name: 'Safari', re: /Version\\/(\\d+).*Safari/ },\n]\n\nconst detectBrowser = (ua: string): { name?: string; version?: string } => {\n for (const b of BROWSERS) {\n const m = ua.match(b.re)\n if (m) return { name: b.name, version: m[1] }\n }\n return {}\n}\n\n// ---- OS detection -------------------------------------------------------\n\nconst detectOS = (ua: string): string | undefined => {\n if (/Windows/.test(ua)) return 'Windows'\n // iOS must precede macOS (iPad/iPhone UAs may mention \"Mac OS X\").\n if (/iPhone|iPad|iPod/.test(ua)) return 'iOS'\n if (/Android/.test(ua)) return 'Android'\n if (/CrOS/.test(ua)) return 'Chrome OS'\n if (/Mac OS X|Macintosh/.test(ua)) return 'macOS'\n if (/Linux/.test(ua)) return 'Linux'\n return undefined\n}\n\n// ---- device type --------------------------------------------------------\n// iPad -> Tablet; a \"Mobile\" token -> Mobile; Android without \"Mobile\" -> Tablet.\n\nconst detectDeviceType = (ua: string): string => {\n if (/iPad/.test(ua)) return 'Tablet'\n if (/Android/.test(ua)) return /Mobile/.test(ua) ? 'Mobile' : 'Tablet'\n if (/iPhone|iPod/.test(ua)) return 'Mobile'\n if (/Mobile/.test(ua)) return 'Mobile'\n return 'Desktop'\n}\n\n/**\n * Parse a user-agent + language into PostHog-style device properties.\n * Returns only the keys it can determine (never undefined values).\n */\nexport function parseDevice(\n userAgent: string,\n language: string | undefined,\n): Record<string, string> {\n const props: Record<string, string> = {}\n\n const ua = userAgent ?? ''\n if (ua) {\n const browser = detectBrowser(ua)\n if (browser.name) props.$browser = browser.name\n if (browser.version) props.$browser_version = browser.version\n\n const os = detectOS(ua)\n if (os) props.$os = os\n\n props.$device_type = detectDeviceType(ua)\n }\n\n if (language) props.$lang = language\n\n return props\n}\n", "// @clickroom/sdk-js \u2014 canonical feature-flag evaluation + remote fetch cache (Phase 10.A).\n// Semantics are the SHARED SOURCE OF TRUTH (D-05, CANONICAL-FLAGS.md). Any deviation\n// breaks SDK<->server parity (AC-5/6/7). Keep byte-for-byte agreement with Rust + tRPC.\n\nimport { sha1 } from './sha1'\n\n// ---- filter data model (\u00A71) --------------------------------------------\n\nexport type FlagOperator = 'exact' | 'is_not' | 'icontains' | 'gt' | 'lt' | 'is_set'\n\nexport interface PropFilter {\n key: string\n operator: FlagOperator\n value: unknown\n}\n\nexport interface Group {\n properties: PropFilter[]\n rollout_percentage: number\n variant?: string | null\n}\n\nexport interface Variant {\n key: string\n rollout_percentage: number\n}\n\nexport interface Multivariate {\n variants: Variant[]\n}\n\nexport interface Filters {\n groups: Group[]\n multivariate?: Multivariate\n payloads?: Record<string, unknown>\n}\n\nexport interface Flag {\n key: string\n active: boolean\n filters: Filters\n}\n\nexport interface FlagResult {\n enabled: boolean\n variant: string | null\n payload: unknown\n}\n\n// ---- hash (\u00A72) ----------------------------------------------------------\n\n// 15 hex f's == 1152921504606846975. Built via BigInt(string) rather than a `\u2026n`\n// literal so the es2018 CDN bundle target accepts it (identical numeric value).\nconst HASH_DENOM = Number(BigInt('0xfffffffffffffff'))\n\n/** Deterministic bucket in [0,1): int(sha1(key+\".\"+distinctId+salt)[0:15]) / 0xFFFFFFFFFFFFFFF. */\nexport function hashFlag(key: string, distinctId: string, salt: string): number {\n const hex = sha1(`${key}.${distinctId}${salt}`)\n // BigInt (not parseInt) \u2014 15 hex chars is up to 60 bits, beyond 2^53 precision.\n const n = Number(BigInt(`0x${hex.slice(0, 15)}`))\n return n / HASH_DENOM\n}\n\n// ---- operators (\u00A74) -----------------------------------------------------\n\n/** actual = person_properties[key]; value = filter value. */\nexport function propMatch(op: FlagOperator, actual: unknown, value: unknown): boolean {\n switch (op) {\n case 'exact':\n return String(actual) === String(value)\n case 'is_not':\n return String(actual) !== String(value)\n case 'icontains':\n return actual != null && String(actual).toLowerCase().includes(String(value).toLowerCase())\n case 'gt':\n return actual != null && Number(actual) > Number(value)\n case 'lt':\n return actual != null && Number(actual) < Number(value)\n case 'is_set':\n return actual !== undefined && actual !== null\n default:\n return false\n }\n}\n\nconst groupMatches = (props: PropFilter[], pp: Record<string, unknown>): boolean => {\n for (const p of props) {\n if (!propMatch(p.operator, pp[p.key], p.value)) return false\n }\n return true\n}\n\nconst pickVariant = (flag: Flag, distinctId: string): string | null => {\n const variants = flag.filters.multivariate?.variants ?? []\n const f = hashFlag(flag.key, distinctId, 'variant')\n let cumulative = 0\n for (const v of variants) {\n cumulative += v.rollout_percentage / 100\n if (f < cumulative) return v.key // strict <\n }\n return variants.length > 0 ? variants[variants.length - 1].key : null\n}\n\n// ---- evaluate (\u00A73) ------------------------------------------------------\n\n/** First matching group wins; in-group-off stops (no fallthrough). */\nexport function evaluate(\n flag: Flag,\n distinctId: string,\n personProps: Record<string, unknown> = {},\n): FlagResult {\n if (!flag.active) return { enabled: false, variant: null, payload: null }\n\n const filters = flag.filters ?? { groups: [] }\n for (const group of filters.groups ?? []) {\n if (!groupMatches(group.properties ?? [], personProps)) continue\n\n if (hashFlag(flag.key, distinctId, '') <= group.rollout_percentage / 100) {\n const variant = filters.multivariate ? pickVariant(flag, distinctId) : (group.variant ?? null)\n const payload = variant != null ? (filters.payloads?.[variant] ?? null) : null\n return { enabled: true, variant, payload }\n }\n // In-group but rolled out off \u2014 stop, no fallthrough.\n return { enabled: false, variant: null, payload: null }\n }\n\n return { enabled: false, variant: null, payload: null }\n}\n\n// ---- remote fetch cache client -----------------------------------------\n\nconst FLAGS_STORAGE_KEY = 'ckrm_flags'\n\nexport type FlagMap = Record<string, FlagResult>\n\nexport interface FlagsResponse {\n flags: FlagMap\n}\n\nconst safeLocalStorage = (): Storage | null => {\n try {\n if (typeof localStorage === 'undefined') return null\n return localStorage\n } catch {\n return null\n }\n}\n\nexport interface FlagClientDeps {\n getApiHost: () => string\n getWriteKey: () => string\n getDistinctId: () => string\n getPersonProperties: () => Record<string, unknown>\n}\n\nexport interface FlagClient {\n /** In-memory cache of the last evaluated flags (or restored from localStorage). */\n getCache(): FlagMap\n isFeatureEnabled(key: string): boolean\n getFeatureFlag(key: string): boolean | string\n getFeatureFlagPayload(key: string): unknown\n /** Fetch + cache flags for the current distinct id/props. Never throws. */\n fetchFlags(): Promise<FlagMap>\n /**\n * Cheap version check (GET `{apiHost}/flags/version`) before paying for a\n * full `fetchFlags()`. Only calls `fetchFlags()` (and updates the stored\n * `lastVersion`) when the returned version differs from the last one seen\n * \u2014 or on the very first check. Network/parse failure on the version check\n * fails open (treated as \"changed\") and falls through to a full fetch,\n * matching this client's existing never-throw/best-effort tolerance.\n * Never throws.\n */\n checkVersionAndMaybeFetch(): Promise<{ changed: boolean; flags: FlagMap }>\n /** Drop the in-memory + persisted cache. */\n clear(): void\n}\n\nexport function createFlagClient(deps: FlagClientDeps): FlagClient {\n let cache: FlagMap = restoreCache()\n\n function restoreCache(): FlagMap {\n try {\n const raw = safeLocalStorage()?.getItem(FLAGS_STORAGE_KEY)\n if (!raw) return {}\n const parsed = JSON.parse(raw) as FlagMap\n if (parsed && typeof parsed === 'object') return parsed\n } catch {\n // ignore corrupt cache\n }\n return {}\n }\n\n const persist = (map: FlagMap): void => {\n try {\n safeLocalStorage()?.setItem(FLAGS_STORAGE_KEY, JSON.stringify(map))\n } catch {\n // ignore quota / disabled storage\n }\n }\n\n let lastVersion: number | undefined\n\n const buildUrl = (): string => {\n const base = deps.getApiHost().replace(/\\/+$/, '')\n return `${base}/v1/flags`\n }\n\n const buildVersionUrl = (): string => {\n const base = deps.getApiHost().replace(/\\/+$/, '')\n return `${base}/v1/flags/version`\n }\n\n const fetchFlags = async (): Promise<FlagMap> => {\n try {\n if (typeof fetch !== 'function') return cache\n const res = await fetch(buildUrl(), {\n method: 'POST',\n headers: {\n 'content-type': 'application/json',\n authorization: `Bearer ${deps.getWriteKey()}`,\n },\n body: JSON.stringify({\n distinct_id: deps.getDistinctId(),\n person_properties: deps.getPersonProperties(),\n }),\n })\n if (!res.ok) return cache // fall back to existing cache\n const data = (await res.json()) as FlagsResponse\n const flags = data?.flags\n if (flags && typeof flags === 'object') {\n cache = flags\n persist(cache)\n }\n return cache\n } catch {\n // Network / parse failure \u2014 keep whatever cache we have (incl. localStorage).\n return cache\n }\n }\n\n const checkVersionAndMaybeFetch = async (): Promise<{ changed: boolean; flags: FlagMap }> => {\n let changed = true\n let newVersion: number | undefined\n\n try {\n if (typeof fetch === 'function') {\n const res = await fetch(buildVersionUrl(), {\n method: 'GET',\n headers: {\n authorization: `Bearer ${deps.getWriteKey()}`,\n },\n })\n if (res.ok) {\n const data = (await res.json()) as { version?: number }\n newVersion = data?.version\n changed = lastVersion === undefined || newVersion !== lastVersion\n }\n // !res.ok \u2014 fail open, `changed` stays true (default).\n }\n } catch {\n // Network / parse failure \u2014 fail open, `changed` stays true.\n }\n\n if (!changed) return { changed: false, flags: cache }\n\n const flags = await fetchFlags()\n if (newVersion !== undefined) lastVersion = newVersion\n return { changed: true, flags }\n }\n\n return {\n getCache: () => cache,\n isFeatureEnabled: (key) => cache[key]?.enabled ?? false,\n getFeatureFlag: (key) => {\n const r = cache[key]\n if (!r?.enabled) return false\n return r.variant != null ? r.variant : true\n },\n getFeatureFlagPayload: (key) => cache[key]?.payload ?? null,\n fetchFlags,\n checkVersionAndMaybeFetch,\n clear: () => {\n cache = {}\n try {\n safeLocalStorage()?.removeItem(FLAGS_STORAGE_KEY)\n } catch {\n // ignore\n }\n },\n }\n}\n", "// @clickroom/sdk-js \u2014 session-id generation + idle rotation (phase 19.C).\n//\n// A session groups a user's events into a contiguous visit. The id is persisted\n// in sessionStorage (per-tab) and rotated when the tab goes idle past a timeout\n// or a session outlives the hard 24h cap. Every captured event carries the\n// current id as `$session_id`; ClickHouse materializes `session_id` from it so\n// the event-detail page can render a true session timeline + session count.\n\n/** Idle window: no captured activity for this long starts a fresh session. */\nconst IDLE_MS = 30 * 60 * 1000\n/** Hard cap: a single session never lives longer than this, even if active. */\nconst MAX_MS = 24 * 60 * 60 * 1000\n\nconst SID_KEY = 'ckrm_sid'\n\ninterface StoredSession {\n /** Session id. */\n id: string\n /** Epoch ms the session started (for the MAX_MS cap). */\n start: number\n /** Epoch ms of the last captured activity (for the IDLE_MS rotation). */\n last: number\n}\n\nexport interface SessionManager {\n /** Current session id, rotating first if idle/expired. Bumps last-activity. */\n getSessionId(): string\n /** Forget the current session \u2014 the next getSessionId() starts a fresh one. */\n reset(): void\n}\n\nexport interface SessionDeps {\n /** Epoch ms clock (injectable for tests). */\n now: () => number\n /** sessionStorage accessor (SSR-safe; returns null when unavailable). */\n storage: () => Storage | null\n /** New session id generator (shares the SDK's uuid helper). */\n genId: () => string\n}\n\nconst parse = (raw: string | null): StoredSession | null => {\n if (!raw) return null\n try {\n const v = JSON.parse(raw)\n if (\n v &&\n typeof v === 'object' &&\n typeof v.id === 'string' &&\n typeof v.start === 'number' &&\n typeof v.last === 'number'\n ) {\n return v as StoredSession\n }\n } catch {\n // fall through \u2014 treat as no session\n }\n return null\n}\n\nexport function createSessionManager({ now, storage, genId }: SessionDeps): SessionManager {\n const load = (): StoredSession | null => {\n try {\n return parse(storage()?.getItem(SID_KEY) ?? null)\n } catch {\n return null\n }\n }\n\n const save = (s: StoredSession): void => {\n try {\n storage()?.setItem(SID_KEY, JSON.stringify(s))\n } catch {\n // ignore \u2014 best-effort persistence\n }\n }\n\n const start = (t: number): StoredSession => {\n const s: StoredSession = { id: genId(), start: t, last: t }\n save(s)\n return s\n }\n\n return {\n getSessionId(): string {\n const t = now()\n const existing = load()\n const expired = !existing || t - existing.last > IDLE_MS || t - existing.start > MAX_MS\n if (expired) {\n return start(t).id\n }\n existing.last = t\n save(existing)\n return existing.id\n },\n reset(): void {\n try {\n storage()?.removeItem(SID_KEY)\n } catch {\n // ignore\n }\n },\n }\n}\n", "// @clickroom/sdk-js \u2014 batching transport with beacon + fetch fallback and bounded retry.\n\nimport type { ClickroomConfig, EventPayload } from './types'\n\nconst FLUSH_SIZE = 20\nconst BATCH_MAX = 100\nconst FLUSH_INTERVAL_MS = 3000\n\nconst RETRY_BASE_MS = 1000\nconst RETRY_CAP_MS = 30_000\nconst RETRY_MAX_EVENTS = 1000\n\nexport interface Transport {\n enqueue(event: EventPayload): void\n flush(useBeacon?: boolean): void\n teardown(): void\n}\n\nconst isBrowser = (): boolean => typeof window !== 'undefined'\n\nexport function createTransport(\n getConfig: () => ClickroomConfig,\n getWriteKey: () => string,\n): Transport {\n let buffer: EventPayload[] = []\n let retryBuffer: EventPayload[] = []\n let retryAttempt = 0\n let retryTimer: ReturnType<typeof setTimeout> | null = null\n let interval: ReturnType<typeof setInterval> | null = null\n\n const buildUrl = (): string => {\n const { api_host } = getConfig()\n const base = api_host.replace(/\\/+$/, '')\n return `${base}/v1/capture?token=${encodeURIComponent(getWriteKey())}`\n }\n\n const buildBody = (batch: EventPayload[]): string => JSON.stringify({ batch })\n\n const scheduleRetry = (): void => {\n if (retryTimer !== null || retryBuffer.length === 0) return\n const delay = Math.min(RETRY_BASE_MS * 2 ** retryAttempt, RETRY_CAP_MS)\n try {\n retryTimer = setTimeout(() => {\n retryTimer = null\n flush(false)\n }, delay)\n } catch {\n retryTimer = null\n }\n }\n\n const requeue = (batch: EventPayload[]): void => {\n // Re-queue oldest-first, dropping oldest when exceeding the cap.\n retryBuffer = retryBuffer.concat(batch)\n if (retryBuffer.length > RETRY_MAX_EVENTS) {\n retryBuffer = retryBuffer.slice(retryBuffer.length - RETRY_MAX_EVENTS)\n }\n retryAttempt = Math.min(retryAttempt + 1, 5)\n scheduleRetry()\n }\n\n const sendBeacon = (url: string, body: string): boolean => {\n try {\n if (typeof navigator === 'undefined' || typeof navigator.sendBeacon !== 'function') {\n return false\n }\n // `text/plain`, not `application/json`: sendBeacon() is *always*\n // credentialed (no opt-out) and a non-safelisted content-type makes it\n // a real cross-origin request subject to CORS credential checks \u2014\n // servers that permissively echo `Access-Control-Allow-Origin: *` (or\n // any wildcard) get hard-rejected by the browser for a credentialed\n // request. `text/plain` keeps this a CORS-simple request, matching\n // posthog-js's same workaround. capture-rs parses the body as raw\n // bytes regardless of declared content-type, so this is a no-op\n // server-side.\n const blob = new Blob([body], { type: 'text/plain' })\n return navigator.sendBeacon(url, blob)\n } catch {\n return false\n }\n }\n\n // 4xx responses mean the server will never accept this batch as-is (e.g.\n // schema-invalid events) \u2014 retrying would just resend a request that's\n // permanently rejected. Surface it (matching the existing console.warn\n // idiom used elsewhere in this SDK \u2014 see index.ts's capture-before-init\n // warning) and drop the batch instead of requeuing it.\n const reportRejection = async (res: Response, batch: EventPayload[]): Promise<void> => {\n let detail: unknown\n try {\n detail = await res.json()\n } catch {\n // Non-JSON or empty body \u2014 surface status only.\n }\n console.warn(\n `[clickroom] capture() batch rejected by server (HTTP ${res.status}); dropping ${batch.length} event(s)`,\n detail,\n )\n }\n\n const sendFetch = (url: string, body: string, batch: EventPayload[]): void => {\n try {\n if (typeof fetch !== 'function') {\n requeue(batch)\n return\n }\n fetch(url, {\n method: 'POST',\n keepalive: true,\n headers: { 'content-type': 'application/json' },\n body,\n })\n .then((res) => {\n if (res.ok) {\n // Delivered \u2014 reset backoff.\n retryAttempt = 0\n if (retryBuffer.length > 0) scheduleRetry()\n return\n }\n if (res.status >= 500) {\n // Server-side/transient failure \u2014 retry with existing backoff.\n requeue(batch)\n return\n }\n // 4xx \u2014 permanent failure, do not retry.\n void reportRejection(res, batch)\n })\n .catch(() => {\n // Network error \u2014 retry with existing backoff.\n requeue(batch)\n })\n } catch {\n requeue(batch)\n }\n }\n\n function flush(useBeacon = false): void {\n if (!isBrowser()) {\n buffer = []\n return\n }\n // Drain pending + retry buffers into batches.\n const pending = retryBuffer.concat(buffer)\n buffer = []\n retryBuffer = []\n if (pending.length === 0) return\n\n try {\n const url = buildUrl()\n for (let i = 0; i < pending.length; i += BATCH_MAX) {\n const batch = pending.slice(i, i + BATCH_MAX)\n const body = buildBody(batch)\n if (useBeacon && sendBeacon(url, body)) continue\n if (useBeacon) {\n // Beacon unavailable during unload \u2014 best-effort fetch fallback.\n sendFetch(url, body, batch)\n continue\n }\n sendFetch(url, body, batch)\n }\n } catch {\n // Never throw from flush.\n }\n }\n\n const onVisibilityChange = (): void => {\n try {\n if (typeof document !== 'undefined' && document.hidden) flush(true)\n } catch {\n // ignore\n }\n }\n\n const onPageHide = (): void => {\n flush(true)\n }\n\n const enqueue = (event: EventPayload): void => {\n if (!isBrowser()) return\n try {\n buffer.push(event)\n if (buffer.length >= FLUSH_SIZE) flush(false)\n } catch {\n // ignore\n }\n }\n\n const teardown = (): void => {\n try {\n if (interval !== null) {\n clearInterval(interval)\n interval = null\n }\n if (retryTimer !== null) {\n clearTimeout(retryTimer)\n retryTimer = null\n }\n if (typeof document !== 'undefined') {\n document.removeEventListener('visibilitychange', onVisibilityChange)\n }\n if (typeof window !== 'undefined') {\n window.removeEventListener('pagehide', onPageHide)\n window.removeEventListener('beforeunload', onPageHide)\n }\n } catch {\n // ignore\n }\n }\n\n // Wire up timers + lifecycle listeners.\n if (isBrowser()) {\n try {\n interval = setInterval(() => flush(false), FLUSH_INTERVAL_MS)\n if (typeof document !== 'undefined') {\n document.addEventListener('visibilitychange', onVisibilityChange)\n }\n window.addEventListener('pagehide', onPageHide)\n window.addEventListener('beforeunload', onPageHide)\n } catch {\n // ignore\n }\n }\n\n return { enqueue, flush, teardown }\n}\n", "// @clickroom/sdk-js \u2014 UTM campaign parameter parsing.\n// PURE, zero-dependency: this package bundles to a CDN script, so no imports.\n// Parses `utm_*` query params off a URL's search string into PostHog-style\n// `$utm_*` properties. Only emits keys actually present in the query string \u2014\n// never an empty string or a key for an absent param.\n\nconst UTM_KEYS = ['source', 'medium', 'campaign', 'term', 'content'] as const\n\n/**\n * Parse `utm_source`/`utm_medium`/`utm_campaign`/`utm_term`/`utm_content` off\n * a `location.search`-style string into `$utm_*` properties.\n */\nexport function parseUtm(search: string): Record<string, string> {\n const props: Record<string, string> = {}\n if (!search) return props\n\n try {\n const params = new URLSearchParams(search)\n for (const key of UTM_KEYS) {\n const value = params.get(`utm_${key}`)\n if (value) props[`$utm_${key}`] = value\n }\n } catch {\n // Malformed query string \u2014 return whatever was parsed before the throw.\n }\n\n return props\n}\n", "// @clickroom/sdk-js \u2014 clickroom-js browser SDK core (autocapture + flags). Phase 06.A.\n\nimport { initAutocapture } from './autocapture'\nimport { classifyChannel } from './channel'\nimport { parseDevice } from './device'\nimport { createFlagClient, type FlagClient, type FlagMap } from './flags'\nimport { createSessionManager, type SessionManager } from './session'\nimport { createTransport, type Transport } from './transport'\nimport type { AutocaptureContext, ClickroomConfig, ClickroomOptions, EventPayload } from './types'\nimport { parseUtm } from './utm'\n\nexport const PACKAGE_NAME = '@clickroom/sdk-js'\n\nconst LIB_NAME = 'clickroom-js'\nconst LIB_VERSION = '0.1.0'\n\nconst DID_KEY = 'ckrm_did'\nconst STORAGE_PREFIX = 'ckrm_'\n\nconst DEFAULT_CONFIG: ClickroomConfig = {\n api_host: 'https://sdk.clickroom.co',\n autocapture: true,\n capture_pageview: true,\n}\n\nconst isBrowser = (): boolean => typeof window !== 'undefined'\n\n// Floor for `feature_flag_poll_interval` \u2014 keeps a misconfigured caller from\n// hammering the flags endpoint (mirrors the intent of sdk-node's mandatory\n// `pollInterval`, just defensive instead of required).\nconst MIN_FLAG_POLL_INTERVAL_MS = 5_000\n\ntype FlagListener = (flags: Record<string, boolean | string>) => void\n\ninterface State {\n initialized: boolean\n warnedNotInit: boolean\n writeKey: string\n config: ClickroomConfig\n distinctId: string\n personProperties: Record<string, unknown>\n transport: Transport | null\n session: SessionManager | null\n // Referrer/channel/UTM context, computed once per session (on the first\n // captured event of that session id) and reused for every subsequent event\n // \u2014 mirrors how `$session_id` itself is scoped per session, not per event.\n sessionContext: { sessionId: string; props: Record<string, unknown> } | null\n teardownAutocapture: (() => void) | null\n flagListeners: FlagListener[]\n flagClient: FlagClient | null\n flagsLoaded: boolean\n emittedFlagCalls: Set<string>\n flagPollId: ReturnType<typeof setInterval> | null\n}\n\nconst state: State = {\n initialized: false,\n warnedNotInit: false,\n writeKey: '',\n config: { ...DEFAULT_CONFIG },\n distinctId: '',\n personProperties: {},\n transport: null,\n session: null,\n sessionContext: null,\n teardownAutocapture: null,\n flagListeners: [],\n flagClient: null,\n flagsLoaded: false,\n emittedFlagCalls: new Set(),\n flagPollId: null,\n}\n\n// ---- storage helpers (SSR-safe) ----------------------------------------\n\nconst safeLocalStorage = (): Storage | null => {\n try {\n if (typeof localStorage === 'undefined') return null\n return localStorage\n } catch {\n return null\n }\n}\n\nconst safeSessionStorage = (): Storage | null => {\n try {\n if (typeof sessionStorage === 'undefined') return null\n return sessionStorage\n } catch {\n return null\n }\n}\n\nconst storageGet = (key: string): string | null => {\n try {\n return safeLocalStorage()?.getItem(key) ?? null\n } catch {\n return null\n }\n}\n\nconst storageSet = (key: string, value: string): void => {\n try {\n safeLocalStorage()?.setItem(key, value)\n } catch {\n // ignore\n }\n}\n\nconst storageRemove = (key: string): void => {\n try {\n safeLocalStorage()?.removeItem(key)\n } catch {\n // ignore\n }\n}\n\nconst generateId = (): string => {\n try {\n if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {\n return crypto.randomUUID()\n }\n } catch {\n // fall through\n }\n // Fallback: not RFC-compliant but unique enough for an anonymous id.\n return `ckrm_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 12)}`\n}\n\nconst loadOrCreateDistinctId = (): string => {\n const existing = storageGet(DID_KEY)\n if (existing) return existing\n const fresh = generateId()\n storageSet(DID_KEY, fresh)\n return fresh\n}\n\n// ---- property enrichment ------------------------------------------------\n\nconst baseProperties = (): Record<string, unknown> => {\n const props: Record<string, unknown> = {\n $lib: LIB_NAME,\n $lib_version: LIB_VERSION,\n }\n try {\n if (typeof location !== 'undefined') {\n props.$current_url = location.href\n props.$host = location.host\n props.$pathname = location.pathname\n }\n if (typeof document !== 'undefined') {\n props.$referrer = document.referrer\n }\n if (typeof window !== 'undefined' && window.screen) {\n props.$screen_height = window.screen.height\n props.$screen_width = window.screen.width\n }\n if (typeof window !== 'undefined') {\n props.$viewport_height = window.innerHeight\n props.$viewport_width = window.innerWidth\n }\n if (typeof navigator !== 'undefined') {\n Object.assign(props, parseDevice(navigator.userAgent, navigator.language))\n }\n } catch {\n // ignore \u2014 best-effort enrichment\n }\n return props\n}\n\n// Parses `document.referrer` + `location.search`'s utm_* params into\n// `$utm_*`/`$channel_type` properties. Computed once per session (see\n// `getSessionContextProps` below) rather than on every event.\nconst buildSessionContextProps = (): Record<string, unknown> => {\n try {\n const search = typeof location !== 'undefined' ? location.search : ''\n const referrer = typeof document !== 'undefined' ? document.referrer : ''\n const hostname = typeof location !== 'undefined' ? location.hostname : ''\n const utm = parseUtm(search)\n return { ...utm, $channel_type: classifyChannel(referrer, utm, hostname) }\n } catch {\n return {}\n }\n}\n\n// Returns the current session's cached channel/UTM props, recomputing only\n// when the session id has rotated (new session -> re-parse the current URL).\nconst getSessionContextProps = (sessionId: string | undefined): Record<string, unknown> => {\n if (!sessionId) return buildSessionContextProps()\n if (!state.sessionContext || state.sessionContext.sessionId !== sessionId) {\n state.sessionContext = { sessionId, props: buildSessionContextProps() }\n }\n return state.sessionContext.props\n}\n\n// ---- core capture -------------------------------------------------------\n\nconst warnNotInit = (): void => {\n if (state.warnedNotInit) return\n state.warnedNotInit = true\n try {\n console.warn('[clickroom] capture() called before init(); event dropped')\n } catch {\n // ignore\n }\n}\n\n// Internal capture \u2014 accepts an optional elements_chain (used by autocapture).\nconst captureInternal = (\n event: string,\n properties?: Record<string, unknown>,\n elementsChain?: string,\n): void => {\n if (!state.initialized || !state.transport) {\n warnNotInit()\n return\n }\n try {\n const sessionId = state.session?.getSessionId()\n const sessionProps = sessionId ? { $session_id: sessionId } : {}\n const payload: EventPayload = {\n event,\n distinct_id: state.distinctId,\n timestamp: new Date().toISOString(),\n properties: {\n ...baseProperties(),\n ...sessionProps,\n ...getSessionContextProps(sessionId),\n ...(properties ?? {}),\n },\n }\n if (elementsChain) payload.elements_chain = elementsChain\n state.transport.enqueue(payload)\n } catch {\n // never throw\n }\n}\n\n// ---- feature flags ------------------------------------------------------\n\n// Flatten the cached flag results into the listener-facing map: `{ key: variant|enabled }`.\nconst flagFlatMap = (cache: FlagMap): Record<string, boolean | string> => {\n const flat: Record<string, boolean | string> = {}\n for (const [key, r] of Object.entries(cache)) {\n if (!r.enabled) continue\n flat[key] = r.variant != null ? r.variant : true\n }\n return flat\n}\n\nconst fireFlagListeners = (): void => {\n if (!state.flagClient) return\n const flat = flagFlatMap(state.flagClient.getCache())\n for (const cb of state.flagListeners) {\n try {\n cb(flat)\n } catch {\n // a listener must not break the others\n }\n }\n}\n\n// Version-gated flag refresh: cheap version check first, only paying for a\n// full fetch (+ exposure) when the version actually changed. Always notifies\n// listeners afterward \u2014 matches the pre-existing \"always notify after a\n// refresh call finishes\" contract, incl. fresh subscribers via onFeatureFlags.\n// Never throws.\nconst refreshFlags = (): void => {\n if (!state.flagClient) return\n state.flagClient\n .checkVersionAndMaybeFetch()\n .then(() => {\n state.flagsLoaded = true\n fireFlagListeners()\n })\n .catch(() => {\n // checkVersionAndMaybeFetch already swallows errors; belt-and-suspenders.\n })\n}\n\n// Emit a `$feature_flag_called` exposure event at most once per (key, response)\n// per identity. Never throws \u2014 feature-flag reads must stay SSR-safe.\nconst maybeEmitFlagCall = (key: string, response: boolean | string): void => {\n try {\n // No real value yet \u2014 don't emit a misleading `false` before flags load.\n if (!state.flagsLoaded) return\n const resp = String(response)\n const dedup = `${key}:${resp}`\n if (state.emittedFlagCalls.has(dedup)) return\n state.emittedFlagCalls.add(dedup)\n captureInternal('$feature_flag_called', {\n $feature_flag: key,\n $feature_flag_response: resp,\n })\n } catch {\n // never throw from a flag read\n }\n}\n\n// ---- public API ---------------------------------------------------------\n\nfunction init(writeKey: string, opts?: ClickroomOptions): void {\n if (state.initialized) return\n if (!isBrowser()) {\n // SSR: no-op init. Public methods remain safe no-ops.\n return\n }\n\n state.writeKey = writeKey\n state.config = {\n api_host: opts?.api_host ?? DEFAULT_CONFIG.api_host,\n autocapture: opts?.autocapture ?? DEFAULT_CONFIG.autocapture,\n capture_pageview: opts?.capture_pageview ?? DEFAULT_CONFIG.capture_pageview,\n }\n\n state.distinctId = loadOrCreateDistinctId()\n state.session = createSessionManager({\n now: () => Date.now(),\n storage: safeSessionStorage,\n genId: generateId,\n })\n state.transport = createTransport(\n () => state.config,\n () => state.writeKey,\n )\n state.flagClient = createFlagClient({\n getApiHost: () => state.config.api_host,\n getWriteKey: () => state.writeKey,\n getDistinctId: () => state.distinctId,\n getPersonProperties: () => state.personProperties,\n })\n state.initialized = true\n\n // Non-blocking: load flags for this distinct id, then notify listeners.\n refreshFlags()\n\n // Opt-in poll: re-fetch on an interval so long-lived pages (dashboards,\n // SPAs that rarely re-identify) see flag changes without a page reload.\n // Off by default \u2014 identical to pre-poll behavior when unset.\n if (opts?.feature_flag_poll_interval) {\n const intervalMs = Math.max(opts.feature_flag_poll_interval, MIN_FLAG_POLL_INTERVAL_MS)\n state.flagPollId = setInterval(() => refreshFlags(), intervalMs)\n }\n\n if (state.config.autocapture) {\n const ctx: AutocaptureContext = {\n config: state.config,\n capture: captureInternal,\n }\n try {\n state.teardownAutocapture = initAutocapture(ctx)\n } catch {\n // autocapture is best-effort; never break init\n }\n } else if (state.config.capture_pageview) {\n // Autocapture owns $pageview when enabled (incl. the first).\n // Only emit the initial pageview here when autocapture is off.\n captureInternal('$pageview')\n }\n}\n\nfunction capture(event: string, properties?: Record<string, unknown>): void {\n captureInternal(event, properties)\n}\n\nfunction identify(distinctId: string, personProps?: Record<string, unknown>): void {\n if (!state.initialized) {\n warnNotInit()\n return\n }\n const previousId = state.distinctId\n state.distinctId = distinctId\n storageSet(DID_KEY, distinctId)\n if (personProps) state.personProperties = { ...state.personProperties, ...personProps }\n captureInternal('$identify', {\n $set: personProps ?? {},\n ...(previousId !== distinctId ? { $anon_distinct_id: previousId } : {}),\n })\n // New identity re-exposes flags \u2014 drop the dedup set.\n state.emittedFlagCalls.clear()\n // Distinct id / person props changed \u2014 re-evaluate flags and re-notify.\n refreshFlags()\n}\n\nfunction reset(): void {\n try {\n // Clear all cached clickroom keys (flags, did, etc.).\n const ls = safeLocalStorage()\n if (ls) {\n const toRemove: string[] = []\n for (let i = 0; i < ls.length; i += 1) {\n const key = ls.key(i)\n if (key?.startsWith(STORAGE_PREFIX)) toRemove.push(key)\n }\n for (const key of toRemove) storageRemove(key)\n }\n } catch {\n // ignore\n }\n state.distinctId = generateId()\n storageSet(DID_KEY, state.distinctId)\n // New identity starts a fresh session.\n state.session?.reset()\n state.sessionContext = null\n // Drop cached flags + person props for the previous identity.\n state.personProperties = {}\n state.flagsLoaded = false\n state.emittedFlagCalls.clear()\n state.flagClient?.clear()\n}\n\n// ---- feature flags (public getters) -------------------------------------\n\nfunction isFeatureEnabled(key: string): boolean {\n const result = state.flagClient?.isFeatureEnabled(key) ?? false\n maybeEmitFlagCall(key, result)\n return result\n}\n\nfunction getFeatureFlag(key: string): boolean | string {\n const result = state.flagClient?.getFeatureFlag(key) ?? false\n maybeEmitFlagCall(key, result)\n return result\n}\n\nfunction getFeatureFlagPayload(key: string): unknown {\n return state.flagClient?.getFeatureFlagPayload(key) ?? null\n}\n\nfunction onFeatureFlags(cb: (flags: Record<string, boolean | string>) => void): void {\n state.flagListeners.push(cb)\n // If flags are already loaded, fire immediately with the current map.\n if (state.flagsLoaded && state.flagClient) {\n try {\n cb(flagFlatMap(state.flagClient.getCache()))\n } catch {\n // ignore listener errors\n }\n }\n}\n\n/** Manually re-fetch flag definitions and notify `onFeatureFlags` listeners.\n * Same trigger `identify()` already uses internally \u2014 exposed directly so a\n * consumer can refresh on its own signal (a manual \"reload flags\" button, a\n * websocket/SSE event from elsewhere, etc.) without faking an identify call. */\nfunction refreshFeatureFlags(): void {\n refreshFlags()\n}\n\n/** Stop the `feature_flag_poll_interval` loop started in `init()`, if any. */\nfunction stopFeatureFlagPolling(): void {\n if (state.flagPollId !== null) {\n clearInterval(state.flagPollId)\n state.flagPollId = null\n }\n}\n\n// ---- singleton + named exports ------------------------------------------\n\nconst clickroom = {\n init,\n capture,\n identify,\n reset,\n isFeatureEnabled,\n getFeatureFlag,\n getFeatureFlagPayload,\n onFeatureFlags,\n refreshFeatureFlags,\n stopFeatureFlagPolling,\n}\n\nexport type { AutocaptureContext, ClickroomConfig, ClickroomOptions, EventPayload }\nexport {\n capture,\n getFeatureFlag,\n getFeatureFlagPayload,\n identify,\n init,\n isFeatureEnabled,\n onFeatureFlags,\n refreshFeatureFlags,\n reset,\n stopFeatureFlagPolling,\n}\nexport default clickroom\n", "// @clickroom/sdk-js \u2014 CDN IIFE entry. Attaches the singleton to window.clickroom.\nimport clickroom from './index'\n\ndeclare global {\n interface Window {\n clickroom: typeof clickroom\n }\n}\n\nif (typeof window !== 'undefined') window.clickroom = clickroom\n\nexport default clickroom\n"],
5
- "mappings": "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",
6
- "names": ["INTERESTING_TAGS", "OPT_OUT_CLASS", "OPT_OUT_ATTR", "escapeValue", "value", "nthChild", "el", "n", "sib", "nthOfType", "tag", "elementText", "_a", "_b", "trimmed", "serializeElement", "pairs", "id", "className", "href", "text", "attrs", "i", "attr", "serializeElementsChain", "target", "parts", "depth", "isOptedOut", "node", "isInteresting", "getEventTarget", "locationProps", "initAutocapture", "ctx", "handleInteraction", "event", "raw", "target", "getEventTarget", "chain", "serializeElementsChain", "e", "interactionEvents", "type", "lastUrl", "emitPageview", "origPushState", "origReplaceState", "patched", "args", "PAID_MEDIUM_RE", "SEARCH_ENGINE_DOMAINS", "SOCIAL_DOMAINS", "matchesDomain", "domain", "list", "needle", "extractDomain", "url", "e", "classifyChannel", "referrer", "utm", "currentHostname", "referrerDomain", "utmMedium", "hasReferrer", "hasUtm", "BROWSERS", "detectBrowser", "ua", "b", "m", "detectOS", "detectDeviceType", "parseDevice", "userAgent", "language", "props", "browser", "os", "HASH_DENOM", "FLAGS_STORAGE_KEY", "safeLocalStorage", "createFlagClient", "deps", "cache", "restoreCache", "_a", "raw", "parsed", "e", "persist", "map", "lastVersion", "buildUrl", "buildVersionUrl", "fetchFlags", "res", "data", "flags", "key", "_b", "r", "changed", "newVersion", "SID_KEY", "parse", "raw", "v", "e", "createSessionManager", "now", "storage", "genId", "load", "_a", "_b", "save", "start", "t", "s", "existing", "isBrowser", "createTransport", "getConfig", "getWriteKey", "buffer", "retryBuffer", "retryAttempt", "retryTimer", "interval", "buildUrl", "api_host", "buildBody", "batch", "scheduleRetry", "delay", "flush", "e", "requeue", "sendBeacon", "url", "body", "blob", "reportRejection", "res", "detail", "sendFetch", "useBeacon", "pending", "i", "onVisibilityChange", "onPageHide", "enqueue", "event", "teardown", "UTM_KEYS", "parseUtm", "search", "props", "params", "key", "value", "e", "LIB_NAME", "LIB_VERSION", "DID_KEY", "STORAGE_PREFIX", "DEFAULT_CONFIG", "isBrowser", "MIN_FLAG_POLL_INTERVAL_MS", "state", "safeLocalStorage", "safeSessionStorage", "storageGet", "key", "_a", "_b", "e", "storageSet", "value", "storageRemove", "generateId", "loadOrCreateDistinctId", "existing", "fresh", "baseProperties", "props", "parseDevice", "buildSessionContextProps", "search", "referrer", "hostname", "utm", "parseUtm", "classifyChannel", "getSessionContextProps", "sessionId", "warnNotInit", "captureInternal", "event", "properties", "elementsChain", "sessionProps", "payload", "flagFlatMap", "cache", "flat", "fireFlagListeners", "cb", "refreshFlags", "maybeEmitFlagCall", "response", "resp", "dedup", "init", "writeKey", "opts", "_c", "createSessionManager", "createTransport", "createFlagClient", "intervalMs", "ctx", "initAutocapture", "capture", "identify", "distinctId", "personProps", "previousId", "reset", "ls", "toRemove", "isFeatureEnabled", "result", "getFeatureFlag", "getFeatureFlagPayload", "onFeatureFlags", "refreshFeatureFlags", "stopFeatureFlagPolling", "clickroom", "index_default", "clickroom", "index_default", "global_default"]
7
- }