@splitlab/js-client 0.6.0 → 0.8.0

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/index.cjs CHANGED
@@ -1 +1 @@
1
- "use strict";var f=Object.defineProperty;var P=Object.getOwnPropertyDescriptor;var x=Object.getOwnPropertyNames;var $=Object.prototype.hasOwnProperty;var D=(n,e)=>{for(var t in e)f(n,t,{get:e[t],enumerable:!0})},L=(n,e,t,i)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of x(e))!$.call(n,s)&&s!==t&&f(n,s,{get:()=>e[s],enumerable:!(i=P(e,s))||i.enumerable});return n};var O=n=>L(f({},"__esModule",{value:!0}),n);var B={};D(B,{SplitLabClient:()=>b,hashToFloat:()=>g.hashToFloat,murmurhash3:()=>g.murmurhash3});module.exports=O(B);var g=require("@splitlab/core"),m=require("@splitlab/core");var w=require("@splitlab/core");var o=typeof globalThis<"u"&&typeof globalThis.document<"u";function I(){if(!o)return null;let n=globalThis,e=n.navigator,t=n.location,i=n.document,s=n.screen,r=(0,w.parseUserAgent)(e?.userAgent);return{pathname:t?.pathname??null,hostname:t?.hostname??null,referrer:i?.referrer||null,search_params:t?.search||null,page_title:i?.title||null,hash:t?.hash||null,user_agent:e?.userAgent??null,browser:r.browser,browser_version:r.browser_version,os:r.os,os_version:r.os_version,device_type:r.device_type,screen_width:s?.width??null,screen_height:s?.height??null,viewport_width:n.innerWidth??null,viewport_height:n.innerHeight??null,language:e?.language??null,timezone:z()}}function z(){try{return Intl.DateTimeFormat().resolvedOptions().timeZone}catch{return null}}var j=["utm_source","utm_medium","utm_campaign","utm_term","utm_content"],y="__ot_utm";function _(){if(!o)return{};try{let i=globalThis.sessionStorage?.getItem(y);if(i)return JSON.parse(i)}catch{}let n=new URLSearchParams(globalThis.location?.search||""),e={},t=!1;for(let i of j){let s=n.get(i);s&&(e[i]=s,t=!0)}if(t)try{globalThis.sessionStorage?.setItem(y,JSON.stringify(e))}catch{}return e}var c="_sl_did",d="_sl_sid",E=63072e3,C="__ot_did";function v(n){if(!o)return null;try{let e=globalThis.document?.cookie?.match(new RegExp("(?:^|; )"+n.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")+"=([^;]*)"));return e?decodeURIComponent(e[1]):null}catch{return null}}function u(n,e,t){if(o)try{globalThis.document.cookie=`${n}=${encodeURIComponent(e)}; path=/; max-age=${t}; SameSite=Lax`}catch{}}function T(n){if(!o)return h();let e=Math.floor(n/1e3),t=v(d);if(t)return u(d,t,e),t;let i=h();return u(d,i,e),i}function R(){if(!o)return h();let n=v(c);if(n)return n;let e=null;try{let t=globalThis.localStorage;e=t?.getItem(C)||null,e&&t?.removeItem(C)}catch{}return e||(e=h()),u(c,e,E),e}function U(n){!o||v(c)===n||u(c,n,E)}var S="__ot_ref";function k(){if(!o)return null;try{let n=globalThis.sessionStorage,e=n?.getItem(S);if(e!==null)return e||null;let t=globalThis.document?.referrer||"";return n?.setItem(S,t),t||null}catch{return globalThis.document?.referrer||null}}function h(){let n=Date.now().toString(36),e=Math.random().toString(36).substring(2,10);return`${n}-${e}`}var p=typeof globalThis<"u"&&typeof globalThis.document<"u",b=class{constructor(e){this.evalResult=null;this.serverConfig=null;this.eventQueue=[];this.flushTimer=null;this.initialized=!1;this.visibilityHandler=null;this.beforeUnloadHandler=null;this.lastEtag=null;this.eventSource=null;this.deviceId=null;this.utmParams={};this.initialReferrer=null;this.pageviewCleanup=null;this.trackedExposures=new Set;this.config={apiKey:e.apiKey,baseUrl:e.baseUrl.replace(/\/$/,""),ingestUrl:(e.ingestUrl||e.baseUrl).replace(/\/$/,""),distinctId:e.distinctId??null,attributes:e.attributes||{},autoFlushInterval:e.autoFlushInterval??8e3,autoFlushSize:e.autoFlushSize??20,enableLocalEvaluation:e.enableLocalEvaluation??!0,realtimeUpdates:e.realtimeUpdates??!1,onConfigUpdate:e.onConfigUpdate??null,captureContext:e.captureContext??p,captureUtm:e.captureUtm??!0,trackSessions:e.trackSessions??!0,sessionTimeout:e.sessionTimeout??30*6e4,persistDeviceId:e.persistDeviceId??!0,trackPageviews:e.trackPageviews??!1,superProperties:e.superProperties??{},environment:e.environment??"production"},this.config.captureUtm&&(this.utmParams=_()),this.config.captureContext&&(this.initialReferrer=k()),e.bootstrap?.deviceId?(this.deviceId=e.bootstrap.deviceId,U(e.bootstrap.deviceId)):this.deviceId=R()}get effectiveDistinctId(){return this.config.distinctId||this.deviceId}get bucketingId(){return this.deviceId}async initialize(){let e=this.initialized;if(!e)if(this.config.enableLocalEvaluation)try{let{config:t,etag:i}=await this.fetchConfig();this.serverConfig=t,this.lastEtag=i,this.evalResult=(0,m.localEvaluate)(this.serverConfig,this.bucketingId,this.config.attributes)}catch(t){this.serverConfig||(console.warn("SplitLab: config fetch failed, using safe defaults",t),this.serverConfig={experiments:[],flags:[]},this.evalResult={experiments:{},flags:{}})}else{let t={distinct_id:this.effectiveDistinctId};Object.keys(this.config.attributes).length>0&&(t.attributes=this.config.attributes);let i=await this.request("POST","/api/sdk/evaluate",t);this.evalResult={experiments:i.experiments,flags:i.flags}}this.flushTimer=setInterval(()=>{this.flush().catch(()=>{})},this.config.autoFlushInterval),this.config.realtimeUpdates&&this.config.enableLocalEvaluation&&this.connectConfigStream(),p&&(this.visibilityHandler=()=>{globalThis.document?.visibilityState==="hidden"?this.flushSync():this.config.enableLocalEvaluation&&this.refresh().catch(()=>{})},globalThis.document?.addEventListener("visibilitychange",this.visibilityHandler),this.beforeUnloadHandler=()=>{this.flushSync()},globalThis.addEventListener("beforeunload",this.beforeUnloadHandler)),this.config.trackPageviews&&p&&this.setupPageviewTracking(),this.initialized=!0,e&&this.refresh().catch(()=>{})}getVariant(e){if(!this.initialized||!this.evalResult)throw new Error("SplitLabClient not initialized. Call initialize() first.");let t=this.evalResult.experiments[e]??null;return t!==null&&!this.trackedExposures.has(e)&&(this.trackedExposures.add(e),this.track("$exposure",{experiment_key:e,variant_key:t}).catch(()=>{})),t}isFeatureEnabled(e){if(!this.initialized||!this.evalResult)throw new Error("SplitLabClient not initialized. Call initialize() first.");return this.evalResult.flags[e]??!1}hydrateFromBootstrap(e){this.serverConfig=e.serverConfig,this.lastEtag=e.etag,this.evalResult=e.evalResult,this.initialized=!0}async track(e,t){let i=this.enrichProperties(t);this.eventQueue.push({distinct_id:this.effectiveDistinctId,event_name:e,properties:i,timestamp:new Date().toISOString()}),this.eventQueue.length>=this.config.autoFlushSize&&await this.flush()}async trackPageview(e){await this.track("$pageview",e)}setSuperProperties(e){Object.assign(this.config.superProperties,e)}unsetSuperProperty(e){delete this.config.superProperties[e]}async flush(){if(this.eventQueue.length===0)return;let e=this.eventQueue;this.eventQueue=[];try{await this.request("POST","/ingest/batch",{events:e},!0)}catch{this.eventQueue=e.concat(this.eventQueue)}}async identify(e,t){let i=this.effectiveDistinctId;this.config.distinctId=e,i!==e&&await this.track("$identify",{...t,previous_distinct_id:i}),this.config.enableLocalEvaluation||(this.evalResult=null,this.initialized=!1,await this.initialize())}async refresh(){if(this.config.enableLocalEvaluation)try{let{config:e,etag:t,notModified:i}=await this.fetchConfig();if(i)return;this.serverConfig=e,this.lastEtag=t,this.evalResult=(0,m.localEvaluate)(this.serverConfig,this.bucketingId,this.config.attributes),this.config.onConfigUpdate&&this.config.onConfigUpdate()}catch{}else{let e={distinct_id:this.effectiveDistinctId};Object.keys(this.config.attributes).length>0&&(e.attributes=this.config.attributes);let t=await this.request("POST","/api/sdk/evaluate",e);this.evalResult={experiments:t.experiments,flags:t.flags}}}getDistinctId(){return this.effectiveDistinctId}getDeviceId(){return this.deviceId}setAttributes(e){this.config.attributes={...this.config.attributes,...e}}async destroy(){await this.flush(),this.flushTimer!==null&&(clearInterval(this.flushTimer),this.flushTimer=null),this.eventSource&&(this.eventSource.close(),this.eventSource=null),this.visibilityHandler&&(globalThis.document?.removeEventListener("visibilitychange",this.visibilityHandler),this.visibilityHandler=null),this.beforeUnloadHandler&&typeof globalThis.removeEventListener=="function"&&(globalThis.removeEventListener("beforeunload",this.beforeUnloadHandler),this.beforeUnloadHandler=null),this.pageviewCleanup&&(this.pageviewCleanup(),this.pageviewCleanup=null),this.initialized=!1}enrichProperties(e){let t={};if(this.config.captureContext){let i=I();if(i){for(let[s,r]of Object.entries(i))r!==null&&r!==""&&r!==void 0&&(t[s]=r);this.initialReferrer&&(t.referrer=this.initialReferrer)}}if(this.config.captureUtm)for(let[i,s]of Object.entries(this.utmParams))s&&(t[i]=s);this.config.trackSessions&&(t.session_id=T(this.config.sessionTimeout)),this.config.persistDeviceId&&this.deviceId&&(t.device_id=this.deviceId);for(let[i,s]of Object.entries(this.config.superProperties))s!==void 0&&(t[i]=s);if(e)for(let[i,s]of Object.entries(e))s!==void 0&&(t[i]=s);return t}setupPageviewTracking(){let e=globalThis,t=e.history;if(!t?.pushState)return;let i=e.location?.href,s=t.pushState.bind(t);t.pushState=(...l)=>{s(...l),this.onUrlChange(i),i=e.location?.href};let r=t.replaceState.bind(t);t.replaceState=(...l)=>{r(...l),this.onUrlChange(i),i=e.location?.href};let a=()=>{this.onUrlChange(i),i=e.location?.href};e.addEventListener("popstate",a),this.trackPageview().catch(()=>{}),this.pageviewCleanup=()=>{t.pushState=s,t.replaceState=r,e.removeEventListener("popstate",a)}}onUrlChange(e){globalThis.location?.href!==e&&setTimeout(()=>{this.trackPageview({previous_url:e||null}).catch(()=>{})},0)}async fetchConfig(){let e={};this.lastEtag&&(e["If-None-Match"]=this.lastEtag);let t=this.config.environment!=="production"?`&env=${encodeURIComponent(this.config.environment)}`:"",i=await fetch(`${this.config.baseUrl}/api/sdk/config?key=${encodeURIComponent(this.config.apiKey)}${t}`,{method:"GET",headers:e});if(i.status===304)return{config:this.serverConfig,etag:this.lastEtag,notModified:!0};if(!i.ok){let a=await i.text().catch(()=>"");throw new Error(`SplitLab API error ${i.status}: ${a}`)}let s=i.headers.get("etag");return{config:await i.json(),etag:s}}connectConfigStream(){if(typeof EventSource>"u")return;let e=this.config.environment!=="production"?`&env=${encodeURIComponent(this.config.environment)}`:"",t=`${this.config.baseUrl}/api/sdk/config/stream?key=${encodeURIComponent(this.config.apiKey)}${e}`;this.eventSource=new EventSource(t),this.eventSource.addEventListener("config_updated",()=>{this.refresh().catch(()=>{})}),this.eventSource.onerror=()=>{}}flushSync(){if(this.eventQueue.length===0)return;let e=this.eventQueue;this.eventQueue=[];let t=JSON.stringify({events:e}),i=`${this.config.ingestUrl}/ingest/batch?key=${encodeURIComponent(this.config.apiKey)}`;if(typeof globalThis.navigator?.sendBeacon=="function"){let s=new Blob([t],{type:"application/json"});try{if(globalThis.navigator.sendBeacon(i,s))return}catch{}}if(typeof fetch=="function")try{fetch(i,{method:"POST",headers:{"Content-Type":"application/json","X-API-Key":this.config.apiKey},body:t,keepalive:!0}).catch(()=>{})}catch{}}async request(e,t,i,s){let r=s?this.config.ingestUrl:this.config.baseUrl,a=await fetch(`${r}${t}`,{method:e,headers:{"Content-Type":"application/json","X-API-Key":this.config.apiKey},body:i?JSON.stringify(i):void 0});if(!a.ok){let l=await a.text().catch(()=>"");throw new Error(`SplitLab API error ${a.status}: ${l}`)}return a.json()}};0&&(module.exports={SplitLabClient,hashToFloat,murmurhash3});
1
+ "use strict";var v=Object.defineProperty;var L=Object.getOwnPropertyDescriptor;var O=Object.getOwnPropertyNames;var z=Object.prototype.hasOwnProperty;var j=(n,e)=>{for(var t in e)v(n,t,{get:e[t],enumerable:!0})},A=(n,e,t,i)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of O(e))!z.call(n,s)&&s!==t&&v(n,s,{get:()=>e[s],enumerable:!(i=L(e,s))||i.enumerable});return n};var B=n=>A(v({},"__esModule",{value:!0}),n);var M={};j(M,{SplitLabClient:()=>w,hashToFloat:()=>g.hashToFloat,murmurhash3:()=>g.murmurhash3});module.exports=B(M);var g=require("@splitlab/core"),C=require("@splitlab/core");var R=require("@splitlab/core");var c=typeof globalThis<"u"&&typeof globalThis.document<"u";function U(){if(!c)return null;let n=globalThis,e=n.navigator,t=n.location,i=n.document,s=n.screen,r=(0,R.parseUserAgent)(e?.userAgent);return{pathname:t?.pathname??null,hostname:t?.hostname??null,referrer:i?.referrer||null,search_params:t?.search||null,page_title:i?.title||null,hash:t?.hash||null,user_agent:e?.userAgent??null,browser:r.browser,browser_version:r.browser_version,os:r.os,os_version:r.os_version,device_type:r.device_type,screen_width:s?.width??null,screen_height:s?.height??null,viewport_width:n.innerWidth??null,viewport_height:n.innerHeight??null,language:e?.language??null,timezone:F()}}function F(){try{return Intl.DateTimeFormat().resolvedOptions().timeZone}catch{return null}}var H=["utm_source","utm_medium","utm_campaign","utm_term","utm_content"],_="__ot_utm";function T(){if(!c)return{};try{let i=globalThis.sessionStorage?.getItem(_);if(i)return JSON.parse(i)}catch{}let n=new URLSearchParams(globalThis.location?.search||""),e={},t=!1;for(let i of H){let s=n.get(i);s&&(e[i]=s,t=!0)}if(t)try{globalThis.sessionStorage?.setItem(_,JSON.stringify(e))}catch{}return e}var u="_sl_did",m="_sl_sid",k=63072e3,I="__ot_did";function y(n){if(!c)return null;try{let e=globalThis.document?.cookie?.match(new RegExp("(?:^|; )"+n.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")+"=([^;]*)"));return e?decodeURIComponent(e[1]):null}catch{return null}}function h(n,e,t){if(c)try{globalThis.document.cookie=`${n}=${encodeURIComponent(e)}; path=/; max-age=${t}; SameSite=Lax`}catch{}}function x(n){if(!c)return f();let e=Math.floor(n/1e3),t=y(m);if(t)return h(m,t,e),t;let i=f();return h(m,i,e),i}function P(){if(!c)return f();let n=y(u);if(n)return n;let e=null;try{let t=globalThis.localStorage;e=t?.getItem(I)||null,e&&t?.removeItem(I)}catch{}return e||(e=f()),h(u,e,k),e}function $(n){!c||y(u)===n||h(u,n,k)}var E="__ot_ref";function D(){if(!c)return null;try{let n=globalThis.sessionStorage,e=n?.getItem(E);if(e!==null)return e||null;let t=globalThis.document?.referrer||"";return n?.setItem(E,t),t||null}catch{return globalThis.document?.referrer||null}}function f(){let n=Date.now().toString(36),e=Math.random().toString(36).substring(2,10);return`${n}-${e}`}var b=typeof globalThis<"u"&&typeof globalThis.document<"u",w=class{constructor(e){this.evalResult=null;this.serverConfig=null;this.eventQueue=[];this.flushTimer=null;this.initialized=!1;this.visibilityHandler=null;this.beforeUnloadHandler=null;this.lastEtag=null;this.eventSource=null;this.deviceId=null;this.utmParams={};this.initialReferrer=null;this.pageviewCleanup=null;this.trackedExposures=new Set;this.config={apiKey:e.apiKey,baseUrl:e.baseUrl.replace(/\/$/,""),ingestUrl:(e.ingestUrl||e.baseUrl).replace(/\/$/,""),distinctId:e.distinctId??null,attributes:e.attributes||{},autoFlushInterval:e.autoFlushInterval??8e3,autoFlushSize:e.autoFlushSize??20,enableLocalEvaluation:e.enableLocalEvaluation??!0,realtimeUpdates:e.realtimeUpdates??!1,onConfigUpdate:e.onConfigUpdate??null,captureContext:e.captureContext??b,captureUtm:e.captureUtm??!0,trackSessions:e.trackSessions??!0,sessionTimeout:e.sessionTimeout??30*6e4,persistDeviceId:e.persistDeviceId??!0,trackPageviews:e.trackPageviews??!1,superProperties:e.superProperties??{},environment:e.environment??"production"},this.config.captureUtm&&(this.utmParams=T()),this.config.captureContext&&(this.initialReferrer=D()),e.bootstrap?.deviceId?(this.deviceId=e.bootstrap.deviceId,$(e.bootstrap.deviceId)):this.deviceId=P()}get effectiveDistinctId(){return this.config.distinctId||this.deviceId}get bucketingId(){return this.deviceId}async initialize(){let e=this.initialized;if(!e)if(this.config.enableLocalEvaluation)try{let{config:t,etag:i}=await this.fetchConfig();this.serverConfig=t,this.lastEtag=i,this.evalResult=(0,C.localEvaluate)(this.serverConfig,this.bucketingId,this.config.attributes)}catch(t){this.serverConfig||(console.warn("SplitLab: config fetch failed, using safe defaults",t),this.serverConfig={experiments:[],flags:[]},this.evalResult={experiments:{},flags:{}})}else{let t={distinct_id:this.effectiveDistinctId};Object.keys(this.config.attributes).length>0&&(t.attributes=this.config.attributes);let i=await this.request("POST","/api/sdk/evaluate",t);this.evalResult={experiments:i.experiments,flags:i.flags}}this.flushTimer=setInterval(()=>{this.flush().catch(()=>{})},this.config.autoFlushInterval),this.config.realtimeUpdates&&this.config.enableLocalEvaluation&&this.connectConfigStream(),b&&(this.visibilityHandler=()=>{globalThis.document?.visibilityState==="hidden"?this.flushSync():this.config.enableLocalEvaluation&&this.refresh().catch(()=>{})},globalThis.document?.addEventListener("visibilitychange",this.visibilityHandler),this.beforeUnloadHandler=()=>{this.flushSync()},globalThis.addEventListener("beforeunload",this.beforeUnloadHandler)),this.config.trackPageviews&&b&&this.setupPageviewTracking(),this.initialized=!0,e&&this.refresh().catch(()=>{})}getVariant(e){if(!this.initialized||!this.evalResult)throw new Error("SplitLabClient not initialized. Call initialize() first.");let t=this.evalResult.experiments[e]??null;return t!==null&&!this.trackedExposures.has(e)&&(this.trackedExposures.add(e),this.track("$exposure",{experiment_key:e,variant_key:t}).catch(()=>{})),t}isFeatureEnabled(e){if(!this.initialized||!this.evalResult)throw new Error("SplitLabClient not initialized. Call initialize() first.");return this.evalResult.flags[e]??!1}hydrateFromBootstrap(e){this.serverConfig=e.serverConfig,this.lastEtag=e.etag,this.evalResult=e.evalResult,this.initialized=!0}async track(e,t){this.eventQueue.push({distinct_id:this.effectiveDistinctId,event_name:e,properties:this.mergeUserProperties(t),timestamp:new Date().toISOString()}),this.eventQueue.length>=this.config.autoFlushSize&&await this.flush()}async trackPageview(e){await this.track("$pageview",e)}setSuperProperties(e){Object.assign(this.config.superProperties,e)}unsetSuperProperty(e){delete this.config.superProperties[e]}async flush(){if(this.eventQueue.length===0)return;let e=this.eventQueue;this.eventQueue=[];try{let t=JSON.stringify({context:this.buildContext(),events:e}),i=await K(t),s=JSON.stringify({_e:i}),r=`${this.config.ingestUrl}/ingest/batch?key=${encodeURIComponent(this.config.apiKey)}`,o=await fetch(r,{method:"POST",headers:{"Content-Type":"application/json"},body:s});if(!o.ok)throw new Error(`${o.status}`)}catch{this.eventQueue=e.concat(this.eventQueue)}}async identify(e,t){let i=this.effectiveDistinctId;this.config.distinctId=e,i!==e&&await this.track("$identify",{...t,previous_distinct_id:i}),this.config.enableLocalEvaluation||(this.evalResult=null,this.initialized=!1,await this.initialize())}async refresh(){if(this.config.enableLocalEvaluation)try{let{config:e,etag:t,notModified:i}=await this.fetchConfig();if(i)return;this.serverConfig=e,this.lastEtag=t,this.evalResult=(0,C.localEvaluate)(this.serverConfig,this.bucketingId,this.config.attributes),this.config.onConfigUpdate&&this.config.onConfigUpdate()}catch{}else{let e={distinct_id:this.effectiveDistinctId};Object.keys(this.config.attributes).length>0&&(e.attributes=this.config.attributes);let t=await this.request("POST","/api/sdk/evaluate",e);this.evalResult={experiments:t.experiments,flags:t.flags}}}getDistinctId(){return this.effectiveDistinctId}getDeviceId(){return this.deviceId}setAttributes(e){this.config.attributes={...this.config.attributes,...e}}async destroy(){await this.flush(),this.flushTimer!==null&&(clearInterval(this.flushTimer),this.flushTimer=null),this.eventSource&&(this.eventSource.close(),this.eventSource=null),this.visibilityHandler&&(globalThis.document?.removeEventListener("visibilitychange",this.visibilityHandler),this.visibilityHandler=null),this.beforeUnloadHandler&&typeof globalThis.removeEventListener=="function"&&(globalThis.removeEventListener("beforeunload",this.beforeUnloadHandler),this.beforeUnloadHandler=null),this.pageviewCleanup&&(this.pageviewCleanup(),this.pageviewCleanup=null),this.initialized=!1}buildContext(){let e={};if(this.config.captureContext){let t=U();if(t){for(let[i,s]of Object.entries(t))s!==null&&s!==""&&s!==void 0&&(e[i]=s);this.initialReferrer&&(e.referrer=this.initialReferrer)}}if(this.config.captureUtm)for(let[t,i]of Object.entries(this.utmParams))i&&(e[t]=i);this.config.trackSessions&&(e.session_id=x(this.config.sessionTimeout)),this.config.persistDeviceId&&this.deviceId&&(e.device_id=this.deviceId);for(let[t,i]of Object.entries(this.config.superProperties))i!==void 0&&(e[t]=i);return this.remapCustomDimensions(e)}remapCustomDimensions(e){if(!this.serverConfig?.custom_dimensions?.length)return e;let t={...e};for(let i of this.serverConfig.custom_dimensions)i.property_key in t&&(t[`dim_${i.index}`]=t[i.property_key],delete t[i.property_key]);return t}mergeUserProperties(e){if(!e)return;let t={};for(let[i,s]of Object.entries(e))s!==void 0&&(t[i]=s);if(Object.keys(t).length!==0)return this.remapCustomDimensions(t)}setupPageviewTracking(){let e=globalThis,t=e.history;if(!t?.pushState)return;let i=e.location?.href,s=t.pushState.bind(t);t.pushState=(...l)=>{s(...l),this.onUrlChange(i),i=e.location?.href};let r=t.replaceState.bind(t);t.replaceState=(...l)=>{r(...l),this.onUrlChange(i),i=e.location?.href};let o=()=>{this.onUrlChange(i),i=e.location?.href};e.addEventListener("popstate",o),this.trackPageview().catch(()=>{}),this.pageviewCleanup=()=>{t.pushState=s,t.replaceState=r,e.removeEventListener("popstate",o)}}onUrlChange(e){globalThis.location?.href!==e&&setTimeout(()=>{this.trackPageview({previous_url:e||null}).catch(()=>{})},0)}async fetchConfig(){let e={};this.lastEtag&&(e["If-None-Match"]=this.lastEtag);let t=this.config.environment!=="production"?`&env=${encodeURIComponent(this.config.environment)}`:"",i=await fetch(`${this.config.baseUrl}/api/sdk/config?key=${encodeURIComponent(this.config.apiKey)}${t}`,{method:"GET",headers:e});if(i.status===304)return{config:this.serverConfig,etag:this.lastEtag,notModified:!0};if(!i.ok){let o=await i.text().catch(()=>"");throw new Error(`SplitLab API error ${i.status}: ${o}`)}let s=i.headers.get("etag");return{config:await i.json(),etag:s}}connectConfigStream(){if(typeof EventSource>"u")return;let e=this.config.environment!=="production"?`&env=${encodeURIComponent(this.config.environment)}`:"",t=`${this.config.baseUrl}/api/sdk/config/stream?key=${encodeURIComponent(this.config.apiKey)}${e}`;this.eventSource=new EventSource(t),this.eventSource.addEventListener("config_updated",()=>{this.refresh().catch(()=>{})}),this.eventSource.onerror=()=>{}}flushSync(){if(this.eventQueue.length===0)return;let e=this.eventQueue;this.eventQueue=[];let t=JSON.stringify({context:this.buildContext(),events:e}),i=typeof btoa=="function"?btoa(unescape(encodeURIComponent(t))):t,r=typeof btoa=="function"?JSON.stringify({_e:i}):t,o=`${this.config.ingestUrl}/ingest/batch?key=${encodeURIComponent(this.config.apiKey)}`;if(typeof globalThis.navigator?.sendBeacon=="function"){let l=new Blob([r],{type:"application/json"});try{if(globalThis.navigator.sendBeacon(o,l))return}catch{}}if(typeof fetch=="function")try{fetch(o,{method:"POST",headers:{"Content-Type":"application/json"},body:r,keepalive:!0}).catch(()=>{})}catch{}}async request(e,t,i){let s=await fetch(`${this.config.baseUrl}${t}`,{method:e,headers:{"Content-Type":"application/json","X-API-Key":this.config.apiKey},body:i?JSON.stringify(i):void 0});if(!s.ok){let r=await s.text().catch(()=>"");throw new Error(`SplitLab API error ${s.status}: ${r}`)}return s.json()}};async function K(n){let e=new TextEncoder().encode(n);if(typeof CompressionStream<"u")try{let t=new CompressionStream("gzip"),i=t.writable.getWriter();i.write(e),i.close();let s=t.readable.getReader(),r=[];for(;;){let{done:a,value:p}=await s.read();if(a)break;r.push(p)}let o=r.reduce((a,p)=>a+p.length,0),l=new Uint8Array(o),d=0;for(let a of r)l.set(a,d),d+=a.length;let S="";for(let a=0;a<l.length;a++)S+=String.fromCharCode(l[a]);return btoa(S)}catch{}return btoa(unescape(encodeURIComponent(n)))}0&&(module.exports={SplitLabClient,hashToFloat,murmurhash3});
package/dist/index.d.cts CHANGED
@@ -1,6 +1,6 @@
1
1
  import * as _splitlab_core from '@splitlab/core';
2
2
  import { BootstrapData } from '@splitlab/core';
3
- export { BootstrapData, EvalResult, ExclusionGroupConfig, ExperimentConfig, FlagConfig, SegmentConfig, ServerConfig, TargetingCondition, TargetingGroup, TargetingRules, TrackEvent, Variant, hashToFloat, murmurhash3 } from '@splitlab/core';
3
+ export { BootstrapData, CustomDimensionMapping, EvalResult, ExclusionGroupConfig, ExperimentConfig, FlagConfig, SegmentConfig, ServerConfig, TargetingCondition, TargetingGroup, TargetingRules, TrackEvent, Variant, hashToFloat, murmurhash3 } from '@splitlab/core';
4
4
 
5
5
  interface SplitLabConfig {
6
6
  apiKey: string;
@@ -107,7 +107,12 @@ declare class SplitLabClient {
107
107
  getDeviceId(): string;
108
108
  setAttributes(attributes: Record<string, any>): void;
109
109
  destroy(): Promise<void>;
110
- private enrichProperties;
110
+ /** Shared context sent once per batch (not duplicated per-event). */
111
+ private buildContext;
112
+ /** Remap user property keys to dim_N slots based on custom dimension config. */
113
+ private remapCustomDimensions;
114
+ /** Per-event properties: only user-supplied props (no auto-captured context). */
115
+ private mergeUserProperties;
111
116
  private setupPageviewTracking;
112
117
  private onUrlChange;
113
118
  private fetchConfig;
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import * as _splitlab_core from '@splitlab/core';
2
2
  import { BootstrapData } from '@splitlab/core';
3
- export { BootstrapData, EvalResult, ExclusionGroupConfig, ExperimentConfig, FlagConfig, SegmentConfig, ServerConfig, TargetingCondition, TargetingGroup, TargetingRules, TrackEvent, Variant, hashToFloat, murmurhash3 } from '@splitlab/core';
3
+ export { BootstrapData, CustomDimensionMapping, EvalResult, ExclusionGroupConfig, ExperimentConfig, FlagConfig, SegmentConfig, ServerConfig, TargetingCondition, TargetingGroup, TargetingRules, TrackEvent, Variant, hashToFloat, murmurhash3 } from '@splitlab/core';
4
4
 
5
5
  interface SplitLabConfig {
6
6
  apiKey: string;
@@ -107,7 +107,12 @@ declare class SplitLabClient {
107
107
  getDeviceId(): string;
108
108
  setAttributes(attributes: Record<string, any>): void;
109
109
  destroy(): Promise<void>;
110
- private enrichProperties;
110
+ /** Shared context sent once per batch (not duplicated per-event). */
111
+ private buildContext;
112
+ /** Remap user property keys to dim_N slots based on custom dimension config. */
113
+ private remapCustomDimensions;
114
+ /** Per-event properties: only user-supplied props (no auto-captured context). */
115
+ private mergeUserProperties;
111
116
  private setupPageviewTracking;
112
117
  private onUrlChange;
113
118
  private fetchConfig;
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- import{murmurhash3 as z,hashToFloat as j}from"@splitlab/core";import{localEvaluate as E}from"@splitlab/core";import{parseUserAgent as R}from"@splitlab/core";var o=typeof globalThis<"u"&&typeof globalThis.document<"u";function b(){if(!o)return null;let n=globalThis,e=n.navigator,t=n.location,i=n.document,s=n.screen,r=R(e?.userAgent);return{pathname:t?.pathname??null,hostname:t?.hostname??null,referrer:i?.referrer||null,search_params:t?.search||null,page_title:i?.title||null,hash:t?.hash||null,user_agent:e?.userAgent??null,browser:r.browser,browser_version:r.browser_version,os:r.os,os_version:r.os_version,device_type:r.device_type,screen_width:s?.width??null,screen_height:s?.height??null,viewport_width:n.innerWidth??null,viewport_height:n.innerHeight??null,language:e?.language??null,timezone:U()}}function U(){try{return Intl.DateTimeFormat().resolvedOptions().timeZone}catch{return null}}var k=["utm_source","utm_medium","utm_campaign","utm_term","utm_content"],v="__ot_utm";function y(){if(!o)return{};try{let i=globalThis.sessionStorage?.getItem(v);if(i)return JSON.parse(i)}catch{}let n=new URLSearchParams(globalThis.location?.search||""),e={},t=!1;for(let i of k){let s=n.get(i);s&&(e[i]=s,t=!0)}if(t)try{globalThis.sessionStorage?.setItem(v,JSON.stringify(e))}catch{}return e}var c="_sl_did",g="_sl_sid",C=63072e3,p="__ot_did";function f(n){if(!o)return null;try{let e=globalThis.document?.cookie?.match(new RegExp("(?:^|; )"+n.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")+"=([^;]*)"));return e?decodeURIComponent(e[1]):null}catch{return null}}function u(n,e,t){if(o)try{globalThis.document.cookie=`${n}=${encodeURIComponent(e)}; path=/; max-age=${t}; SameSite=Lax`}catch{}}function S(n){if(!o)return h();let e=Math.floor(n/1e3),t=f(g);if(t)return u(g,t,e),t;let i=h();return u(g,i,e),i}function w(){if(!o)return h();let n=f(c);if(n)return n;let e=null;try{let t=globalThis.localStorage;e=t?.getItem(p)||null,e&&t?.removeItem(p)}catch{}return e||(e=h()),u(c,e,C),e}function I(n){!o||f(c)===n||u(c,n,C)}var m="__ot_ref";function _(){if(!o)return null;try{let n=globalThis.sessionStorage,e=n?.getItem(m);if(e!==null)return e||null;let t=globalThis.document?.referrer||"";return n?.setItem(m,t),t||null}catch{return globalThis.document?.referrer||null}}function h(){let n=Date.now().toString(36),e=Math.random().toString(36).substring(2,10);return`${n}-${e}`}var d=typeof globalThis<"u"&&typeof globalThis.document<"u",T=class{constructor(e){this.evalResult=null;this.serverConfig=null;this.eventQueue=[];this.flushTimer=null;this.initialized=!1;this.visibilityHandler=null;this.beforeUnloadHandler=null;this.lastEtag=null;this.eventSource=null;this.deviceId=null;this.utmParams={};this.initialReferrer=null;this.pageviewCleanup=null;this.trackedExposures=new Set;this.config={apiKey:e.apiKey,baseUrl:e.baseUrl.replace(/\/$/,""),ingestUrl:(e.ingestUrl||e.baseUrl).replace(/\/$/,""),distinctId:e.distinctId??null,attributes:e.attributes||{},autoFlushInterval:e.autoFlushInterval??8e3,autoFlushSize:e.autoFlushSize??20,enableLocalEvaluation:e.enableLocalEvaluation??!0,realtimeUpdates:e.realtimeUpdates??!1,onConfigUpdate:e.onConfigUpdate??null,captureContext:e.captureContext??d,captureUtm:e.captureUtm??!0,trackSessions:e.trackSessions??!0,sessionTimeout:e.sessionTimeout??30*6e4,persistDeviceId:e.persistDeviceId??!0,trackPageviews:e.trackPageviews??!1,superProperties:e.superProperties??{},environment:e.environment??"production"},this.config.captureUtm&&(this.utmParams=y()),this.config.captureContext&&(this.initialReferrer=_()),e.bootstrap?.deviceId?(this.deviceId=e.bootstrap.deviceId,I(e.bootstrap.deviceId)):this.deviceId=w()}get effectiveDistinctId(){return this.config.distinctId||this.deviceId}get bucketingId(){return this.deviceId}async initialize(){let e=this.initialized;if(!e)if(this.config.enableLocalEvaluation)try{let{config:t,etag:i}=await this.fetchConfig();this.serverConfig=t,this.lastEtag=i,this.evalResult=E(this.serverConfig,this.bucketingId,this.config.attributes)}catch(t){this.serverConfig||(console.warn("SplitLab: config fetch failed, using safe defaults",t),this.serverConfig={experiments:[],flags:[]},this.evalResult={experiments:{},flags:{}})}else{let t={distinct_id:this.effectiveDistinctId};Object.keys(this.config.attributes).length>0&&(t.attributes=this.config.attributes);let i=await this.request("POST","/api/sdk/evaluate",t);this.evalResult={experiments:i.experiments,flags:i.flags}}this.flushTimer=setInterval(()=>{this.flush().catch(()=>{})},this.config.autoFlushInterval),this.config.realtimeUpdates&&this.config.enableLocalEvaluation&&this.connectConfigStream(),d&&(this.visibilityHandler=()=>{globalThis.document?.visibilityState==="hidden"?this.flushSync():this.config.enableLocalEvaluation&&this.refresh().catch(()=>{})},globalThis.document?.addEventListener("visibilitychange",this.visibilityHandler),this.beforeUnloadHandler=()=>{this.flushSync()},globalThis.addEventListener("beforeunload",this.beforeUnloadHandler)),this.config.trackPageviews&&d&&this.setupPageviewTracking(),this.initialized=!0,e&&this.refresh().catch(()=>{})}getVariant(e){if(!this.initialized||!this.evalResult)throw new Error("SplitLabClient not initialized. Call initialize() first.");let t=this.evalResult.experiments[e]??null;return t!==null&&!this.trackedExposures.has(e)&&(this.trackedExposures.add(e),this.track("$exposure",{experiment_key:e,variant_key:t}).catch(()=>{})),t}isFeatureEnabled(e){if(!this.initialized||!this.evalResult)throw new Error("SplitLabClient not initialized. Call initialize() first.");return this.evalResult.flags[e]??!1}hydrateFromBootstrap(e){this.serverConfig=e.serverConfig,this.lastEtag=e.etag,this.evalResult=e.evalResult,this.initialized=!0}async track(e,t){let i=this.enrichProperties(t);this.eventQueue.push({distinct_id:this.effectiveDistinctId,event_name:e,properties:i,timestamp:new Date().toISOString()}),this.eventQueue.length>=this.config.autoFlushSize&&await this.flush()}async trackPageview(e){await this.track("$pageview",e)}setSuperProperties(e){Object.assign(this.config.superProperties,e)}unsetSuperProperty(e){delete this.config.superProperties[e]}async flush(){if(this.eventQueue.length===0)return;let e=this.eventQueue;this.eventQueue=[];try{await this.request("POST","/ingest/batch",{events:e},!0)}catch{this.eventQueue=e.concat(this.eventQueue)}}async identify(e,t){let i=this.effectiveDistinctId;this.config.distinctId=e,i!==e&&await this.track("$identify",{...t,previous_distinct_id:i}),this.config.enableLocalEvaluation||(this.evalResult=null,this.initialized=!1,await this.initialize())}async refresh(){if(this.config.enableLocalEvaluation)try{let{config:e,etag:t,notModified:i}=await this.fetchConfig();if(i)return;this.serverConfig=e,this.lastEtag=t,this.evalResult=E(this.serverConfig,this.bucketingId,this.config.attributes),this.config.onConfigUpdate&&this.config.onConfigUpdate()}catch{}else{let e={distinct_id:this.effectiveDistinctId};Object.keys(this.config.attributes).length>0&&(e.attributes=this.config.attributes);let t=await this.request("POST","/api/sdk/evaluate",e);this.evalResult={experiments:t.experiments,flags:t.flags}}}getDistinctId(){return this.effectiveDistinctId}getDeviceId(){return this.deviceId}setAttributes(e){this.config.attributes={...this.config.attributes,...e}}async destroy(){await this.flush(),this.flushTimer!==null&&(clearInterval(this.flushTimer),this.flushTimer=null),this.eventSource&&(this.eventSource.close(),this.eventSource=null),this.visibilityHandler&&(globalThis.document?.removeEventListener("visibilitychange",this.visibilityHandler),this.visibilityHandler=null),this.beforeUnloadHandler&&typeof globalThis.removeEventListener=="function"&&(globalThis.removeEventListener("beforeunload",this.beforeUnloadHandler),this.beforeUnloadHandler=null),this.pageviewCleanup&&(this.pageviewCleanup(),this.pageviewCleanup=null),this.initialized=!1}enrichProperties(e){let t={};if(this.config.captureContext){let i=b();if(i){for(let[s,r]of Object.entries(i))r!==null&&r!==""&&r!==void 0&&(t[s]=r);this.initialReferrer&&(t.referrer=this.initialReferrer)}}if(this.config.captureUtm)for(let[i,s]of Object.entries(this.utmParams))s&&(t[i]=s);this.config.trackSessions&&(t.session_id=S(this.config.sessionTimeout)),this.config.persistDeviceId&&this.deviceId&&(t.device_id=this.deviceId);for(let[i,s]of Object.entries(this.config.superProperties))s!==void 0&&(t[i]=s);if(e)for(let[i,s]of Object.entries(e))s!==void 0&&(t[i]=s);return t}setupPageviewTracking(){let e=globalThis,t=e.history;if(!t?.pushState)return;let i=e.location?.href,s=t.pushState.bind(t);t.pushState=(...l)=>{s(...l),this.onUrlChange(i),i=e.location?.href};let r=t.replaceState.bind(t);t.replaceState=(...l)=>{r(...l),this.onUrlChange(i),i=e.location?.href};let a=()=>{this.onUrlChange(i),i=e.location?.href};e.addEventListener("popstate",a),this.trackPageview().catch(()=>{}),this.pageviewCleanup=()=>{t.pushState=s,t.replaceState=r,e.removeEventListener("popstate",a)}}onUrlChange(e){globalThis.location?.href!==e&&setTimeout(()=>{this.trackPageview({previous_url:e||null}).catch(()=>{})},0)}async fetchConfig(){let e={};this.lastEtag&&(e["If-None-Match"]=this.lastEtag);let t=this.config.environment!=="production"?`&env=${encodeURIComponent(this.config.environment)}`:"",i=await fetch(`${this.config.baseUrl}/api/sdk/config?key=${encodeURIComponent(this.config.apiKey)}${t}`,{method:"GET",headers:e});if(i.status===304)return{config:this.serverConfig,etag:this.lastEtag,notModified:!0};if(!i.ok){let a=await i.text().catch(()=>"");throw new Error(`SplitLab API error ${i.status}: ${a}`)}let s=i.headers.get("etag");return{config:await i.json(),etag:s}}connectConfigStream(){if(typeof EventSource>"u")return;let e=this.config.environment!=="production"?`&env=${encodeURIComponent(this.config.environment)}`:"",t=`${this.config.baseUrl}/api/sdk/config/stream?key=${encodeURIComponent(this.config.apiKey)}${e}`;this.eventSource=new EventSource(t),this.eventSource.addEventListener("config_updated",()=>{this.refresh().catch(()=>{})}),this.eventSource.onerror=()=>{}}flushSync(){if(this.eventQueue.length===0)return;let e=this.eventQueue;this.eventQueue=[];let t=JSON.stringify({events:e}),i=`${this.config.ingestUrl}/ingest/batch?key=${encodeURIComponent(this.config.apiKey)}`;if(typeof globalThis.navigator?.sendBeacon=="function"){let s=new Blob([t],{type:"application/json"});try{if(globalThis.navigator.sendBeacon(i,s))return}catch{}}if(typeof fetch=="function")try{fetch(i,{method:"POST",headers:{"Content-Type":"application/json","X-API-Key":this.config.apiKey},body:t,keepalive:!0}).catch(()=>{})}catch{}}async request(e,t,i,s){let r=s?this.config.ingestUrl:this.config.baseUrl,a=await fetch(`${r}${t}`,{method:e,headers:{"Content-Type":"application/json","X-API-Key":this.config.apiKey},body:i?JSON.stringify(i):void 0});if(!a.ok){let l=await a.text().catch(()=>"");throw new Error(`SplitLab API error ${a.status}: ${l}`)}return a.json()}};export{T as SplitLabClient,j as hashToFloat,z as murmurhash3};
1
+ import{murmurhash3 as H,hashToFloat as K}from"@splitlab/core";import{localEvaluate as k}from"@splitlab/core";import{parseUserAgent as P}from"@splitlab/core";var c=typeof globalThis<"u"&&typeof globalThis.document<"u";function S(){if(!c)return null;let n=globalThis,e=n.navigator,t=n.location,i=n.document,s=n.screen,r=P(e?.userAgent);return{pathname:t?.pathname??null,hostname:t?.hostname??null,referrer:i?.referrer||null,search_params:t?.search||null,page_title:i?.title||null,hash:t?.hash||null,user_agent:e?.userAgent??null,browser:r.browser,browser_version:r.browser_version,os:r.os,os_version:r.os_version,device_type:r.device_type,screen_width:s?.width??null,screen_height:s?.height??null,viewport_width:n.innerWidth??null,viewport_height:n.innerHeight??null,language:e?.language??null,timezone:$()}}function $(){try{return Intl.DateTimeFormat().resolvedOptions().timeZone}catch{return null}}var D=["utm_source","utm_medium","utm_campaign","utm_term","utm_content"],b="__ot_utm";function _(){if(!c)return{};try{let i=globalThis.sessionStorage?.getItem(b);if(i)return JSON.parse(i)}catch{}let n=new URLSearchParams(globalThis.location?.search||""),e={},t=!1;for(let i of D){let s=n.get(i);s&&(e[i]=s,t=!0)}if(t)try{globalThis.sessionStorage?.setItem(b,JSON.stringify(e))}catch{}return e}var u="_sl_did",p="_sl_sid",I=63072e3,C="__ot_did";function v(n){if(!c)return null;try{let e=globalThis.document?.cookie?.match(new RegExp("(?:^|; )"+n.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")+"=([^;]*)"));return e?decodeURIComponent(e[1]):null}catch{return null}}function h(n,e,t){if(c)try{globalThis.document.cookie=`${n}=${encodeURIComponent(e)}; path=/; max-age=${t}; SameSite=Lax`}catch{}}function E(n){if(!c)return f();let e=Math.floor(n/1e3),t=v(p);if(t)return h(p,t,e),t;let i=f();return h(p,i,e),i}function R(){if(!c)return f();let n=v(u);if(n)return n;let e=null;try{let t=globalThis.localStorage;e=t?.getItem(C)||null,e&&t?.removeItem(C)}catch{}return e||(e=f()),h(u,e,I),e}function U(n){!c||v(u)===n||h(u,n,I)}var w="__ot_ref";function T(){if(!c)return null;try{let n=globalThis.sessionStorage,e=n?.getItem(w);if(e!==null)return e||null;let t=globalThis.document?.referrer||"";return n?.setItem(w,t),t||null}catch{return globalThis.document?.referrer||null}}function f(){let n=Date.now().toString(36),e=Math.random().toString(36).substring(2,10);return`${n}-${e}`}var m=typeof globalThis<"u"&&typeof globalThis.document<"u",x=class{constructor(e){this.evalResult=null;this.serverConfig=null;this.eventQueue=[];this.flushTimer=null;this.initialized=!1;this.visibilityHandler=null;this.beforeUnloadHandler=null;this.lastEtag=null;this.eventSource=null;this.deviceId=null;this.utmParams={};this.initialReferrer=null;this.pageviewCleanup=null;this.trackedExposures=new Set;this.config={apiKey:e.apiKey,baseUrl:e.baseUrl.replace(/\/$/,""),ingestUrl:(e.ingestUrl||e.baseUrl).replace(/\/$/,""),distinctId:e.distinctId??null,attributes:e.attributes||{},autoFlushInterval:e.autoFlushInterval??8e3,autoFlushSize:e.autoFlushSize??20,enableLocalEvaluation:e.enableLocalEvaluation??!0,realtimeUpdates:e.realtimeUpdates??!1,onConfigUpdate:e.onConfigUpdate??null,captureContext:e.captureContext??m,captureUtm:e.captureUtm??!0,trackSessions:e.trackSessions??!0,sessionTimeout:e.sessionTimeout??30*6e4,persistDeviceId:e.persistDeviceId??!0,trackPageviews:e.trackPageviews??!1,superProperties:e.superProperties??{},environment:e.environment??"production"},this.config.captureUtm&&(this.utmParams=_()),this.config.captureContext&&(this.initialReferrer=T()),e.bootstrap?.deviceId?(this.deviceId=e.bootstrap.deviceId,U(e.bootstrap.deviceId)):this.deviceId=R()}get effectiveDistinctId(){return this.config.distinctId||this.deviceId}get bucketingId(){return this.deviceId}async initialize(){let e=this.initialized;if(!e)if(this.config.enableLocalEvaluation)try{let{config:t,etag:i}=await this.fetchConfig();this.serverConfig=t,this.lastEtag=i,this.evalResult=k(this.serverConfig,this.bucketingId,this.config.attributes)}catch(t){this.serverConfig||(console.warn("SplitLab: config fetch failed, using safe defaults",t),this.serverConfig={experiments:[],flags:[]},this.evalResult={experiments:{},flags:{}})}else{let t={distinct_id:this.effectiveDistinctId};Object.keys(this.config.attributes).length>0&&(t.attributes=this.config.attributes);let i=await this.request("POST","/api/sdk/evaluate",t);this.evalResult={experiments:i.experiments,flags:i.flags}}this.flushTimer=setInterval(()=>{this.flush().catch(()=>{})},this.config.autoFlushInterval),this.config.realtimeUpdates&&this.config.enableLocalEvaluation&&this.connectConfigStream(),m&&(this.visibilityHandler=()=>{globalThis.document?.visibilityState==="hidden"?this.flushSync():this.config.enableLocalEvaluation&&this.refresh().catch(()=>{})},globalThis.document?.addEventListener("visibilitychange",this.visibilityHandler),this.beforeUnloadHandler=()=>{this.flushSync()},globalThis.addEventListener("beforeunload",this.beforeUnloadHandler)),this.config.trackPageviews&&m&&this.setupPageviewTracking(),this.initialized=!0,e&&this.refresh().catch(()=>{})}getVariant(e){if(!this.initialized||!this.evalResult)throw new Error("SplitLabClient not initialized. Call initialize() first.");let t=this.evalResult.experiments[e]??null;return t!==null&&!this.trackedExposures.has(e)&&(this.trackedExposures.add(e),this.track("$exposure",{experiment_key:e,variant_key:t}).catch(()=>{})),t}isFeatureEnabled(e){if(!this.initialized||!this.evalResult)throw new Error("SplitLabClient not initialized. Call initialize() first.");return this.evalResult.flags[e]??!1}hydrateFromBootstrap(e){this.serverConfig=e.serverConfig,this.lastEtag=e.etag,this.evalResult=e.evalResult,this.initialized=!0}async track(e,t){this.eventQueue.push({distinct_id:this.effectiveDistinctId,event_name:e,properties:this.mergeUserProperties(t),timestamp:new Date().toISOString()}),this.eventQueue.length>=this.config.autoFlushSize&&await this.flush()}async trackPageview(e){await this.track("$pageview",e)}setSuperProperties(e){Object.assign(this.config.superProperties,e)}unsetSuperProperty(e){delete this.config.superProperties[e]}async flush(){if(this.eventQueue.length===0)return;let e=this.eventQueue;this.eventQueue=[];try{let t=JSON.stringify({context:this.buildContext(),events:e}),i=await L(t),s=JSON.stringify({_e:i}),r=`${this.config.ingestUrl}/ingest/batch?key=${encodeURIComponent(this.config.apiKey)}`,o=await fetch(r,{method:"POST",headers:{"Content-Type":"application/json"},body:s});if(!o.ok)throw new Error(`${o.status}`)}catch{this.eventQueue=e.concat(this.eventQueue)}}async identify(e,t){let i=this.effectiveDistinctId;this.config.distinctId=e,i!==e&&await this.track("$identify",{...t,previous_distinct_id:i}),this.config.enableLocalEvaluation||(this.evalResult=null,this.initialized=!1,await this.initialize())}async refresh(){if(this.config.enableLocalEvaluation)try{let{config:e,etag:t,notModified:i}=await this.fetchConfig();if(i)return;this.serverConfig=e,this.lastEtag=t,this.evalResult=k(this.serverConfig,this.bucketingId,this.config.attributes),this.config.onConfigUpdate&&this.config.onConfigUpdate()}catch{}else{let e={distinct_id:this.effectiveDistinctId};Object.keys(this.config.attributes).length>0&&(e.attributes=this.config.attributes);let t=await this.request("POST","/api/sdk/evaluate",e);this.evalResult={experiments:t.experiments,flags:t.flags}}}getDistinctId(){return this.effectiveDistinctId}getDeviceId(){return this.deviceId}setAttributes(e){this.config.attributes={...this.config.attributes,...e}}async destroy(){await this.flush(),this.flushTimer!==null&&(clearInterval(this.flushTimer),this.flushTimer=null),this.eventSource&&(this.eventSource.close(),this.eventSource=null),this.visibilityHandler&&(globalThis.document?.removeEventListener("visibilitychange",this.visibilityHandler),this.visibilityHandler=null),this.beforeUnloadHandler&&typeof globalThis.removeEventListener=="function"&&(globalThis.removeEventListener("beforeunload",this.beforeUnloadHandler),this.beforeUnloadHandler=null),this.pageviewCleanup&&(this.pageviewCleanup(),this.pageviewCleanup=null),this.initialized=!1}buildContext(){let e={};if(this.config.captureContext){let t=S();if(t){for(let[i,s]of Object.entries(t))s!==null&&s!==""&&s!==void 0&&(e[i]=s);this.initialReferrer&&(e.referrer=this.initialReferrer)}}if(this.config.captureUtm)for(let[t,i]of Object.entries(this.utmParams))i&&(e[t]=i);this.config.trackSessions&&(e.session_id=E(this.config.sessionTimeout)),this.config.persistDeviceId&&this.deviceId&&(e.device_id=this.deviceId);for(let[t,i]of Object.entries(this.config.superProperties))i!==void 0&&(e[t]=i);return this.remapCustomDimensions(e)}remapCustomDimensions(e){if(!this.serverConfig?.custom_dimensions?.length)return e;let t={...e};for(let i of this.serverConfig.custom_dimensions)i.property_key in t&&(t[`dim_${i.index}`]=t[i.property_key],delete t[i.property_key]);return t}mergeUserProperties(e){if(!e)return;let t={};for(let[i,s]of Object.entries(e))s!==void 0&&(t[i]=s);if(Object.keys(t).length!==0)return this.remapCustomDimensions(t)}setupPageviewTracking(){let e=globalThis,t=e.history;if(!t?.pushState)return;let i=e.location?.href,s=t.pushState.bind(t);t.pushState=(...l)=>{s(...l),this.onUrlChange(i),i=e.location?.href};let r=t.replaceState.bind(t);t.replaceState=(...l)=>{r(...l),this.onUrlChange(i),i=e.location?.href};let o=()=>{this.onUrlChange(i),i=e.location?.href};e.addEventListener("popstate",o),this.trackPageview().catch(()=>{}),this.pageviewCleanup=()=>{t.pushState=s,t.replaceState=r,e.removeEventListener("popstate",o)}}onUrlChange(e){globalThis.location?.href!==e&&setTimeout(()=>{this.trackPageview({previous_url:e||null}).catch(()=>{})},0)}async fetchConfig(){let e={};this.lastEtag&&(e["If-None-Match"]=this.lastEtag);let t=this.config.environment!=="production"?`&env=${encodeURIComponent(this.config.environment)}`:"",i=await fetch(`${this.config.baseUrl}/api/sdk/config?key=${encodeURIComponent(this.config.apiKey)}${t}`,{method:"GET",headers:e});if(i.status===304)return{config:this.serverConfig,etag:this.lastEtag,notModified:!0};if(!i.ok){let o=await i.text().catch(()=>"");throw new Error(`SplitLab API error ${i.status}: ${o}`)}let s=i.headers.get("etag");return{config:await i.json(),etag:s}}connectConfigStream(){if(typeof EventSource>"u")return;let e=this.config.environment!=="production"?`&env=${encodeURIComponent(this.config.environment)}`:"",t=`${this.config.baseUrl}/api/sdk/config/stream?key=${encodeURIComponent(this.config.apiKey)}${e}`;this.eventSource=new EventSource(t),this.eventSource.addEventListener("config_updated",()=>{this.refresh().catch(()=>{})}),this.eventSource.onerror=()=>{}}flushSync(){if(this.eventQueue.length===0)return;let e=this.eventQueue;this.eventQueue=[];let t=JSON.stringify({context:this.buildContext(),events:e}),i=typeof btoa=="function"?btoa(unescape(encodeURIComponent(t))):t,r=typeof btoa=="function"?JSON.stringify({_e:i}):t,o=`${this.config.ingestUrl}/ingest/batch?key=${encodeURIComponent(this.config.apiKey)}`;if(typeof globalThis.navigator?.sendBeacon=="function"){let l=new Blob([r],{type:"application/json"});try{if(globalThis.navigator.sendBeacon(o,l))return}catch{}}if(typeof fetch=="function")try{fetch(o,{method:"POST",headers:{"Content-Type":"application/json"},body:r,keepalive:!0}).catch(()=>{})}catch{}}async request(e,t,i){let s=await fetch(`${this.config.baseUrl}${t}`,{method:e,headers:{"Content-Type":"application/json","X-API-Key":this.config.apiKey},body:i?JSON.stringify(i):void 0});if(!s.ok){let r=await s.text().catch(()=>"");throw new Error(`SplitLab API error ${s.status}: ${r}`)}return s.json()}};async function L(n){let e=new TextEncoder().encode(n);if(typeof CompressionStream<"u")try{let t=new CompressionStream("gzip"),i=t.writable.getWriter();i.write(e),i.close();let s=t.readable.getReader(),r=[];for(;;){let{done:a,value:d}=await s.read();if(a)break;r.push(d)}let o=r.reduce((a,d)=>a+d.length,0),l=new Uint8Array(o),g=0;for(let a of r)l.set(a,g),g+=a.length;let y="";for(let a=0;a<l.length;a++)y+=String.fromCharCode(l[a]);return btoa(y)}catch{}return btoa(unescape(encodeURIComponent(n)))}export{x as SplitLabClient,K as hashToFloat,H as murmurhash3};
@@ -1 +1 @@
1
- "use strict";var SplitLab=(()=>{var y=Object.defineProperty;var j=Object.getOwnPropertyDescriptor;var N=Object.getOwnPropertyNames;var K=Object.prototype.hasOwnProperty;var H=(n,e)=>{for(var t in e)y(n,t,{get:e[t],enumerable:!0})},Q=(n,e,t,i)=>{if(e&&typeof e=="object"||typeof e=="function")for(let a of N(e))!K.call(n,a)&&a!==t&&y(n,a,{get:()=>e[a],enumerable:!(i=j(e,a))||i.enumerable});return n};var G=n=>Q(y({},"__esModule",{value:!0}),n);var X={};H(X,{SplitLabClient:()=>k,hashToFloat:()=>E,murmurhash3:()=>g});function g(n,e=0){let t=e>>>0,i=n.length,a=i>>2,r=3432918353,o=461845907;for(let l=0;l<a;l++){let c=n.charCodeAt(l*4)&255|(n.charCodeAt(l*4+1)&255)<<8|(n.charCodeAt(l*4+2)&255)<<16|(n.charCodeAt(l*4+3)&255)<<24;c=Math.imul(c,r),c=c<<15|c>>>17,c=Math.imul(c,o),t^=c,t=t<<13|t>>>19,t=Math.imul(t,5)+3864292196}let u=a*4,s=0;switch(i&3){case 3:s^=(n.charCodeAt(u+2)&255)<<16;case 2:s^=(n.charCodeAt(u+1)&255)<<8;case 1:s^=n.charCodeAt(u)&255,s=Math.imul(s,r),s=s<<15|s>>>17,s=Math.imul(s,o),t^=s}return t^=i,t^=t>>>16,t=Math.imul(t,2246822507),t^=t>>>13,t=Math.imul(t,3266489909),t^=t>>>16,t>>>0}function E(n,e){return g(n,e)/4294967295}function S(n,e,t={}){let i={},a={},r=n.segments||[],o=n.exclusion_groups||[],u=new Map;for(let s of o)for(let[l,c]of Object.entries(s.experiments))u.set(l,{layer_key:s.layer_key,...c});for(let s of n.experiments){if(s.targeting_rules&&!v(s.targeting_rules,t,r)){i[s.key]=null;continue}let l=u.get(s.key);if(l){let f=g(l.layer_key+":"+e)%1e4;if(f<l.bucket_start||f>=l.bucket_end){i[s.key]=null;continue}}let c=g(s.key+":"+e);if(c%1e4/100>=s.traffic_percentage){i[s.key]=null;continue}let z=s.variants.reduce((f,F)=>f+F.weight,0),B=c%z,I=0,d=null;for(let f of s.variants)if(I+=f.weight,B<I){d=f.key;break}d||(d=s.variants[s.variants.length-1].key),i[s.key]=d}for(let s of n.flags){if(s.rules&&!v(s.rules,t,r)){a[s.key]=!1;continue}let l=g(s.key+":"+e)%100;a[s.key]=l<s.rollout_percentage}return{experiments:i,flags:a}}function v(n,e,t=[]){if(!n.groups||n.groups.length===0)return!0;let i=r=>{if(!r.attribute)return!0;if(r.attribute==="$segment"){if(!Array.isArray(r.value))return!1;let u=r.value.map(String);return r.operator==="in"?u.some(s=>{let l=t.find(c=>c.id===s||c.key===s);return l?v(l.rules,e,t):!1}):r.operator==="not_in"?!u.some(s=>{let l=t.find(c=>c.id===s||c.key===s);return l?v(l.rules,e,t):!1}):!1}let o=e[r.attribute];if(o==null)return!1;switch(r.operator){case"is":return String(o)===String(r.value);case"is_not":return String(o)!==String(r.value);case"contains":return String(o).toLowerCase().includes(String(r.value).toLowerCase());case"not_contains":return!String(o).toLowerCase().includes(String(r.value).toLowerCase());case"gt":return Number(o)>Number(r.value);case"lt":return Number(o)<Number(r.value);case"gte":return Number(o)>=Number(r.value);case"lte":return Number(o)<=Number(r.value);case"in":return Array.isArray(r.value)&&r.value.map(String).includes(String(o));case"not_in":return Array.isArray(r.value)&&!r.value.map(String).includes(String(o));default:return!1}},a=r=>r.conditions.every(i);return n.match==="any"?n.groups.some(a):n.groups.every(a)}var V=[[/Edg(?:e|A|iOS)?\/(\S+)/,"Edge"],[/OPR\/(\S+)/,"Opera"],[/SamsungBrowser\/(\S+)/,"Samsung Internet"],[/UCBrowser\/(\S+)/,"UC Browser"],[/Firefox\/(\S+)/,"Firefox"],[/CriOS\/(\S+)/,"Chrome"],[/FxiOS\/(\S+)/,"Firefox"],[/Chrome\/(\S+)/,"Chrome"],[/Safari\/(\S+)/,"Safari"]],Y=[[/Windows NT (\d+\.\d+)/,"Windows"],[/Mac OS X ([\d_]+)/,"macOS"],[/Android ([\d.]+)/,"Android"],[/iPhone OS ([\d_]+)/,"iOS"],[/iPad.*OS ([\d_]+)/,"iPadOS"],[/CrOS/,"ChromeOS"],[/Linux/,"Linux"]];function T(n){if(!n)return{browser:null,browser_version:null,os:null,os_version:null,device_type:null};let e=null,t=null,i=null,a=null;if(/Safari/.test(n)&&!/Chrome|CriOS|Chromium|Edg|OPR|SamsungBrowser|UCBrowser/.test(n)){let o=n.match(/Version\/(\S+)/);e="Safari",t=o?o[1]:null}else for(let[o,u]of V){let s=n.match(o);if(s){e=u,t=s[1]||null;break}}for(let[o,u]of Y){let s=n.match(o);if(s){i=u,a=s[1]?.replace(/_/g,".")||null;break}}let r="desktop";return/Mobi|Android.*Mobile|iPhone/.test(n)?r="mobile":/iPad|Android(?!.*Mobile)|Tablet/.test(n)&&(r="tablet"),{browser:e,browser_version:t,os:i,os_version:a,device_type:r}}var h=typeof globalThis<"u"&&typeof globalThis.document<"u";function P(){if(!h)return null;let n=globalThis,e=n.navigator,t=n.location,i=n.document,a=n.screen,r=T(e?.userAgent);return{pathname:t?.pathname??null,hostname:t?.hostname??null,referrer:i?.referrer||null,search_params:t?.search||null,page_title:i?.title||null,hash:t?.hash||null,user_agent:e?.userAgent??null,browser:r.browser,browser_version:r.browser_version,os:r.os,os_version:r.os_version,device_type:r.device_type,screen_width:a?.width??null,screen_height:a?.height??null,viewport_width:n.innerWidth??null,viewport_height:n.innerHeight??null,language:e?.language??null,timezone:q()}}function q(){try{return Intl.DateTimeFormat().resolvedOptions().timeZone}catch{return null}}var J=["utm_source","utm_medium","utm_campaign","utm_term","utm_content"],x="__ot_utm";function O(){if(!h)return{};try{let i=globalThis.sessionStorage?.getItem(x);if(i)return JSON.parse(i)}catch{}let n=new URLSearchParams(globalThis.location?.search||""),e={},t=!1;for(let i of J){let a=n.get(i);a&&(e[i]=a,t=!0)}if(t)try{globalThis.sessionStorage?.setItem(x,JSON.stringify(e))}catch{}return e}var p="_sl_did",C="_sl_sid",A=63072e3,U="__ot_did";function w(n){if(!h)return null;try{let e=globalThis.document?.cookie?.match(new RegExp("(?:^|; )"+n.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")+"=([^;]*)"));return e?decodeURIComponent(e[1]):null}catch{return null}}function m(n,e,t){if(h)try{globalThis.document.cookie=`${n}=${encodeURIComponent(e)}; path=/; max-age=${t}; SameSite=Lax`}catch{}}function L(n){if(!h)return b();let e=Math.floor(n/1e3),t=w(C);if(t)return m(C,t,e),t;let i=b();return m(C,i,e),i}function $(){if(!h)return b();let n=w(p);if(n)return n;let e=null;try{let t=globalThis.localStorage;e=t?.getItem(U)||null,e&&t?.removeItem(U)}catch{}return e||(e=b()),m(p,e,A),e}function D(n){!h||w(p)===n||m(p,n,A)}var R="__ot_ref";function M(){if(!h)return null;try{let n=globalThis.sessionStorage,e=n?.getItem(R);if(e!==null)return e||null;let t=globalThis.document?.referrer||"";return n?.setItem(R,t),t||null}catch{return globalThis.document?.referrer||null}}function b(){let n=Date.now().toString(36),e=Math.random().toString(36).substring(2,10);return`${n}-${e}`}var _=typeof globalThis<"u"&&typeof globalThis.document<"u",k=class{constructor(e){this.evalResult=null;this.serverConfig=null;this.eventQueue=[];this.flushTimer=null;this.initialized=!1;this.visibilityHandler=null;this.beforeUnloadHandler=null;this.lastEtag=null;this.eventSource=null;this.deviceId=null;this.utmParams={};this.initialReferrer=null;this.pageviewCleanup=null;this.trackedExposures=new Set;this.config={apiKey:e.apiKey,baseUrl:e.baseUrl.replace(/\/$/,""),ingestUrl:(e.ingestUrl||e.baseUrl).replace(/\/$/,""),distinctId:e.distinctId??null,attributes:e.attributes||{},autoFlushInterval:e.autoFlushInterval??8e3,autoFlushSize:e.autoFlushSize??20,enableLocalEvaluation:e.enableLocalEvaluation??!0,realtimeUpdates:e.realtimeUpdates??!1,onConfigUpdate:e.onConfigUpdate??null,captureContext:e.captureContext??_,captureUtm:e.captureUtm??!0,trackSessions:e.trackSessions??!0,sessionTimeout:e.sessionTimeout??30*6e4,persistDeviceId:e.persistDeviceId??!0,trackPageviews:e.trackPageviews??!1,superProperties:e.superProperties??{},environment:e.environment??"production"},this.config.captureUtm&&(this.utmParams=O()),this.config.captureContext&&(this.initialReferrer=M()),e.bootstrap?.deviceId?(this.deviceId=e.bootstrap.deviceId,D(e.bootstrap.deviceId)):this.deviceId=$()}get effectiveDistinctId(){return this.config.distinctId||this.deviceId}get bucketingId(){return this.deviceId}async initialize(){let e=this.initialized;if(!e)if(this.config.enableLocalEvaluation)try{let{config:t,etag:i}=await this.fetchConfig();this.serverConfig=t,this.lastEtag=i,this.evalResult=S(this.serverConfig,this.bucketingId,this.config.attributes)}catch(t){this.serverConfig||(console.warn("SplitLab: config fetch failed, using safe defaults",t),this.serverConfig={experiments:[],flags:[]},this.evalResult={experiments:{},flags:{}})}else{let t={distinct_id:this.effectiveDistinctId};Object.keys(this.config.attributes).length>0&&(t.attributes=this.config.attributes);let i=await this.request("POST","/api/sdk/evaluate",t);this.evalResult={experiments:i.experiments,flags:i.flags}}this.flushTimer=setInterval(()=>{this.flush().catch(()=>{})},this.config.autoFlushInterval),this.config.realtimeUpdates&&this.config.enableLocalEvaluation&&this.connectConfigStream(),_&&(this.visibilityHandler=()=>{globalThis.document?.visibilityState==="hidden"?this.flushSync():this.config.enableLocalEvaluation&&this.refresh().catch(()=>{})},globalThis.document?.addEventListener("visibilitychange",this.visibilityHandler),this.beforeUnloadHandler=()=>{this.flushSync()},globalThis.addEventListener("beforeunload",this.beforeUnloadHandler)),this.config.trackPageviews&&_&&this.setupPageviewTracking(),this.initialized=!0,e&&this.refresh().catch(()=>{})}getVariant(e){if(!this.initialized||!this.evalResult)throw new Error("SplitLabClient not initialized. Call initialize() first.");let t=this.evalResult.experiments[e]??null;return t!==null&&!this.trackedExposures.has(e)&&(this.trackedExposures.add(e),this.track("$exposure",{experiment_key:e,variant_key:t}).catch(()=>{})),t}isFeatureEnabled(e){if(!this.initialized||!this.evalResult)throw new Error("SplitLabClient not initialized. Call initialize() first.");return this.evalResult.flags[e]??!1}hydrateFromBootstrap(e){this.serverConfig=e.serverConfig,this.lastEtag=e.etag,this.evalResult=e.evalResult,this.initialized=!0}async track(e,t){let i=this.enrichProperties(t);this.eventQueue.push({distinct_id:this.effectiveDistinctId,event_name:e,properties:i,timestamp:new Date().toISOString()}),this.eventQueue.length>=this.config.autoFlushSize&&await this.flush()}async trackPageview(e){await this.track("$pageview",e)}setSuperProperties(e){Object.assign(this.config.superProperties,e)}unsetSuperProperty(e){delete this.config.superProperties[e]}async flush(){if(this.eventQueue.length===0)return;let e=this.eventQueue;this.eventQueue=[];try{await this.request("POST","/ingest/batch",{events:e},!0)}catch{this.eventQueue=e.concat(this.eventQueue)}}async identify(e,t){let i=this.effectiveDistinctId;this.config.distinctId=e,i!==e&&await this.track("$identify",{...t,previous_distinct_id:i}),this.config.enableLocalEvaluation||(this.evalResult=null,this.initialized=!1,await this.initialize())}async refresh(){if(this.config.enableLocalEvaluation)try{let{config:e,etag:t,notModified:i}=await this.fetchConfig();if(i)return;this.serverConfig=e,this.lastEtag=t,this.evalResult=S(this.serverConfig,this.bucketingId,this.config.attributes),this.config.onConfigUpdate&&this.config.onConfigUpdate()}catch{}else{let e={distinct_id:this.effectiveDistinctId};Object.keys(this.config.attributes).length>0&&(e.attributes=this.config.attributes);let t=await this.request("POST","/api/sdk/evaluate",e);this.evalResult={experiments:t.experiments,flags:t.flags}}}getDistinctId(){return this.effectiveDistinctId}getDeviceId(){return this.deviceId}setAttributes(e){this.config.attributes={...this.config.attributes,...e}}async destroy(){await this.flush(),this.flushTimer!==null&&(clearInterval(this.flushTimer),this.flushTimer=null),this.eventSource&&(this.eventSource.close(),this.eventSource=null),this.visibilityHandler&&(globalThis.document?.removeEventListener("visibilitychange",this.visibilityHandler),this.visibilityHandler=null),this.beforeUnloadHandler&&typeof globalThis.removeEventListener=="function"&&(globalThis.removeEventListener("beforeunload",this.beforeUnloadHandler),this.beforeUnloadHandler=null),this.pageviewCleanup&&(this.pageviewCleanup(),this.pageviewCleanup=null),this.initialized=!1}enrichProperties(e){let t={};if(this.config.captureContext){let i=P();if(i){for(let[a,r]of Object.entries(i))r!==null&&r!==""&&r!==void 0&&(t[a]=r);this.initialReferrer&&(t.referrer=this.initialReferrer)}}if(this.config.captureUtm)for(let[i,a]of Object.entries(this.utmParams))a&&(t[i]=a);this.config.trackSessions&&(t.session_id=L(this.config.sessionTimeout)),this.config.persistDeviceId&&this.deviceId&&(t.device_id=this.deviceId);for(let[i,a]of Object.entries(this.config.superProperties))a!==void 0&&(t[i]=a);if(e)for(let[i,a]of Object.entries(e))a!==void 0&&(t[i]=a);return t}setupPageviewTracking(){let e=globalThis,t=e.history;if(!t?.pushState)return;let i=e.location?.href,a=t.pushState.bind(t);t.pushState=(...u)=>{a(...u),this.onUrlChange(i),i=e.location?.href};let r=t.replaceState.bind(t);t.replaceState=(...u)=>{r(...u),this.onUrlChange(i),i=e.location?.href};let o=()=>{this.onUrlChange(i),i=e.location?.href};e.addEventListener("popstate",o),this.trackPageview().catch(()=>{}),this.pageviewCleanup=()=>{t.pushState=a,t.replaceState=r,e.removeEventListener("popstate",o)}}onUrlChange(e){globalThis.location?.href!==e&&setTimeout(()=>{this.trackPageview({previous_url:e||null}).catch(()=>{})},0)}async fetchConfig(){let e={};this.lastEtag&&(e["If-None-Match"]=this.lastEtag);let t=this.config.environment!=="production"?`&env=${encodeURIComponent(this.config.environment)}`:"",i=await fetch(`${this.config.baseUrl}/api/sdk/config?key=${encodeURIComponent(this.config.apiKey)}${t}`,{method:"GET",headers:e});if(i.status===304)return{config:this.serverConfig,etag:this.lastEtag,notModified:!0};if(!i.ok){let o=await i.text().catch(()=>"");throw new Error(`SplitLab API error ${i.status}: ${o}`)}let a=i.headers.get("etag");return{config:await i.json(),etag:a}}connectConfigStream(){if(typeof EventSource>"u")return;let e=this.config.environment!=="production"?`&env=${encodeURIComponent(this.config.environment)}`:"",t=`${this.config.baseUrl}/api/sdk/config/stream?key=${encodeURIComponent(this.config.apiKey)}${e}`;this.eventSource=new EventSource(t),this.eventSource.addEventListener("config_updated",()=>{this.refresh().catch(()=>{})}),this.eventSource.onerror=()=>{}}flushSync(){if(this.eventQueue.length===0)return;let e=this.eventQueue;this.eventQueue=[];let t=JSON.stringify({events:e}),i=`${this.config.ingestUrl}/ingest/batch?key=${encodeURIComponent(this.config.apiKey)}`;if(typeof globalThis.navigator?.sendBeacon=="function"){let a=new Blob([t],{type:"application/json"});try{if(globalThis.navigator.sendBeacon(i,a))return}catch{}}if(typeof fetch=="function")try{fetch(i,{method:"POST",headers:{"Content-Type":"application/json","X-API-Key":this.config.apiKey},body:t,keepalive:!0}).catch(()=>{})}catch{}}async request(e,t,i,a){let r=a?this.config.ingestUrl:this.config.baseUrl,o=await fetch(`${r}${t}`,{method:e,headers:{"Content-Type":"application/json","X-API-Key":this.config.apiKey},body:i?JSON.stringify(i):void 0});if(!o.ok){let u=await o.text().catch(()=>"");throw new Error(`SplitLab API error ${o.status}: ${u}`)}return o.json()}};return G(X);})();
1
+ "use strict";var SplitLab=(()=>{var S=Object.defineProperty;var j=Object.getOwnPropertyDescriptor;var N=Object.getOwnPropertyNames;var H=Object.prototype.hasOwnProperty;var K=(n,e)=>{for(var t in e)S(n,t,{get:e[t],enumerable:!0})},Q=(n,e,t,i)=>{if(e&&typeof e=="object"||typeof e=="function")for(let o of N(e))!H.call(n,o)&&o!==t&&S(n,o,{get:()=>e[o],enumerable:!(i=j(e,o))||i.enumerable});return n};var J=n=>Q(S({},"__esModule",{value:!0}),n);var X={};K(X,{SplitLabClient:()=>E,hashToFloat:()=>x,murmurhash3:()=>g});function g(n,e=0){let t=e>>>0,i=n.length,o=i>>2,r=3432918353,a=461845907;for(let u=0;u<o;u++){let l=n.charCodeAt(u*4)&255|(n.charCodeAt(u*4+1)&255)<<8|(n.charCodeAt(u*4+2)&255)<<16|(n.charCodeAt(u*4+3)&255)<<24;l=Math.imul(l,r),l=l<<15|l>>>17,l=Math.imul(l,a),t^=l,t=t<<13|t>>>19,t=Math.imul(t,5)+3864292196}let c=o*4,s=0;switch(i&3){case 3:s^=(n.charCodeAt(c+2)&255)<<16;case 2:s^=(n.charCodeAt(c+1)&255)<<8;case 1:s^=n.charCodeAt(c)&255,s=Math.imul(s,r),s=s<<15|s>>>17,s=Math.imul(s,a),t^=s}return t^=i,t^=t>>>16,t=Math.imul(t,2246822507),t^=t>>>13,t=Math.imul(t,3266489909),t^=t>>>16,t>>>0}function x(n,e){return g(n,e)/4294967295}function C(n,e,t={}){let i={},o={},r=n.segments||[],a=n.exclusion_groups||[],c=new Map;for(let s of a)for(let[u,l]of Object.entries(s.experiments))c.set(u,{layer_key:s.layer_key,...l});for(let s of n.experiments){if(s.targeting_rules&&!v(s.targeting_rules,t,r)){i[s.key]=null;continue}let u=c.get(s.key);if(u){let f=g(u.layer_key+":"+e)%1e4;if(f<u.bucket_start||f>=u.bucket_end){i[s.key]=null;continue}}let l=g(s.key+":"+e);if(l%1e4/100>=s.traffic_percentage){i[s.key]=null;continue}let d=s.variants.reduce((f,F)=>f+F.weight,0),B=l%d,I=0,p=null;for(let f of s.variants)if(I+=f.weight,B<I){p=f.key;break}p||(p=s.variants[s.variants.length-1].key),i[s.key]=p}for(let s of n.flags){if(s.rules&&!v(s.rules,t,r)){o[s.key]=!1;continue}let u=g(s.key+":"+e)%100;o[s.key]=u<s.rollout_percentage}return{experiments:i,flags:o}}function v(n,e,t=[]){if(!n.groups||n.groups.length===0)return!0;let i=r=>{if(!r.attribute)return!0;if(r.attribute==="$segment"){if(!Array.isArray(r.value))return!1;let c=r.value.map(String);return r.operator==="in"?c.some(s=>{let u=t.find(l=>l.id===s||l.key===s);return u?v(u.rules,e,t):!1}):r.operator==="not_in"?!c.some(s=>{let u=t.find(l=>l.id===s||l.key===s);return u?v(u.rules,e,t):!1}):!1}let a=e[r.attribute];if(a==null)return!1;switch(r.operator){case"is":return String(a)===String(r.value);case"is_not":return String(a)!==String(r.value);case"contains":return String(a).toLowerCase().includes(String(r.value).toLowerCase());case"not_contains":return!String(a).toLowerCase().includes(String(r.value).toLowerCase());case"gt":return Number(a)>Number(r.value);case"lt":return Number(a)<Number(r.value);case"gte":return Number(a)>=Number(r.value);case"lte":return Number(a)<=Number(r.value);case"in":return Array.isArray(r.value)&&r.value.map(String).includes(String(a));case"not_in":return Array.isArray(r.value)&&!r.value.map(String).includes(String(a));default:return!1}},o=r=>r.conditions.every(i);return n.match==="any"?n.groups.some(o):n.groups.every(o)}var G=[[/Edg(?:e|A|iOS)?\/(\S+)/,"Edge"],[/OPR\/(\S+)/,"Opera"],[/SamsungBrowser\/(\S+)/,"Samsung Internet"],[/UCBrowser\/(\S+)/,"UC Browser"],[/Firefox\/(\S+)/,"Firefox"],[/CriOS\/(\S+)/,"Chrome"],[/FxiOS\/(\S+)/,"Firefox"],[/Chrome\/(\S+)/,"Chrome"],[/Safari\/(\S+)/,"Safari"]],V=[[/Windows NT (\d+\.\d+)/,"Windows"],[/Mac OS X ([\d_]+)/,"macOS"],[/Android ([\d.]+)/,"Android"],[/iPhone OS ([\d_]+)/,"iOS"],[/iPad.*OS ([\d_]+)/,"iPadOS"],[/CrOS/,"ChromeOS"],[/Linux/,"Linux"]];function R(n){if(!n)return{browser:null,browser_version:null,os:null,os_version:null,device_type:null};let e=null,t=null,i=null,o=null;if(/Safari/.test(n)&&!/Chrome|CriOS|Chromium|Edg|OPR|SamsungBrowser|UCBrowser/.test(n)){let a=n.match(/Version\/(\S+)/);e="Safari",t=a?a[1]:null}else for(let[a,c]of G){let s=n.match(a);if(s){e=c,t=s[1]||null;break}}for(let[a,c]of V){let s=n.match(a);if(s){i=c,o=s[1]?.replace(/_/g,".")||null;break}}let r="desktop";return/Mobi|Android.*Mobile|iPhone/.test(n)?r="mobile":/iPad|Android(?!.*Mobile)|Tablet/.test(n)&&(r="tablet"),{browser:e,browser_version:t,os:i,os_version:o,device_type:r}}var h=typeof globalThis<"u"&&typeof globalThis.document<"u";function O(){if(!h)return null;let n=globalThis,e=n.navigator,t=n.location,i=n.document,o=n.screen,r=R(e?.userAgent);return{pathname:t?.pathname??null,hostname:t?.hostname??null,referrer:i?.referrer||null,search_params:t?.search||null,page_title:i?.title||null,hash:t?.hash||null,user_agent:e?.userAgent??null,browser:r.browser,browser_version:r.browser_version,os:r.os,os_version:r.os_version,device_type:r.device_type,screen_width:o?.width??null,screen_height:o?.height??null,viewport_width:n.innerWidth??null,viewport_height:n.innerHeight??null,language:e?.language??null,timezone:Y()}}function Y(){try{return Intl.DateTimeFormat().resolvedOptions().timeZone}catch{return null}}var W=["utm_source","utm_medium","utm_campaign","utm_term","utm_content"],U="__ot_utm";function A(){if(!h)return{};try{let i=globalThis.sessionStorage?.getItem(U);if(i)return JSON.parse(i)}catch{}let n=new URLSearchParams(globalThis.location?.search||""),e={},t=!1;for(let i of W){let o=n.get(i);o&&(e[i]=o,t=!0)}if(t)try{globalThis.sessionStorage?.setItem(U,JSON.stringify(e))}catch{}return e}var m="_sl_did",w="_sl_sid",L=63072e3,T="__ot_did";function _(n){if(!h)return null;try{let e=globalThis.document?.cookie?.match(new RegExp("(?:^|; )"+n.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")+"=([^;]*)"));return e?decodeURIComponent(e[1]):null}catch{return null}}function y(n,e,t){if(h)try{globalThis.document.cookie=`${n}=${encodeURIComponent(e)}; path=/; max-age=${t}; SameSite=Lax`}catch{}}function $(n){if(!h)return b();let e=Math.floor(n/1e3),t=_(w);if(t)return y(w,t,e),t;let i=b();return y(w,i,e),i}function D(){if(!h)return b();let n=_(m);if(n)return n;let e=null;try{let t=globalThis.localStorage;e=t?.getItem(T)||null,e&&t?.removeItem(T)}catch{}return e||(e=b()),y(m,e,L),e}function M(n){!h||_(m)===n||y(m,n,L)}var P="__ot_ref";function z(){if(!h)return null;try{let n=globalThis.sessionStorage,e=n?.getItem(P);if(e!==null)return e||null;let t=globalThis.document?.referrer||"";return n?.setItem(P,t),t||null}catch{return globalThis.document?.referrer||null}}function b(){let n=Date.now().toString(36),e=Math.random().toString(36).substring(2,10);return`${n}-${e}`}var k=typeof globalThis<"u"&&typeof globalThis.document<"u",E=class{constructor(e){this.evalResult=null;this.serverConfig=null;this.eventQueue=[];this.flushTimer=null;this.initialized=!1;this.visibilityHandler=null;this.beforeUnloadHandler=null;this.lastEtag=null;this.eventSource=null;this.deviceId=null;this.utmParams={};this.initialReferrer=null;this.pageviewCleanup=null;this.trackedExposures=new Set;this.config={apiKey:e.apiKey,baseUrl:e.baseUrl.replace(/\/$/,""),ingestUrl:(e.ingestUrl||e.baseUrl).replace(/\/$/,""),distinctId:e.distinctId??null,attributes:e.attributes||{},autoFlushInterval:e.autoFlushInterval??8e3,autoFlushSize:e.autoFlushSize??20,enableLocalEvaluation:e.enableLocalEvaluation??!0,realtimeUpdates:e.realtimeUpdates??!1,onConfigUpdate:e.onConfigUpdate??null,captureContext:e.captureContext??k,captureUtm:e.captureUtm??!0,trackSessions:e.trackSessions??!0,sessionTimeout:e.sessionTimeout??30*6e4,persistDeviceId:e.persistDeviceId??!0,trackPageviews:e.trackPageviews??!1,superProperties:e.superProperties??{},environment:e.environment??"production"},this.config.captureUtm&&(this.utmParams=A()),this.config.captureContext&&(this.initialReferrer=z()),e.bootstrap?.deviceId?(this.deviceId=e.bootstrap.deviceId,M(e.bootstrap.deviceId)):this.deviceId=D()}get effectiveDistinctId(){return this.config.distinctId||this.deviceId}get bucketingId(){return this.deviceId}async initialize(){let e=this.initialized;if(!e)if(this.config.enableLocalEvaluation)try{let{config:t,etag:i}=await this.fetchConfig();this.serverConfig=t,this.lastEtag=i,this.evalResult=C(this.serverConfig,this.bucketingId,this.config.attributes)}catch(t){this.serverConfig||(console.warn("SplitLab: config fetch failed, using safe defaults",t),this.serverConfig={experiments:[],flags:[]},this.evalResult={experiments:{},flags:{}})}else{let t={distinct_id:this.effectiveDistinctId};Object.keys(this.config.attributes).length>0&&(t.attributes=this.config.attributes);let i=await this.request("POST","/api/sdk/evaluate",t);this.evalResult={experiments:i.experiments,flags:i.flags}}this.flushTimer=setInterval(()=>{this.flush().catch(()=>{})},this.config.autoFlushInterval),this.config.realtimeUpdates&&this.config.enableLocalEvaluation&&this.connectConfigStream(),k&&(this.visibilityHandler=()=>{globalThis.document?.visibilityState==="hidden"?this.flushSync():this.config.enableLocalEvaluation&&this.refresh().catch(()=>{})},globalThis.document?.addEventListener("visibilitychange",this.visibilityHandler),this.beforeUnloadHandler=()=>{this.flushSync()},globalThis.addEventListener("beforeunload",this.beforeUnloadHandler)),this.config.trackPageviews&&k&&this.setupPageviewTracking(),this.initialized=!0,e&&this.refresh().catch(()=>{})}getVariant(e){if(!this.initialized||!this.evalResult)throw new Error("SplitLabClient not initialized. Call initialize() first.");let t=this.evalResult.experiments[e]??null;return t!==null&&!this.trackedExposures.has(e)&&(this.trackedExposures.add(e),this.track("$exposure",{experiment_key:e,variant_key:t}).catch(()=>{})),t}isFeatureEnabled(e){if(!this.initialized||!this.evalResult)throw new Error("SplitLabClient not initialized. Call initialize() first.");return this.evalResult.flags[e]??!1}hydrateFromBootstrap(e){this.serverConfig=e.serverConfig,this.lastEtag=e.etag,this.evalResult=e.evalResult,this.initialized=!0}async track(e,t){this.eventQueue.push({distinct_id:this.effectiveDistinctId,event_name:e,properties:this.mergeUserProperties(t),timestamp:new Date().toISOString()}),this.eventQueue.length>=this.config.autoFlushSize&&await this.flush()}async trackPageview(e){await this.track("$pageview",e)}setSuperProperties(e){Object.assign(this.config.superProperties,e)}unsetSuperProperty(e){delete this.config.superProperties[e]}async flush(){if(this.eventQueue.length===0)return;let e=this.eventQueue;this.eventQueue=[];try{let t=JSON.stringify({context:this.buildContext(),events:e}),i=await q(t),o=JSON.stringify({_e:i}),r=`${this.config.ingestUrl}/ingest/batch?key=${encodeURIComponent(this.config.apiKey)}`,a=await fetch(r,{method:"POST",headers:{"Content-Type":"application/json"},body:o});if(!a.ok)throw new Error(`${a.status}`)}catch{this.eventQueue=e.concat(this.eventQueue)}}async identify(e,t){let i=this.effectiveDistinctId;this.config.distinctId=e,i!==e&&await this.track("$identify",{...t,previous_distinct_id:i}),this.config.enableLocalEvaluation||(this.evalResult=null,this.initialized=!1,await this.initialize())}async refresh(){if(this.config.enableLocalEvaluation)try{let{config:e,etag:t,notModified:i}=await this.fetchConfig();if(i)return;this.serverConfig=e,this.lastEtag=t,this.evalResult=C(this.serverConfig,this.bucketingId,this.config.attributes),this.config.onConfigUpdate&&this.config.onConfigUpdate()}catch{}else{let e={distinct_id:this.effectiveDistinctId};Object.keys(this.config.attributes).length>0&&(e.attributes=this.config.attributes);let t=await this.request("POST","/api/sdk/evaluate",e);this.evalResult={experiments:t.experiments,flags:t.flags}}}getDistinctId(){return this.effectiveDistinctId}getDeviceId(){return this.deviceId}setAttributes(e){this.config.attributes={...this.config.attributes,...e}}async destroy(){await this.flush(),this.flushTimer!==null&&(clearInterval(this.flushTimer),this.flushTimer=null),this.eventSource&&(this.eventSource.close(),this.eventSource=null),this.visibilityHandler&&(globalThis.document?.removeEventListener("visibilitychange",this.visibilityHandler),this.visibilityHandler=null),this.beforeUnloadHandler&&typeof globalThis.removeEventListener=="function"&&(globalThis.removeEventListener("beforeunload",this.beforeUnloadHandler),this.beforeUnloadHandler=null),this.pageviewCleanup&&(this.pageviewCleanup(),this.pageviewCleanup=null),this.initialized=!1}buildContext(){let e={};if(this.config.captureContext){let t=O();if(t){for(let[i,o]of Object.entries(t))o!==null&&o!==""&&o!==void 0&&(e[i]=o);this.initialReferrer&&(e.referrer=this.initialReferrer)}}if(this.config.captureUtm)for(let[t,i]of Object.entries(this.utmParams))i&&(e[t]=i);this.config.trackSessions&&(e.session_id=$(this.config.sessionTimeout)),this.config.persistDeviceId&&this.deviceId&&(e.device_id=this.deviceId);for(let[t,i]of Object.entries(this.config.superProperties))i!==void 0&&(e[t]=i);return this.remapCustomDimensions(e)}remapCustomDimensions(e){if(!this.serverConfig?.custom_dimensions?.length)return e;let t={...e};for(let i of this.serverConfig.custom_dimensions)i.property_key in t&&(t[`dim_${i.index}`]=t[i.property_key],delete t[i.property_key]);return t}mergeUserProperties(e){if(!e)return;let t={};for(let[i,o]of Object.entries(e))o!==void 0&&(t[i]=o);if(Object.keys(t).length!==0)return this.remapCustomDimensions(t)}setupPageviewTracking(){let e=globalThis,t=e.history;if(!t?.pushState)return;let i=e.location?.href,o=t.pushState.bind(t);t.pushState=(...c)=>{o(...c),this.onUrlChange(i),i=e.location?.href};let r=t.replaceState.bind(t);t.replaceState=(...c)=>{r(...c),this.onUrlChange(i),i=e.location?.href};let a=()=>{this.onUrlChange(i),i=e.location?.href};e.addEventListener("popstate",a),this.trackPageview().catch(()=>{}),this.pageviewCleanup=()=>{t.pushState=o,t.replaceState=r,e.removeEventListener("popstate",a)}}onUrlChange(e){globalThis.location?.href!==e&&setTimeout(()=>{this.trackPageview({previous_url:e||null}).catch(()=>{})},0)}async fetchConfig(){let e={};this.lastEtag&&(e["If-None-Match"]=this.lastEtag);let t=this.config.environment!=="production"?`&env=${encodeURIComponent(this.config.environment)}`:"",i=await fetch(`${this.config.baseUrl}/api/sdk/config?key=${encodeURIComponent(this.config.apiKey)}${t}`,{method:"GET",headers:e});if(i.status===304)return{config:this.serverConfig,etag:this.lastEtag,notModified:!0};if(!i.ok){let a=await i.text().catch(()=>"");throw new Error(`SplitLab API error ${i.status}: ${a}`)}let o=i.headers.get("etag");return{config:await i.json(),etag:o}}connectConfigStream(){if(typeof EventSource>"u")return;let e=this.config.environment!=="production"?`&env=${encodeURIComponent(this.config.environment)}`:"",t=`${this.config.baseUrl}/api/sdk/config/stream?key=${encodeURIComponent(this.config.apiKey)}${e}`;this.eventSource=new EventSource(t),this.eventSource.addEventListener("config_updated",()=>{this.refresh().catch(()=>{})}),this.eventSource.onerror=()=>{}}flushSync(){if(this.eventQueue.length===0)return;let e=this.eventQueue;this.eventQueue=[];let t=JSON.stringify({context:this.buildContext(),events:e}),i=typeof btoa=="function"?btoa(unescape(encodeURIComponent(t))):t,r=typeof btoa=="function"?JSON.stringify({_e:i}):t,a=`${this.config.ingestUrl}/ingest/batch?key=${encodeURIComponent(this.config.apiKey)}`;if(typeof globalThis.navigator?.sendBeacon=="function"){let c=new Blob([r],{type:"application/json"});try{if(globalThis.navigator.sendBeacon(a,c))return}catch{}}if(typeof fetch=="function")try{fetch(a,{method:"POST",headers:{"Content-Type":"application/json"},body:r,keepalive:!0}).catch(()=>{})}catch{}}async request(e,t,i){let o=await fetch(`${this.config.baseUrl}${t}`,{method:e,headers:{"Content-Type":"application/json","X-API-Key":this.config.apiKey},body:i?JSON.stringify(i):void 0});if(!o.ok){let r=await o.text().catch(()=>"");throw new Error(`SplitLab API error ${o.status}: ${r}`)}return o.json()}};async function q(n){let e=new TextEncoder().encode(n);if(typeof CompressionStream<"u")try{let t=new CompressionStream("gzip"),i=t.writable.getWriter();i.write(e),i.close();let o=t.readable.getReader(),r=[];for(;;){let{done:l,value:d}=await o.read();if(l)break;r.push(d)}let a=r.reduce((l,d)=>l+d.length,0),c=new Uint8Array(a),s=0;for(let l of r)c.set(l,s),s+=l.length;let u="";for(let l=0;l<c.length;l++)u+=String.fromCharCode(c[l]);return btoa(u)}catch{}return btoa(unescape(encodeURIComponent(n)))}return J(X);})();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@splitlab/js-client",
3
- "version": "0.6.0",
3
+ "version": "0.8.0",
4
4
  "description": "Lightweight JavaScript client SDK for SplitLab A/B testing and analytics",
5
5
  "license": "MIT",
6
6
  "publishConfig": {
@@ -30,7 +30,7 @@
30
30
  "dev": "tsup --watch"
31
31
  },
32
32
  "dependencies": {
33
- "@splitlab/core": "^0.3.2"
33
+ "@splitlab/core": "^0.4.0"
34
34
  },
35
35
  "devDependencies": {
36
36
  "tsup": "^8.0.0",