@schematichq/schematic-js 1.2.9 → 1.2.10

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.
@@ -1,3 +1,3 @@
1
- "use strict";(()=>{var re=Object.create;var Q=Object.defineProperty;var se=Object.getOwnPropertyDescriptor;var ie=Object.getOwnPropertyNames;var ae=Object.getPrototypeOf,oe=Object.prototype.hasOwnProperty;var le=(r,e)=>()=>(e||r((e={exports:{}}).exports,e),e.exports);var ce=(r,e,t,i)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of ie(e))!oe.call(r,s)&&s!==t&&Q(r,s,{get:()=>e[s],enumerable:!(i=se(e,s))||i.enumerable});return r};var ue=(r,e,t)=>(t=r!=null?re(ae(r)):{},ce(e||!r||!r.__esModule?Q(t,"default",{value:r,enumerable:!0}):t,r));var K=le(z=>{(function(r){var e=(function(t){var i=typeof globalThis<"u"&&globalThis||typeof r<"u"&&r||typeof global<"u"&&global||{},s={searchParams:"URLSearchParams"in i,iterable:"Symbol"in i&&"iterator"in Symbol,blob:"FileReader"in i&&"Blob"in i&&(function(){try{return new Blob,!0}catch{return!1}})(),formData:"FormData"in i,arrayBuffer:"ArrayBuffer"in i};function l(n){return n&&DataView.prototype.isPrototypeOf(n)}if(s.arrayBuffer)var o=["[object Int8Array]","[object Uint8Array]","[object Uint8ClampedArray]","[object Int16Array]","[object Uint16Array]","[object Int32Array]","[object Uint32Array]","[object Float32Array]","[object Float64Array]"],c=ArrayBuffer.isView||function(n){return n&&o.indexOf(Object.prototype.toString.call(n))>-1};function d(n){if(typeof n!="string"&&(n=String(n)),/[^a-z0-9\-#$%&'*+.^_`|~!]/i.test(n)||n==="")throw new TypeError('Invalid character in header field name: "'+n+'"');return n.toLowerCase()}function g(n){return typeof n!="string"&&(n=String(n)),n}function E(n){var a={next:function(){var u=n.shift();return{done:u===void 0,value:u}}};return s.iterable&&(a[Symbol.iterator]=function(){return a}),a}function h(n){this.map={},n instanceof h?n.forEach(function(a,u){this.append(u,a)},this):Array.isArray(n)?n.forEach(function(a){if(a.length!=2)throw new TypeError("Headers constructor: expected name/value pair to be length 2, found"+a.length);this.append(a[0],a[1])},this):n&&Object.getOwnPropertyNames(n).forEach(function(a){this.append(a,n[a])},this)}h.prototype.append=function(n,a){n=d(n),a=g(a);var u=this.map[n];this.map[n]=u?u+", "+a:a},h.prototype.delete=function(n){delete this.map[d(n)]},h.prototype.get=function(n){return n=d(n),this.has(n)?this.map[n]:null},h.prototype.has=function(n){return this.map.hasOwnProperty(d(n))},h.prototype.set=function(n,a){this.map[d(n)]=g(a)},h.prototype.forEach=function(n,a){for(var u in this.map)this.map.hasOwnProperty(u)&&n.call(a,this.map[u],u,this)},h.prototype.keys=function(){var n=[];return this.forEach(function(a,u){n.push(u)}),E(n)},h.prototype.values=function(){var n=[];return this.forEach(function(a){n.push(a)}),E(n)},h.prototype.entries=function(){var n=[];return this.forEach(function(a,u){n.push([u,a])}),E(n)},s.iterable&&(h.prototype[Symbol.iterator]=h.prototype.entries);function b(n){if(!n._noBody){if(n.bodyUsed)return Promise.reject(new TypeError("Already read"));n.bodyUsed=!0}}function m(n){return new Promise(function(a,u){n.onload=function(){a(n.result)},n.onerror=function(){u(n.error)}})}function F(n){var a=new FileReader,u=m(a);return a.readAsArrayBuffer(n),u}function $(n){var a=new FileReader,u=m(a),p=/charset=([A-Za-z0-9_-]+)/.exec(n.type),y=p?p[1]:"utf-8";return a.readAsText(n,y),u}function Y(n){for(var a=new Uint8Array(n),u=new Array(a.length),p=0;p<a.length;p++)u[p]=String.fromCharCode(a[p]);return u.join("")}function J(n){if(n.slice)return n.slice(0);var a=new Uint8Array(n.byteLength);return a.set(new Uint8Array(n)),a.buffer}function q(){return this.bodyUsed=!1,this._initBody=function(n){this.bodyUsed=this.bodyUsed,this._bodyInit=n,n?typeof n=="string"?this._bodyText=n:s.blob&&Blob.prototype.isPrototypeOf(n)?this._bodyBlob=n:s.formData&&FormData.prototype.isPrototypeOf(n)?this._bodyFormData=n:s.searchParams&&URLSearchParams.prototype.isPrototypeOf(n)?this._bodyText=n.toString():s.arrayBuffer&&s.blob&&l(n)?(this._bodyArrayBuffer=J(n.buffer),this._bodyInit=new Blob([this._bodyArrayBuffer])):s.arrayBuffer&&(ArrayBuffer.prototype.isPrototypeOf(n)||c(n))?this._bodyArrayBuffer=J(n):this._bodyText=n=Object.prototype.toString.call(n):(this._noBody=!0,this._bodyText=""),this.headers.get("content-type")||(typeof n=="string"?this.headers.set("content-type","text/plain;charset=UTF-8"):this._bodyBlob&&this._bodyBlob.type?this.headers.set("content-type",this._bodyBlob.type):s.searchParams&&URLSearchParams.prototype.isPrototypeOf(n)&&this.headers.set("content-type","application/x-www-form-urlencoded;charset=UTF-8"))},s.blob&&(this.blob=function(){var n=b(this);if(n)return n;if(this._bodyBlob)return Promise.resolve(this._bodyBlob);if(this._bodyArrayBuffer)return Promise.resolve(new Blob([this._bodyArrayBuffer]));if(this._bodyFormData)throw new Error("could not read FormData body as blob");return Promise.resolve(new Blob([this._bodyText]))}),this.arrayBuffer=function(){if(this._bodyArrayBuffer){var n=b(this);return n||(ArrayBuffer.isView(this._bodyArrayBuffer)?Promise.resolve(this._bodyArrayBuffer.buffer.slice(this._bodyArrayBuffer.byteOffset,this._bodyArrayBuffer.byteOffset+this._bodyArrayBuffer.byteLength)):Promise.resolve(this._bodyArrayBuffer))}else{if(s.blob)return this.blob().then(F);throw new Error("could not read as ArrayBuffer")}},this.text=function(){var n=b(this);if(n)return n;if(this._bodyBlob)return $(this._bodyBlob);if(this._bodyArrayBuffer)return Promise.resolve(Y(this._bodyArrayBuffer));if(this._bodyFormData)throw new Error("could not read FormData body as text");return Promise.resolve(this._bodyText)},s.formData&&(this.formData=function(){return this.text().then(ee)}),this.json=function(){return this.text().then(JSON.parse)},this}var Z=["CONNECT","DELETE","GET","HEAD","OPTIONS","PATCH","POST","PUT","TRACE"];function j(n){var a=n.toUpperCase();return Z.indexOf(a)>-1?a:n}function S(n,a){if(!(this instanceof S))throw new TypeError('Please use the "new" operator, this DOM object constructor cannot be called as a function.');a=a||{};var u=a.body;if(n instanceof S){if(n.bodyUsed)throw new TypeError("Already read");this.url=n.url,this.credentials=n.credentials,a.headers||(this.headers=new h(n.headers)),this.method=n.method,this.mode=n.mode,this.signal=n.signal,!u&&n._bodyInit!=null&&(u=n._bodyInit,n.bodyUsed=!0)}else this.url=String(n);if(this.credentials=a.credentials||this.credentials||"same-origin",(a.headers||!this.headers)&&(this.headers=new h(a.headers)),this.method=j(a.method||this.method||"GET"),this.mode=a.mode||this.mode||null,this.signal=a.signal||this.signal||(function(){if("AbortController"in i){var f=new AbortController;return f.signal}})(),this.referrer=null,(this.method==="GET"||this.method==="HEAD")&&u)throw new TypeError("Body not allowed for GET or HEAD requests");if(this._initBody(u),(this.method==="GET"||this.method==="HEAD")&&(a.cache==="no-store"||a.cache==="no-cache")){var p=/([?&])_=[^&]*/;if(p.test(this.url))this.url=this.url.replace(p,"$1_="+new Date().getTime());else{var y=/\?/;this.url+=(y.test(this.url)?"&":"?")+"_="+new Date().getTime()}}}S.prototype.clone=function(){return new S(this,{body:this._bodyInit})};function ee(n){var a=new FormData;return n.trim().split("&").forEach(function(u){if(u){var p=u.split("="),y=p.shift().replace(/\+/g," "),f=p.join("=").replace(/\+/g," ");a.append(decodeURIComponent(y),decodeURIComponent(f))}}),a}function te(n){var a=new h,u=n.replace(/\r?\n[\t ]+/g," ");return u.split("\r").map(function(p){return p.indexOf(`
2
- `)===0?p.substr(1,p.length):p}).forEach(function(p){var y=p.split(":"),f=y.shift().trim();if(f){var _=y.join(":").trim();try{a.append(f,_)}catch(P){console.warn("Response "+P.message)}}}),a}q.call(S.prototype);function w(n,a){if(!(this instanceof w))throw new TypeError('Please use the "new" operator, this DOM object constructor cannot be called as a function.');if(a||(a={}),this.type="default",this.status=a.status===void 0?200:a.status,this.status<200||this.status>599)throw new RangeError("Failed to construct 'Response': The status provided (0) is outside the range [200, 599].");this.ok=this.status>=200&&this.status<300,this.statusText=a.statusText===void 0?"":""+a.statusText,this.headers=new h(a.headers),this.url=a.url||"",this._initBody(n)}q.call(w.prototype),w.prototype.clone=function(){return new w(this._bodyInit,{status:this.status,statusText:this.statusText,headers:new h(this.headers),url:this.url})},w.error=function(){var n=new w(null,{status:200,statusText:""});return n.ok=!1,n.status=0,n.type="error",n};var ne=[301,302,303,307,308];w.redirect=function(n,a){if(ne.indexOf(a)===-1)throw new RangeError("Invalid status code");return new w(null,{status:a,headers:{location:n}})},t.DOMException=i.DOMException;try{new t.DOMException}catch{t.DOMException=function(a,u){this.message=a,this.name=u;var p=Error(a);this.stack=p.stack},t.DOMException.prototype=Object.create(Error.prototype),t.DOMException.prototype.constructor=t.DOMException}function A(n,a){return new Promise(function(u,p){var y=new S(n,a);if(y.signal&&y.signal.aborted)return p(new t.DOMException("Aborted","AbortError"));var f=new XMLHttpRequest;function _(){f.abort()}f.onload=function(){var v={statusText:f.statusText,headers:te(f.getAllResponseHeaders()||"")};y.url.indexOf("file://")===0&&(f.status<200||f.status>599)?v.status=200:v.status=f.status,v.url="responseURL"in f?f.responseURL:v.headers.get("X-Request-URL");var x="response"in f?f.response:f.responseText;setTimeout(function(){u(new w(x,v))},0)},f.onerror=function(){setTimeout(function(){p(new TypeError("Network request failed"))},0)},f.ontimeout=function(){setTimeout(function(){p(new TypeError("Network request timed out"))},0)},f.onabort=function(){setTimeout(function(){p(new t.DOMException("Aborted","AbortError"))},0)};function P(v){try{return v===""&&i.location.href?i.location.href:v}catch{return v}}if(f.open(y.method,P(y.url),!0),y.credentials==="include"?f.withCredentials=!0:y.credentials==="omit"&&(f.withCredentials=!1),"responseType"in f&&(s.blob?f.responseType="blob":s.arrayBuffer&&(f.responseType="arraybuffer")),a&&typeof a.headers=="object"&&!(a.headers instanceof h||i.Headers&&a.headers instanceof i.Headers)){var W=[];Object.getOwnPropertyNames(a.headers).forEach(function(v){W.push(d(v)),f.setRequestHeader(v,g(a.headers[v]))}),y.headers.forEach(function(v,x){W.indexOf(x)===-1&&f.setRequestHeader(x,v)})}else y.headers.forEach(function(v,x){f.setRequestHeader(x,v)});y.signal&&(y.signal.addEventListener("abort",_),f.onreadystatechange=function(){f.readyState===4&&y.signal.removeEventListener("abort",_)}),f.send(typeof y._bodyInit>"u"?null:y._bodyInit)})}return A.polyfill=!0,i.fetch||(i.fetch=A,i.Headers=h,i.Request=S,i.Response=w),t.Headers=h,t.Request=S,t.Response=w,t.fetch=A,t})({})})(typeof self<"u"?self:z)});var k=[];for(let r=0;r<256;++r)k.push((r+256).toString(16).slice(1));function H(r,e=0){return(k[r[e+0]]+k[r[e+1]]+k[r[e+2]]+k[r[e+3]]+"-"+k[r[e+4]]+k[r[e+5]]+"-"+k[r[e+6]]+k[r[e+7]]+"-"+k[r[e+8]]+k[r[e+9]]+"-"+k[r[e+10]]+k[r[e+11]]+k[r[e+12]]+k[r[e+13]]+k[r[e+14]]+k[r[e+15]]).toLowerCase()}var L,de=new Uint8Array(16);function B(){if(!L){if(typeof crypto>"u"||!crypto.getRandomValues)throw new Error("crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported");L=crypto.getRandomValues.bind(crypto)}return L(de)}var fe=typeof crypto<"u"&&crypto.randomUUID&&crypto.randomUUID.bind(crypto),N={randomUUID:fe};function he(r,e,t){r=r||{};let i=r.random??r.rng?.()??B();if(i.length<16)throw new Error("Random bytes length must be >= 16");if(i[6]=i[6]&15|64,i[8]=i[8]&63|128,e){if(t=t||0,t<0||t+16>e.length)throw new RangeError(`UUID byte range ${t}:${t+15} is out of buffer bounds`);for(let s=0;s<16;++s)e[t+s]=i[s];return e}return H(i)}function ge(r,e,t){return N.randomUUID&&!e&&!r?N.randomUUID():he(r,e,t)}var C=ge;var st=ue(K());function T(r){return pe(r,!1)}function pe(r,e){return r==null?r:{companyId:r.company_id==null?void 0:r.company_id,error:r.error==null?void 0:r.error,featureAllocation:r.feature_allocation==null?void 0:r.feature_allocation,featureUsage:r.feature_usage==null?void 0:r.feature_usage,featureUsageEvent:r.feature_usage_event==null?void 0:r.feature_usage_event,featureUsagePeriod:r.feature_usage_period==null?void 0:r.feature_usage_period,featureUsageResetAt:r.feature_usage_reset_at==null?void 0:new Date(r.feature_usage_reset_at),flag:r.flag,flagId:r.flag_id==null?void 0:r.flag_id,reason:r.reason,ruleId:r.rule_id==null?void 0:r.rule_id,ruleType:r.rule_type==null?void 0:r.rule_type,userId:r.user_id==null?void 0:r.user_id,value:r.value}}function M(r){return ye(r,!1)}function ye(r,e=!1){return r==null?r:{company_id:r.companyId,error:r.error,flag_id:r.flagId,flag_key:r.flagKey,reason:r.reason,req_company:r.reqCompany,req_user:r.reqUser,rule_id:r.ruleId,user_id:r.userId,value:r.value}}function U(r){return be(r,!1)}function be(r,e){return r==null?r:{data:T(r.data),params:r.params}}function X(r){return ve(r,!1)}function ve(r,e){return r==null?r:{flags:r.flags.map(T)}}function V(r){return ke(r,!1)}function ke(r,e){return r==null?r:{data:X(r.data),params:r.params}}var D=r=>{let{companyId:e,error:t,featureAllocation:i,featureUsage:s,featureUsageEvent:l,featureUsagePeriod:o,featureUsageResetAt:c,flag:d,flagId:g,reason:E,ruleId:h,ruleType:b,userId:m,value:F}=T(r);return{featureUsageExceeded:!F&&(b=="company_override_usage_exceeded"||b=="plan_entitlement_usage_exceeded"),companyId:e??void 0,error:t??void 0,featureAllocation:i??void 0,featureUsage:s??void 0,featureUsageEvent:l===null?void 0:l,featureUsagePeriod:o??void 0,featureUsageResetAt:c??void 0,flag:d,flagId:g??void 0,reason:E,ruleId:h??void 0,ruleType:b??void 0,userId:m??void 0,value:F}};function R(r){let e=Object.keys(r).reduce((t,i)=>{let l=Object.keys(r[i]||{}).sort().reduce((o,c)=>(o[c]=r[i][c],o),{});return t[i]=l,t},{});return JSON.stringify(e)}var O="1.2.9";var G="schematicId";var I=class{additionalHeaders={};apiKey;apiUrl="https://api.schematichq.com";conn=null;context={};debugEnabled=!1;offlineEnabled=!1;eventQueue;contextDependentEventQueue;eventUrl="https://c.schematichq.com";flagCheckListeners={};flagValueListeners={};isPending=!0;isPendingListeners=new Set;storage;useWebSocket=!1;checks={};featureUsageEventMap={};webSocketUrl="wss://api.schematichq.com";webSocketConnectionTimeout=1e4;webSocketReconnect=!0;webSocketMaxReconnectAttempts=7;webSocketInitialRetryDelay=1e3;webSocketMaxRetryDelay=3e4;wsReconnectAttempts=0;wsReconnectTimer=null;wsIntentionalDisconnect=!1;maxEventQueueSize=100;maxEventRetries=5;eventRetryInitialDelay=1e3;eventRetryMaxDelay=3e4;retryTimer=null;flagValueDefaults={};flagCheckDefaults={};constructor(e,t){if(this.apiKey=e,this.eventQueue=[],this.contextDependentEventQueue=[],this.useWebSocket=t?.useWebSocket??!1,this.debugEnabled=t?.debug??!1,this.offlineEnabled=t?.offline??!1,typeof window<"u"&&typeof window.location<"u"){let i=new URLSearchParams(window.location.search),s=i.get("schematic_debug");s!==null&&(s===""||s==="true"||s==="1")&&(this.debugEnabled=!0);let l=i.get("schematic_offline");l!==null&&(l===""||l==="true"||l==="1")&&(this.offlineEnabled=!0,this.debugEnabled=!0)}this.offlineEnabled&&t?.debug!==!1&&(this.debugEnabled=!0),this.offlineEnabled&&this.setIsPending(!1),this.additionalHeaders={"X-Schematic-Client-Version":`schematic-js@${O}`,...t?.additionalHeaders??{}},t?.storage?this.storage=t.storage:typeof localStorage<"u"&&(this.storage=localStorage),t?.apiUrl!==void 0&&(this.apiUrl=t.apiUrl),t?.eventUrl!==void 0&&(this.eventUrl=t.eventUrl),t?.webSocketUrl!==void 0&&(this.webSocketUrl=t.webSocketUrl),t?.webSocketConnectionTimeout!==void 0&&(this.webSocketConnectionTimeout=t.webSocketConnectionTimeout),t?.webSocketReconnect!==void 0&&(this.webSocketReconnect=t.webSocketReconnect),t?.webSocketMaxReconnectAttempts!==void 0&&(this.webSocketMaxReconnectAttempts=t.webSocketMaxReconnectAttempts),t?.webSocketInitialRetryDelay!==void 0&&(this.webSocketInitialRetryDelay=t.webSocketInitialRetryDelay),t?.webSocketMaxRetryDelay!==void 0&&(this.webSocketMaxRetryDelay=t.webSocketMaxRetryDelay),t?.maxEventQueueSize!==void 0&&(this.maxEventQueueSize=t.maxEventQueueSize),t?.maxEventRetries!==void 0&&(this.maxEventRetries=t.maxEventRetries),t?.eventRetryInitialDelay!==void 0&&(this.eventRetryInitialDelay=t.eventRetryInitialDelay),t?.eventRetryMaxDelay!==void 0&&(this.eventRetryMaxDelay=t.eventRetryMaxDelay),t?.flagValueDefaults!==void 0&&(this.flagValueDefaults=t.flagValueDefaults),t?.flagCheckDefaults!==void 0&&(this.flagCheckDefaults=t.flagCheckDefaults),typeof window<"u"&&window?.addEventListener&&(window.addEventListener("beforeunload",()=>{this.flushEventQueue(),this.flushContextDependentEventQueue()}),this.useWebSocket&&(window.addEventListener("offline",()=>{this.debug("Browser went offline, closing WebSocket connection"),this.handleNetworkOffline()}),window.addEventListener("online",()=>{this.debug("Browser came online, attempting to reconnect WebSocket"),this.handleNetworkOnline()}))),this.offlineEnabled?this.debug("Initialized with offline mode enabled - no network requests will be made"):this.debugEnabled&&this.debug("Initialized with debug mode enabled")}resolveFallbackValue(e,t){return t!==void 0?t:e in this.flagValueDefaults?this.flagValueDefaults[e]:!1}resolveFallbackCheckFlagReturn(e,t,i="Fallback value used",s){if(t!==void 0)return{flag:e,value:t,reason:i,error:s};if(e in this.flagCheckDefaults){let l=this.flagCheckDefaults[e];return{...l,flag:e,reason:s!==void 0?i:l.reason,error:s}}return e in this.flagValueDefaults?{flag:e,value:this.flagValueDefaults[e],reason:i,error:s}:{flag:e,value:!1,reason:i,error:s}}async checkFlag(e){let{fallback:t,key:i}=e,s=e.context||this.context,l=R(s);if(this.debug(`checkFlag: ${i}`,{context:s,fallback:t}),this.isOffline()){let o=this.resolveFallbackCheckFlagReturn(i,t,"Offline mode - using initialization defaults");return this.debug(`checkFlag offline result: ${i}`,{value:o.value,offlineMode:!0}),o.value}if(!this.useWebSocket){let o=`${this.apiUrl}/flags/${i}/check`;return fetch(o,{method:"POST",headers:{...this.additionalHeaders??{},"Content-Type":"application/json;charset=UTF-8","X-Schematic-Api-Key":this.apiKey},body:JSON.stringify(s)}).then(c=>{if(!c.ok)throw new Error("Network response was not ok");return c.json()}).then(c=>{let d=U(c);this.debug(`checkFlag result: ${i}`,d);let g=D(d.data);return typeof g.featureUsageEvent=="string"&&this.updateFeatureUsageEventMap(g),this.submitFlagCheckEvent(i,g,s),g.value}).catch(c=>{console.error("There was a problem with the fetch operation:",c);let d=this.resolveFallbackCheckFlagReturn(i,t,"API request failed",c instanceof Error?c.message:String(c));return this.submitFlagCheckEvent(i,d,s),d.value})}try{let o=this.checks[l];if(this.conn!==null&&typeof o<"u"&&typeof o[i]<"u")return this.debug(`checkFlag cached result: ${i}`,o[i]),o[i].value;if(this.isOffline())return this.resolveFallbackValue(i,t);try{await this.setContext(s)}catch(E){return console.error("WebSocket connection failed, falling back to REST:",E),this.fallbackToRest(i,s,t)}let d=(this.checks[l]??{})[i],g=d?.value??this.resolveFallbackValue(i,t);return this.debug(`checkFlag WebSocket result: ${i}`,typeof d<"u"?d:{value:g,fallbackUsed:!0}),typeof d<"u"&&this.submitFlagCheckEvent(i,d,s),g}catch(o){console.error("Unexpected error in checkFlag:",o);let c=this.resolveFallbackCheckFlagReturn(i,t,"Unexpected error in flag check",o instanceof Error?o.message:String(o));return this.submitFlagCheckEvent(i,c,s),c.value}}debug(e,...t){this.debugEnabled&&console.log(`[Schematic] ${e}`,...t)}isOffline(){return this.offlineEnabled}submitFlagCheckEvent(e,t,i){let s={flagKey:e,value:t.value,reason:t.reason,flagId:t.flagId,ruleId:t.ruleId,companyId:t.companyId,userId:t.userId,error:t.error,reqCompany:i.company,reqUser:i.user};return this.debug("submitting flag check event:",s),this.handleEvent("flag_check",M(s))}async fallbackToRest(e,t,i){if(this.isOffline()){let s=this.resolveFallbackValue(e,i);return this.debug(`fallbackToRest offline result: ${e}`,{value:s,offlineMode:!0}),s}try{let s=`${this.apiUrl}/flags/${e}/check`,l=await fetch(s,{method:"POST",headers:{...this.additionalHeaders??{},"Content-Type":"application/json;charset=UTF-8","X-Schematic-Api-Key":this.apiKey},body:JSON.stringify(t)});if(!l.ok)throw new Error("Network response was not ok");let o=await l.json(),c=U(o);this.debug(`fallbackToRest result: ${e}`,c);let d=D(c.data);return typeof d.featureUsageEvent=="string"&&this.updateFeatureUsageEventMap(d),this.submitFlagCheckEvent(e,d,t),d.value}catch(s){console.error("REST API call failed, using fallback value:",s);let l=this.resolveFallbackCheckFlagReturn(e,i,"API request failed (fallback)",s instanceof Error?s.message:String(s));return this.submitFlagCheckEvent(e,l,t),l.value}}checkFlags=async e=>{if(e=e||this.context,this.debug("checkFlags",{context:e}),this.isOffline())return this.debug("checkFlags offline result: returning empty object"),{};let t=`${this.apiUrl}/flags/check`,i=JSON.stringify(e);return fetch(t,{method:"POST",headers:{...this.additionalHeaders??{},"Content-Type":"application/json;charset=UTF-8","X-Schematic-Api-Key":this.apiKey},body:i}).then(s=>{if(!s.ok)throw new Error("Network response was not ok");return s.json()}).then(s=>{let l=V(s);return this.debug("checkFlags result:",l),(l?.data?.flags??[]).reduce((o,c)=>(o[c.flag]=c.value,o),{})}).catch(s=>(console.error("There was a problem with the fetch operation:",s),{}))};identify=e=>{this.debug("identify:",e);try{this.setContext({company:e.company?.keys,user:e.keys})}catch(t){console.error("Error setting context:",t)}return this.handleEvent("identify",e)};setContext=async e=>{if(this.isOffline()||!this.useWebSocket)return this.context=e,this.flushContextDependentEventQueue(),this.setIsPending(!1),Promise.resolve();try{this.setIsPending(!0),this.conn||(this.wsReconnectTimer!==null&&(this.debug("Cancelling scheduled reconnection, connecting immediately"),clearTimeout(this.wsReconnectTimer),this.wsReconnectTimer=null),this.conn=this.wsConnect());let t=await this.conn;await this.wsSendMessage(t,e)}catch(t){throw console.error("Failed to establish WebSocket connection:",t),t}};track=e=>{let{company:t,user:i,event:s,traits:l,quantity:o=1}=e;if(!this.hasContext(t,i)){this.debug(`track: queuing event "${s}" until context is available`);let d={api_key:this.apiKey,body:{company:t,event:s,traits:l??{},user:i,quantity:o},sent_at:new Date().toISOString(),tracker_event_id:C(),tracker_user_id:this.getAnonymousId(),type:"track"};return this.contextDependentEventQueue.push(d),Promise.resolve()}let c={company:t??this.context.company,event:s,traits:l??{},user:i??this.context.user,quantity:o};return this.debug("track:",c),s in this.featureUsageEventMap&&this.optimisticallyUpdateFeatureUsage(s,o),this.handleEvent("track",c)};optimisticallyUpdateFeatureUsage=(e,t=1)=>{let i=this.featureUsageEventMap[e];i!=null&&(this.debug(`Optimistically updating feature usage for event: ${e}`,{quantity:t}),Object.entries(i).forEach(([s,l])=>{if(l===void 0)return;let o={...l};if(typeof o.featureUsage=="number"){if(o.featureUsage+=t,typeof o.featureAllocation=="number"){let d=o.featureUsageExceeded===!0,g=o.featureUsage>=o.featureAllocation;g!==d&&(o.featureUsageExceeded=g,g&&(o.value=!1),this.debug(`Usage limit status changed for flag: ${s}`,{was:d?"exceeded":"within limits",now:g?"exceeded":"within limits",featureUsage:o.featureUsage,featureAllocation:o.featureAllocation,value:o.value}))}this.featureUsageEventMap[e]!==void 0&&(this.featureUsageEventMap[e][s]=o);let c=R(this.context);this.checks[c]!==void 0&&this.checks[c]!==null&&(this.checks[c][s]=o),this.notifyFlagCheckListeners(s,o),this.notifyFlagValueListeners(s,o.value)}}))};hasContext=(e,t)=>{let i=e!=null&&Object.keys(e).length>0||t!=null&&Object.keys(t).length>0,s=this.context.company!==void 0&&this.context.company!==null&&Object.keys(this.context.company).length>0||this.context.user!==void 0&&this.context.user!==null&&Object.keys(this.context.user).length>0;return i||s};flushContextDependentEventQueue=()=>{for(this.debug(`flushing ${this.contextDependentEventQueue.length} context-dependent events`);this.contextDependentEventQueue.length>0;){let e=this.contextDependentEventQueue.shift();if(e)if(e.type==="track"&&typeof e.body=="object"&&e.body!==null){let t=e.body,i={...t,company:t.company??this.context.company,user:t.user??this.context.user},s={...e,body:i,sent_at:new Date().toISOString()};this.sendEvent(s)}else this.sendEvent(e)}};startRetryTimer=()=>{this.retryTimer===null&&(this.retryTimer=setInterval(()=>{this.flushEventQueue().catch(e=>{this.debug("Error in retry timer flush:",e)}),this.eventQueue.length===0&&this.stopRetryTimer()},5e3),this.debug("Started retry timer"))};stopRetryTimer=()=>{this.retryTimer!==null&&(clearInterval(this.retryTimer),this.retryTimer=null,this.debug("Stopped retry timer"))};flushEventQueue=async()=>{if(this.eventQueue.length===0)return;let e=Date.now(),t=[],i=[];for(let s of this.eventQueue)s.next_retry_at===void 0||s.next_retry_at<=e?t.push(s):i.push(s);if(t.length===0){this.debug(`No events ready for retry yet (${i.length} still in backoff)`);return}this.debug(`Flushing event queue: ${t.length} ready, ${i.length} waiting`),this.eventQueue=i;for(let s of t)try{await this.sendEvent(s),this.debug("Queued event sent successfully:",s.type)}catch(l){this.debug("Failed to send queued event:",l)}};getAnonymousId=()=>{if(!this.storage)return C();let e=this.storage.getItem(G);if(typeof e<"u")return e;let t=C();return this.storage.setItem(G,t),t};handleEvent=(e,t)=>{let i={api_key:this.apiKey,body:t,sent_at:new Date().toISOString(),tracker_event_id:C(),tracker_user_id:this.getAnonymousId(),type:e};return typeof document<"u"&&document?.hidden?this.storeEvent(i):this.sendEvent(i)};sendEvent=async e=>{let t=`${this.eventUrl}/e`,i=JSON.stringify(e);if(this.debug("sending event:",{url:t,event:e}),this.isOffline())return this.debug("event not sent (offline mode):",{event:e}),Promise.resolve();try{let s=await fetch(t,{method:"POST",headers:{...this.additionalHeaders??{},"Content-Type":"application/json;charset=UTF-8"},body:i});if(!s.ok)throw new Error(`HTTP ${s.status}: ${s.statusText}`);this.debug("event sent:",{status:s.status,statusText:s.statusText})}catch(s){let l=(e.retry_count??0)+1;if(l<=this.maxEventRetries){this.debug(`Event failed to send (attempt ${l}/${this.maxEventRetries}), queueing for retry:`,s);let o=this.eventRetryInitialDelay*Math.pow(2,l-1),c=Math.min(o,this.eventRetryMaxDelay),d=Date.now()+c,g={...e,retry_count:l,next_retry_at:d};this.eventQueue.length<this.maxEventQueueSize?(this.eventQueue.push(g),this.debug(`Event queued for retry in ${c}ms (${this.eventQueue.length}/${this.maxEventQueueSize})`)):(this.debug(`Event queue full (${this.maxEventQueueSize}), dropping oldest event`),this.eventQueue.shift(),this.eventQueue.push(g)),this.startRetryTimer()}else this.debug(`Event failed permanently after ${this.maxEventRetries} attempts, dropping:`,s)}return Promise.resolve()};storeEvent=e=>(this.eventQueue.push(e),Promise.resolve());cleanup=async()=>{if(this.isOffline())return this.debug("cleanup: skipped (offline mode)"),Promise.resolve();if(this.wsIntentionalDisconnect=!0,this.wsReconnectTimer!==null&&(clearTimeout(this.wsReconnectTimer),this.wsReconnectTimer=null),this.stopRetryTimer(),this.conn)try{(await this.conn).close()}catch(e){console.error("Error during cleanup:",e)}finally{this.conn=null}};calculateReconnectDelay=()=>{let e=this.webSocketInitialRetryDelay*Math.pow(2,this.wsReconnectAttempts),t=Math.min(e,this.webSocketMaxRetryDelay),i=Math.random()*t*.5,s=t+i;return this.debug(`Reconnect delay calculated: ${s.toFixed(0)}ms (attempt ${this.wsReconnectAttempts+1}/${this.webSocketMaxReconnectAttempts})`),s};handleNetworkOffline=async()=>{if(this.conn!==null){try{(await this.conn).close()}catch(e){this.debug("Error closing connection on offline:",e)}this.conn=null}this.wsReconnectTimer!==null&&(clearTimeout(this.wsReconnectTimer),this.wsReconnectTimer=null)};handleNetworkOnline=()=>{this.debug("Network online, attempting reconnection and flushing queued events"),this.wsReconnectAttempts=0,this.wsReconnectTimer!==null&&(clearTimeout(this.wsReconnectTimer),this.wsReconnectTimer=null),this.flushEventQueue().catch(e=>{this.debug("Error flushing event queue on network online:",e)}),this.attemptReconnect()};attemptReconnect=()=>{if(this.wsReconnectAttempts>=this.webSocketMaxReconnectAttempts){this.debug(`Maximum reconnection attempts (${this.webSocketMaxReconnectAttempts}) reached, giving up`);return}this.wsReconnectTimer!==null&&clearTimeout(this.wsReconnectTimer);let e=this.calculateReconnectDelay();this.debug(`Scheduling reconnection attempt ${this.wsReconnectAttempts+1}/${this.webSocketMaxReconnectAttempts} in ${e.toFixed(0)}ms`),this.wsReconnectTimer=setTimeout(async()=>{this.wsReconnectTimer=null,this.wsReconnectAttempts++,this.debug(`Attempting to reconnect (attempt ${this.wsReconnectAttempts}/${this.webSocketMaxReconnectAttempts})`);try{this.conn=this.wsConnect();let t=await this.conn;this.debug("Reconnection context check:",{hasCompany:this.context.company!==void 0,hasUser:this.context.user!==void 0,context:this.context}),this.context.company!==void 0||this.context.user!==void 0?(this.debug("Reconnected, force re-sending context"),await this.wsSendContextAfterReconnection(t,this.context)):this.debug("No context to re-send after reconnection - websocket ready for new context"),this.flushEventQueue().catch(i=>{this.debug("Error flushing event queue after websocket reconnection:",i)}),this.debug("Reconnection successful")}catch(t){this.debug("Reconnection attempt failed:",t)}},e)};wsConnect=()=>this.isOffline()?(this.debug("wsConnect: skipped (offline mode)"),Promise.reject(new Error("WebSocket connection skipped in offline mode"))):new Promise((e,t)=>{let i=`${this.webSocketUrl}/flags/bootstrap?apiKey=${this.apiKey}`;this.debug("connecting to WebSocket:",i);let s=new WebSocket(i),l=null,o=!1;l=setTimeout(()=>{o||(this.debug(`WebSocket connection timeout after ${this.webSocketConnectionTimeout}ms`),s.close(),t(new Error("WebSocket connection timeout")))},this.webSocketConnectionTimeout),s.onopen=()=>{o=!0,l!==null&&clearTimeout(l),this.wsReconnectAttempts=0,this.wsIntentionalDisconnect=!1,this.debug("WebSocket connection opened"),e(s)},s.onerror=c=>{o=!0,l!==null&&clearTimeout(l),this.debug("WebSocket connection error:",c),t(c)},s.onclose=()=>{o=!0,l!==null&&clearTimeout(l),this.debug("WebSocket connection closed"),this.conn=null,!this.wsIntentionalDisconnect&&this.webSocketReconnect&&this.attemptReconnect()}});wsSendContextAfterReconnection=(e,t)=>this.isOffline()?(this.debug("wsSendContextAfterReconnection: skipped (offline mode)"),this.setIsPending(!1),Promise.resolve()):new Promise(i=>{this.debug("WebSocket force sending context after reconnection:",t),this.context=t;let s=()=>{let l=!1,o=g=>{let E=JSON.parse(g.data);this.debug("WebSocket message received after reconnection:",E),R(t)in this.checks||(this.checks[R(t)]={}),(E.flags??[]).forEach(h=>{let b=D(h),m=R(t);this.checks[m]===void 0&&(this.checks[m]={}),this.checks[m][b.flag]=b}),this.useWebSocket=!0,e.removeEventListener("message",o),l||(l=!0,i(this.setIsPending(!1)))};e.addEventListener("message",o);let c=this.additionalHeaders["X-Schematic-Client-Version"]??`schematic-js@${O}`,d={apiKey:this.apiKey,clientVersion:c,data:t};this.debug("WebSocket sending forced message after reconnection:",d),e.send(JSON.stringify(d))};e.readyState===WebSocket.OPEN?(this.debug("WebSocket already open, sending forced message after reconnection"),s()):e.addEventListener("open",()=>{this.debug("WebSocket opened, sending forced message after reconnection"),s()})});wsSendMessage=(e,t)=>this.isOffline()?(this.debug("wsSendMessage: skipped (offline mode)"),this.setIsPending(!1),Promise.resolve()):new Promise((i,s)=>{if(R(t)==R(this.context))return this.debug("WebSocket context unchanged, skipping update"),i(this.setIsPending(!1));this.debug("WebSocket context updated:",t),this.context=t;let l=()=>{let o=!1,c=E=>{let h=JSON.parse(E.data);this.debug("WebSocket message received:",h),R(t)in this.checks||(this.checks[R(t)]={}),(h.flags??[]).forEach(b=>{let m=D(b),F=R(t);this.checks[F]===void 0&&(this.checks[F]={}),this.checks[F][m.flag]=m,this.debug("WebSocket flag update:",{flag:m.flag,value:m.value,flagCheck:m}),typeof m.featureUsageEvent=="string"&&this.updateFeatureUsageEventMap(m),(this.flagCheckListeners[b.flag]?.size>0||this.flagValueListeners[b.flag]?.size>0)&&this.submitFlagCheckEvent(m.flag,m,t),this.notifyFlagCheckListeners(b.flag,m),this.notifyFlagValueListeners(b.flag,m.value)}),this.flushContextDependentEventQueue(),this.setIsPending(!1),o||(o=!0,i())};e.addEventListener("message",c);let d=this.additionalHeaders["X-Schematic-Client-Version"]??`schematic-js@${O}`,g={apiKey:this.apiKey,clientVersion:d,data:t};this.debug("WebSocket sending message:",g),e.send(JSON.stringify(g))};e.readyState===WebSocket.OPEN?(this.debug("WebSocket already open, sending message"),l()):e.readyState===WebSocket.CONNECTING?(this.debug("WebSocket connecting, waiting for open to send message"),e.addEventListener("open",l)):(this.debug("WebSocket is closed, cannot send message"),s("WebSocket is not open or connecting"))});getIsPending=()=>this.isPending;addIsPendingListener=e=>(this.isPendingListeners.add(e),()=>{this.isPendingListeners.delete(e)});setIsPending=e=>{this.isPending=e,this.isPendingListeners.forEach(t=>Ee(t,e))};getFlagCheck=e=>{let t=R(this.context);return(this.checks[t]??{})[e]};getFlagValue=e=>this.getFlagCheck(e)?.value;addFlagValueListener=(e,t)=>(e in this.flagValueListeners||(this.flagValueListeners[e]=new Set),this.flagValueListeners[e].add(t),()=>{this.flagValueListeners[e].delete(t)});addFlagCheckListener=(e,t)=>(e in this.flagCheckListeners||(this.flagCheckListeners[e]=new Set),this.flagCheckListeners[e].add(t),()=>{this.flagCheckListeners[e].delete(t)});notifyFlagCheckListeners=(e,t)=>{let i=this.flagCheckListeners?.[e]??[];i.size>0&&this.debug(`Notifying ${i.size} flag check listeners for ${e}`,t),typeof t.featureUsageEvent=="string"&&this.updateFeatureUsageEventMap(t),i.forEach(s=>Re(s,t))};updateFeatureUsageEventMap=e=>{if(typeof e.featureUsageEvent!="string")return;let t=e.featureUsageEvent;(this.featureUsageEventMap[t]===void 0||this.featureUsageEventMap[t]===null)&&(this.featureUsageEventMap[t]={}),this.featureUsageEventMap[t]!==void 0&&(this.featureUsageEventMap[t][e.flag]=e),this.debug(`Updated featureUsageEventMap for event: ${t}, flag: ${e.flag}`,e)};notifyFlagValueListeners=(e,t)=>{let i=this.flagValueListeners?.[e]??[];i.size>0&&this.debug(`Notifying ${i.size} flag value listeners for ${e}`,{value:t}),i.forEach(s=>we(s,t))}},Ee=(r,e)=>{r.length>0?r(e):r()},Re=(r,e)=>{r.length>0?r(e):r()},we=(r,e)=>{r.length>0?r(e):r()};window.Schematic=I;})();
1
+ "use strict";(()=>{var re=Object.create;var Q=Object.defineProperty;var se=Object.getOwnPropertyDescriptor;var ie=Object.getOwnPropertyNames;var ae=Object.getPrototypeOf,oe=Object.prototype.hasOwnProperty;var le=(s,e)=>()=>(e||s((e={exports:{}}).exports,e),e.exports);var ce=(s,e,t,i)=>{if(e&&typeof e=="object"||typeof e=="function")for(let r of ie(e))!oe.call(s,r)&&r!==t&&Q(s,r,{get:()=>e[r],enumerable:!(i=se(e,r))||i.enumerable});return s};var ue=(s,e,t)=>(t=s!=null?re(ae(s)):{},ce(e||!s||!s.__esModule?Q(t,"default",{value:s,enumerable:!0}):t,s));var K=le(z=>{(function(s){var e=(function(t){var i=typeof globalThis<"u"&&globalThis||typeof s<"u"&&s||typeof global<"u"&&global||{},r={searchParams:"URLSearchParams"in i,iterable:"Symbol"in i&&"iterator"in Symbol,blob:"FileReader"in i&&"Blob"in i&&(function(){try{return new Blob,!0}catch{return!1}})(),formData:"FormData"in i,arrayBuffer:"ArrayBuffer"in i};function l(n){return n&&DataView.prototype.isPrototypeOf(n)}if(r.arrayBuffer)var o=["[object Int8Array]","[object Uint8Array]","[object Uint8ClampedArray]","[object Int16Array]","[object Uint16Array]","[object Int32Array]","[object Uint32Array]","[object Float32Array]","[object Float64Array]"],c=ArrayBuffer.isView||function(n){return n&&o.indexOf(Object.prototype.toString.call(n))>-1};function d(n){if(typeof n!="string"&&(n=String(n)),/[^a-z0-9\-#$%&'*+.^_`|~!]/i.test(n)||n==="")throw new TypeError('Invalid character in header field name: "'+n+'"');return n.toLowerCase()}function p(n){return typeof n!="string"&&(n=String(n)),n}function E(n){var a={next:function(){var u=n.shift();return{done:u===void 0,value:u}}};return r.iterable&&(a[Symbol.iterator]=function(){return a}),a}function g(n){this.map={},n instanceof g?n.forEach(function(a,u){this.append(u,a)},this):Array.isArray(n)?n.forEach(function(a){if(a.length!=2)throw new TypeError("Headers constructor: expected name/value pair to be length 2, found"+a.length);this.append(a[0],a[1])},this):n&&Object.getOwnPropertyNames(n).forEach(function(a){this.append(a,n[a])},this)}g.prototype.append=function(n,a){n=d(n),a=p(a);var u=this.map[n];this.map[n]=u?u+", "+a:a},g.prototype.delete=function(n){delete this.map[d(n)]},g.prototype.get=function(n){return n=d(n),this.has(n)?this.map[n]:null},g.prototype.has=function(n){return this.map.hasOwnProperty(d(n))},g.prototype.set=function(n,a){this.map[d(n)]=p(a)},g.prototype.forEach=function(n,a){for(var u in this.map)this.map.hasOwnProperty(u)&&n.call(a,this.map[u],u,this)},g.prototype.keys=function(){var n=[];return this.forEach(function(a,u){n.push(u)}),E(n)},g.prototype.values=function(){var n=[];return this.forEach(function(a){n.push(a)}),E(n)},g.prototype.entries=function(){var n=[];return this.forEach(function(a,u){n.push([u,a])}),E(n)},r.iterable&&(g.prototype[Symbol.iterator]=g.prototype.entries);function k(n){if(!n._noBody){if(n.bodyUsed)return Promise.reject(new TypeError("Already read"));n.bodyUsed=!0}}function x(n){return new Promise(function(a,u){n.onload=function(){a(n.result)},n.onerror=function(){u(n.error)}})}function T(n){var a=new FileReader,u=x(a);return a.readAsArrayBuffer(n),u}function V(n){var a=new FileReader,u=x(a),h=/charset=([A-Za-z0-9_-]+)/.exec(n.type),y=h?h[1]:"utf-8";return a.readAsText(n,y),u}function Y(n){for(var a=new Uint8Array(n),u=new Array(a.length),h=0;h<a.length;h++)u[h]=String.fromCharCode(a[h]);return u.join("")}function W(n){if(n.slice)return n.slice(0);var a=new Uint8Array(n.byteLength);return a.set(new Uint8Array(n)),a.buffer}function J(){return this.bodyUsed=!1,this._initBody=function(n){this.bodyUsed=this.bodyUsed,this._bodyInit=n,n?typeof n=="string"?this._bodyText=n:r.blob&&Blob.prototype.isPrototypeOf(n)?this._bodyBlob=n:r.formData&&FormData.prototype.isPrototypeOf(n)?this._bodyFormData=n:r.searchParams&&URLSearchParams.prototype.isPrototypeOf(n)?this._bodyText=n.toString():r.arrayBuffer&&r.blob&&l(n)?(this._bodyArrayBuffer=W(n.buffer),this._bodyInit=new Blob([this._bodyArrayBuffer])):r.arrayBuffer&&(ArrayBuffer.prototype.isPrototypeOf(n)||c(n))?this._bodyArrayBuffer=W(n):this._bodyText=n=Object.prototype.toString.call(n):(this._noBody=!0,this._bodyText=""),this.headers.get("content-type")||(typeof n=="string"?this.headers.set("content-type","text/plain;charset=UTF-8"):this._bodyBlob&&this._bodyBlob.type?this.headers.set("content-type",this._bodyBlob.type):r.searchParams&&URLSearchParams.prototype.isPrototypeOf(n)&&this.headers.set("content-type","application/x-www-form-urlencoded;charset=UTF-8"))},r.blob&&(this.blob=function(){var n=k(this);if(n)return n;if(this._bodyBlob)return Promise.resolve(this._bodyBlob);if(this._bodyArrayBuffer)return Promise.resolve(new Blob([this._bodyArrayBuffer]));if(this._bodyFormData)throw new Error("could not read FormData body as blob");return Promise.resolve(new Blob([this._bodyText]))}),this.arrayBuffer=function(){if(this._bodyArrayBuffer){var n=k(this);return n||(ArrayBuffer.isView(this._bodyArrayBuffer)?Promise.resolve(this._bodyArrayBuffer.buffer.slice(this._bodyArrayBuffer.byteOffset,this._bodyArrayBuffer.byteOffset+this._bodyArrayBuffer.byteLength)):Promise.resolve(this._bodyArrayBuffer))}else{if(r.blob)return this.blob().then(T);throw new Error("could not read as ArrayBuffer")}},this.text=function(){var n=k(this);if(n)return n;if(this._bodyBlob)return V(this._bodyBlob);if(this._bodyArrayBuffer)return Promise.resolve(Y(this._bodyArrayBuffer));if(this._bodyFormData)throw new Error("could not read FormData body as text");return Promise.resolve(this._bodyText)},r.formData&&(this.formData=function(){return this.text().then(ee)}),this.json=function(){return this.text().then(JSON.parse)},this}var Z=["CONNECT","DELETE","GET","HEAD","OPTIONS","PATCH","POST","PUT","TRACE"];function j(n){var a=n.toUpperCase();return Z.indexOf(a)>-1?a:n}function w(n,a){if(!(this instanceof w))throw new TypeError('Please use the "new" operator, this DOM object constructor cannot be called as a function.');a=a||{};var u=a.body;if(n instanceof w){if(n.bodyUsed)throw new TypeError("Already read");this.url=n.url,this.credentials=n.credentials,a.headers||(this.headers=new g(n.headers)),this.method=n.method,this.mode=n.mode,this.signal=n.signal,!u&&n._bodyInit!=null&&(u=n._bodyInit,n.bodyUsed=!0)}else this.url=String(n);if(this.credentials=a.credentials||this.credentials||"same-origin",(a.headers||!this.headers)&&(this.headers=new g(a.headers)),this.method=j(a.method||this.method||"GET"),this.mode=a.mode||this.mode||null,this.signal=a.signal||this.signal||(function(){if("AbortController"in i){var f=new AbortController;return f.signal}})(),this.referrer=null,(this.method==="GET"||this.method==="HEAD")&&u)throw new TypeError("Body not allowed for GET or HEAD requests");if(this._initBody(u),(this.method==="GET"||this.method==="HEAD")&&(a.cache==="no-store"||a.cache==="no-cache")){var h=/([?&])_=[^&]*/;if(h.test(this.url))this.url=this.url.replace(h,"$1_="+new Date().getTime());else{var y=/\?/;this.url+=(y.test(this.url)?"&":"?")+"_="+new Date().getTime()}}}w.prototype.clone=function(){return new w(this,{body:this._bodyInit})};function ee(n){var a=new FormData;return n.trim().split("&").forEach(function(u){if(u){var h=u.split("="),y=h.shift().replace(/\+/g," "),f=h.join("=").replace(/\+/g," ");a.append(decodeURIComponent(y),decodeURIComponent(f))}}),a}function te(n){var a=new g,u=n.replace(/\r?\n[\t ]+/g," ");return u.split("\r").map(function(h){return h.indexOf(`
2
+ `)===0?h.substr(1,h.length):h}).forEach(function(h){var y=h.split(":"),f=y.shift().trim();if(f){var D=y.join(":").trim();try{a.append(f,D)}catch(A){console.warn("Response "+A.message)}}}),a}J.call(w.prototype);function v(n,a){if(!(this instanceof v))throw new TypeError('Please use the "new" operator, this DOM object constructor cannot be called as a function.');if(a||(a={}),this.type="default",this.status=a.status===void 0?200:a.status,this.status<200||this.status>599)throw new RangeError("Failed to construct 'Response': The status provided (0) is outside the range [200, 599].");this.ok=this.status>=200&&this.status<300,this.statusText=a.statusText===void 0?"":""+a.statusText,this.headers=new g(a.headers),this.url=a.url||"",this._initBody(n)}J.call(v.prototype),v.prototype.clone=function(){return new v(this._bodyInit,{status:this.status,statusText:this.statusText,headers:new g(this.headers),url:this.url})},v.error=function(){var n=new v(null,{status:200,statusText:""});return n.ok=!1,n.status=0,n.type="error",n};var ne=[301,302,303,307,308];v.redirect=function(n,a){if(ne.indexOf(a)===-1)throw new RangeError("Invalid status code");return new v(null,{status:a,headers:{location:n}})},t.DOMException=i.DOMException;try{new t.DOMException}catch{t.DOMException=function(a,u){this.message=a,this.name=u;var h=Error(a);this.stack=h.stack},t.DOMException.prototype=Object.create(Error.prototype),t.DOMException.prototype.constructor=t.DOMException}function I(n,a){return new Promise(function(u,h){var y=new w(n,a);if(y.signal&&y.signal.aborted)return h(new t.DOMException("Aborted","AbortError"));var f=new XMLHttpRequest;function D(){f.abort()}f.onload=function(){var m={statusText:f.statusText,headers:te(f.getAllResponseHeaders()||"")};y.url.indexOf("file://")===0&&(f.status<200||f.status>599)?m.status=200:m.status=f.status,m.url="responseURL"in f?f.responseURL:m.headers.get("X-Request-URL");var F="response"in f?f.response:f.responseText;setTimeout(function(){u(new v(F,m))},0)},f.onerror=function(){setTimeout(function(){h(new TypeError("Network request failed"))},0)},f.ontimeout=function(){setTimeout(function(){h(new TypeError("Network request timed out"))},0)},f.onabort=function(){setTimeout(function(){h(new t.DOMException("Aborted","AbortError"))},0)};function A(m){try{return m===""&&i.location.href?i.location.href:m}catch{return m}}if(f.open(y.method,A(y.url),!0),y.credentials==="include"?f.withCredentials=!0:y.credentials==="omit"&&(f.withCredentials=!1),"responseType"in f&&(r.blob?f.responseType="blob":r.arrayBuffer&&(f.responseType="arraybuffer")),a&&typeof a.headers=="object"&&!(a.headers instanceof g||i.Headers&&a.headers instanceof i.Headers)){var q=[];Object.getOwnPropertyNames(a.headers).forEach(function(m){q.push(d(m)),f.setRequestHeader(m,p(a.headers[m]))}),y.headers.forEach(function(m,F){q.indexOf(F)===-1&&f.setRequestHeader(F,m)})}else y.headers.forEach(function(m,F){f.setRequestHeader(F,m)});y.signal&&(y.signal.addEventListener("abort",D),f.onreadystatechange=function(){f.readyState===4&&y.signal.removeEventListener("abort",D)}),f.send(typeof y._bodyInit>"u"?null:y._bodyInit)})}return I.polyfill=!0,i.fetch||(i.fetch=I,i.Headers=g,i.Request=w,i.Response=v),t.Headers=g,t.Request=w,t.Response=v,t.fetch=I,t})({})})(typeof self<"u"?self:z)});var b=[];for(let s=0;s<256;++s)b.push((s+256).toString(16).slice(1));function H(s,e=0){return(b[s[e+0]]+b[s[e+1]]+b[s[e+2]]+b[s[e+3]]+"-"+b[s[e+4]]+b[s[e+5]]+"-"+b[s[e+6]]+b[s[e+7]]+"-"+b[s[e+8]]+b[s[e+9]]+"-"+b[s[e+10]]+b[s[e+11]]+b[s[e+12]]+b[s[e+13]]+b[s[e+14]]+b[s[e+15]]).toLowerCase()}var P,de=new Uint8Array(16);function B(){if(!P){if(typeof crypto>"u"||!crypto.getRandomValues)throw new Error("crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported");P=crypto.getRandomValues.bind(crypto)}return P(de)}var fe=typeof crypto<"u"&&crypto.randomUUID&&crypto.randomUUID.bind(crypto),L={randomUUID:fe};function he(s,e,t){s=s||{};let i=s.random??s.rng?.()??B();if(i.length<16)throw new Error("Random bytes length must be >= 16");if(i[6]=i[6]&15|64,i[8]=i[8]&63|128,e){if(t=t||0,t<0||t+16>e.length)throw new RangeError(`UUID byte range ${t}:${t+15} is out of buffer bounds`);for(let r=0;r<16;++r)e[t+r]=i[r];return e}return H(i)}function ge(s,e,t){return L.randomUUID&&!e&&!s?L.randomUUID():he(s,e,t)}var C=ge;var st=ue(K());function S(s){return pe(s,!1)}function pe(s,e){return s==null?s:{companyId:s.company_id==null?void 0:s.company_id,error:s.error==null?void 0:s.error,featureAllocation:s.feature_allocation==null?void 0:s.feature_allocation,featureUsage:s.feature_usage==null?void 0:s.feature_usage,featureUsageEvent:s.feature_usage_event==null?void 0:s.feature_usage_event,featureUsagePeriod:s.feature_usage_period==null?void 0:s.feature_usage_period,featureUsageResetAt:s.feature_usage_reset_at==null?void 0:new Date(s.feature_usage_reset_at),flag:s.flag,flagId:s.flag_id==null?void 0:s.flag_id,reason:s.reason,ruleId:s.rule_id==null?void 0:s.rule_id,ruleType:s.rule_type==null?void 0:s.rule_type,userId:s.user_id==null?void 0:s.user_id,value:s.value}}function N(s){return ye(s,!1)}function ye(s,e=!1){return s==null?s:{company_id:s.companyId,error:s.error,flag_id:s.flagId,flag_key:s.flagKey,reason:s.reason,req_company:s.reqCompany,req_user:s.reqUser,rule_id:s.ruleId,user_id:s.userId,value:s.value}}function _(s){return be(s,!1)}function be(s,e){return s==null?s:{data:S(s.data),params:s.params}}function G(s){return ve(s,!1)}function ve(s,e){return s==null?s:{flags:s.flags.map(S)}}function M(s){return ke(s,!1)}function ke(s,e){return s==null?s:{data:G(s.data),params:s.params}}var U=s=>{let{companyId:e,error:t,featureAllocation:i,featureUsage:r,featureUsageEvent:l,featureUsagePeriod:o,featureUsageResetAt:c,flag:d,flagId:p,reason:E,ruleId:g,ruleType:k,userId:x,value:T}=S(s);return{featureUsageExceeded:!T&&(k=="company_override_usage_exceeded"||k=="plan_entitlement_usage_exceeded"),companyId:e??void 0,error:t??void 0,featureAllocation:i??void 0,featureUsage:r??void 0,featureUsageEvent:l===null?void 0:l,featureUsagePeriod:o??void 0,featureUsageResetAt:c??void 0,flag:d,flagId:p??void 0,reason:E,ruleId:g??void 0,ruleType:k??void 0,userId:x??void 0,value:T}};function R(s){let e=Object.keys(s).reduce((t,i)=>{let l=Object.keys(s[i]||{}).sort().reduce((o,c)=>(o[c]=s[i][c],o),{});return t[i]=l,t},{});return JSON.stringify(e)}var $="1.2.10";var X="schematicId";var O=class{additionalHeaders={};apiKey;apiUrl="https://api.schematichq.com";conn=null;context={};debugEnabled=!1;offlineEnabled=!1;eventQueue;contextDependentEventQueue;eventUrl="https://c.schematichq.com";flagCheckListeners={};flagValueListeners={};isPending=!0;isPendingListeners=new Set;storage;useWebSocket=!1;checks={};featureUsageEventMap={};webSocketUrl="wss://api.schematichq.com";webSocketConnectionTimeout=1e4;webSocketReconnect=!0;webSocketMaxReconnectAttempts=7;webSocketInitialRetryDelay=1e3;webSocketMaxRetryDelay=3e4;wsReconnectAttempts=0;wsReconnectTimer=null;wsIntentionalDisconnect=!1;currentWebSocket=null;isConnecting=!1;maxEventQueueSize=100;maxEventRetries=5;eventRetryInitialDelay=1e3;eventRetryMaxDelay=3e4;retryTimer=null;flagValueDefaults={};flagCheckDefaults={};constructor(e,t){if(this.apiKey=e,this.eventQueue=[],this.contextDependentEventQueue=[],this.useWebSocket=t?.useWebSocket??!1,this.debugEnabled=t?.debug??!1,this.offlineEnabled=t?.offline??!1,typeof window<"u"&&typeof window.location<"u"){let i=new URLSearchParams(window.location.search),r=i.get("schematic_debug");r!==null&&(r===""||r==="true"||r==="1")&&(this.debugEnabled=!0);let l=i.get("schematic_offline");l!==null&&(l===""||l==="true"||l==="1")&&(this.offlineEnabled=!0,this.debugEnabled=!0)}this.offlineEnabled&&t?.debug!==!1&&(this.debugEnabled=!0),this.offlineEnabled&&this.setIsPending(!1),this.additionalHeaders={"X-Schematic-Client-Version":`schematic-js@${$}`,...t?.additionalHeaders??{}},t?.storage?this.storage=t.storage:typeof localStorage<"u"&&(this.storage=localStorage),t?.apiUrl!==void 0&&(this.apiUrl=t.apiUrl),t?.eventUrl!==void 0&&(this.eventUrl=t.eventUrl),t?.webSocketUrl!==void 0&&(this.webSocketUrl=t.webSocketUrl),t?.webSocketConnectionTimeout!==void 0&&(this.webSocketConnectionTimeout=t.webSocketConnectionTimeout),t?.webSocketReconnect!==void 0&&(this.webSocketReconnect=t.webSocketReconnect),t?.webSocketMaxReconnectAttempts!==void 0&&(this.webSocketMaxReconnectAttempts=t.webSocketMaxReconnectAttempts),t?.webSocketInitialRetryDelay!==void 0&&(this.webSocketInitialRetryDelay=t.webSocketInitialRetryDelay),t?.webSocketMaxRetryDelay!==void 0&&(this.webSocketMaxRetryDelay=t.webSocketMaxRetryDelay),t?.maxEventQueueSize!==void 0&&(this.maxEventQueueSize=t.maxEventQueueSize),t?.maxEventRetries!==void 0&&(this.maxEventRetries=t.maxEventRetries),t?.eventRetryInitialDelay!==void 0&&(this.eventRetryInitialDelay=t.eventRetryInitialDelay),t?.eventRetryMaxDelay!==void 0&&(this.eventRetryMaxDelay=t.eventRetryMaxDelay),t?.flagValueDefaults!==void 0&&(this.flagValueDefaults=t.flagValueDefaults),t?.flagCheckDefaults!==void 0&&(this.flagCheckDefaults=t.flagCheckDefaults),typeof window<"u"&&window?.addEventListener&&(window.addEventListener("beforeunload",()=>{this.flushEventQueue(),this.flushContextDependentEventQueue()}),this.useWebSocket&&(window.addEventListener("offline",()=>{this.debug("Browser went offline, closing WebSocket connection"),this.handleNetworkOffline()}),window.addEventListener("online",()=>{this.debug("Browser came online, attempting to reconnect WebSocket"),this.handleNetworkOnline()}))),this.offlineEnabled?this.debug("Initialized with offline mode enabled - no network requests will be made"):this.debugEnabled&&this.debug("Initialized with debug mode enabled")}resolveFallbackValue(e,t){return t!==void 0?t:e in this.flagValueDefaults?this.flagValueDefaults[e]:!1}resolveFallbackCheckFlagReturn(e,t,i="Fallback value used",r){if(t!==void 0)return{flag:e,value:t,reason:i,error:r};if(e in this.flagCheckDefaults){let l=this.flagCheckDefaults[e];return{...l,flag:e,reason:r!==void 0?i:l.reason,error:r}}return e in this.flagValueDefaults?{flag:e,value:this.flagValueDefaults[e],reason:i,error:r}:{flag:e,value:!1,reason:i,error:r}}async checkFlag(e){let{fallback:t,key:i}=e,r=e.context||this.context,l=R(r);if(this.debug(`checkFlag: ${i}`,{context:r,fallback:t}),this.isOffline()){let o=this.resolveFallbackCheckFlagReturn(i,t,"Offline mode - using initialization defaults");return this.debug(`checkFlag offline result: ${i}`,{value:o.value,offlineMode:!0}),o.value}if(!this.useWebSocket){let o=`${this.apiUrl}/flags/${i}/check`;return fetch(o,{method:"POST",headers:{...this.additionalHeaders??{},"Content-Type":"application/json;charset=UTF-8","X-Schematic-Api-Key":this.apiKey},body:JSON.stringify(r)}).then(c=>{if(!c.ok)throw new Error("Network response was not ok");return c.json()}).then(c=>{let d=_(c);this.debug(`checkFlag result: ${i}`,d);let p=U(d.data);return typeof p.featureUsageEvent=="string"&&this.updateFeatureUsageEventMap(p),this.submitFlagCheckEvent(i,p,r),p.value}).catch(c=>{console.error("There was a problem with the fetch operation:",c);let d=this.resolveFallbackCheckFlagReturn(i,t,"API request failed",c instanceof Error?c.message:String(c));return this.submitFlagCheckEvent(i,d,r),d.value})}try{let o=this.checks[l];if(this.conn!==null&&typeof o<"u"&&typeof o[i]<"u")return this.debug(`checkFlag cached result: ${i}`,o[i]),o[i].value;if(this.isOffline())return this.resolveFallbackValue(i,t);try{await this.setContext(r)}catch(E){return console.error("WebSocket connection failed, falling back to REST:",E),this.fallbackToRest(i,r,t)}let d=(this.checks[l]??{})[i],p=d?.value??this.resolveFallbackValue(i,t);return this.debug(`checkFlag WebSocket result: ${i}`,typeof d<"u"?d:{value:p,fallbackUsed:!0}),typeof d<"u"&&this.submitFlagCheckEvent(i,d,r),p}catch(o){console.error("Unexpected error in checkFlag:",o);let c=this.resolveFallbackCheckFlagReturn(i,t,"Unexpected error in flag check",o instanceof Error?o.message:String(o));return this.submitFlagCheckEvent(i,c,r),c.value}}debug(e,...t){this.debugEnabled&&console.log(`[Schematic] ${e}`,...t)}createPersistentMessageHandler(e){return t=>{let i=JSON.parse(t.data);this.debug("WebSocket persistent message received:",i),R(e)in this.checks||(this.checks[R(e)]={}),(i.flags??[]).forEach(r=>{let l=U(r),o=R(e);this.checks[o]===void 0&&(this.checks[o]={}),this.checks[o][l.flag]=l,this.debug("WebSocket flag update:",{flag:l.flag,value:l.value,flagCheck:l}),typeof l.featureUsageEvent=="string"&&this.updateFeatureUsageEventMap(l),((this.flagCheckListeners[r.flag]?.size??0)>0||(this.flagValueListeners[r.flag]?.size??0)>0)&&this.submitFlagCheckEvent(l.flag,l,e),this.debug(`About to notify listeners for flag ${r.flag}`,{flag:r.flag,value:l.value}),this.notifyFlagCheckListeners(r.flag,l),this.notifyFlagValueListeners(r.flag,l.value),this.debug(`Finished notifying listeners for flag ${r.flag}`,{flag:r.flag,value:l.value})}),this.flushContextDependentEventQueue(),this.setIsPending(!1)}}isOffline(){return this.offlineEnabled}submitFlagCheckEvent(e,t,i){let r={flagKey:e,value:t.value,reason:t.reason,flagId:t.flagId,ruleId:t.ruleId,companyId:t.companyId,userId:t.userId,error:t.error,reqCompany:i.company,reqUser:i.user};return this.debug("submitting flag check event:",r),this.handleEvent("flag_check",N(r))}async fallbackToRest(e,t,i){if(this.isOffline()){let r=this.resolveFallbackValue(e,i);return this.debug(`fallbackToRest offline result: ${e}`,{value:r,offlineMode:!0}),r}try{let r=`${this.apiUrl}/flags/${e}/check`,l=await fetch(r,{method:"POST",headers:{...this.additionalHeaders??{},"Content-Type":"application/json;charset=UTF-8","X-Schematic-Api-Key":this.apiKey},body:JSON.stringify(t)});if(!l.ok)throw new Error("Network response was not ok");let o=await l.json(),c=_(o);this.debug(`fallbackToRest result: ${e}`,c);let d=U(c.data);return typeof d.featureUsageEvent=="string"&&this.updateFeatureUsageEventMap(d),this.submitFlagCheckEvent(e,d,t),d.value}catch(r){console.error("REST API call failed, using fallback value:",r);let l=this.resolveFallbackCheckFlagReturn(e,i,"API request failed (fallback)",r instanceof Error?r.message:String(r));return this.submitFlagCheckEvent(e,l,t),l.value}}checkFlags=async e=>{if(e=e||this.context,this.debug("checkFlags",{context:e}),this.isOffline())return this.debug("checkFlags offline result: returning empty object"),{};let t=`${this.apiUrl}/flags/check`,i=JSON.stringify(e);return fetch(t,{method:"POST",headers:{...this.additionalHeaders??{},"Content-Type":"application/json;charset=UTF-8","X-Schematic-Api-Key":this.apiKey},body:i}).then(r=>{if(!r.ok)throw new Error("Network response was not ok");return r.json()}).then(r=>{let l=M(r);return this.debug("checkFlags result:",l),(l?.data?.flags??[]).reduce((o,c)=>(o[c.flag]=c.value,o),{})}).catch(r=>(console.error("There was a problem with the fetch operation:",r),{}))};identify=e=>{this.debug("identify:",e);try{this.setContext({company:e.company?.keys,user:e.keys})}catch(t){console.error("Error setting context:",t)}return this.handleEvent("identify",e)};setContext=async e=>{if(this.isOffline()||!this.useWebSocket)return this.context=e,this.flushContextDependentEventQueue(),this.setIsPending(!1),Promise.resolve();try{if(this.setIsPending(!0),!this.conn){if(this.isConnecting){for(this.debug("Connection already in progress, waiting for it to complete");this.isConnecting&&this.conn===null;)await new Promise(i=>setTimeout(i,10));if(this.conn!==null){let i=await this.conn;await this.wsSendMessage(i,e);return}}this.wsReconnectTimer!==null&&(this.debug("Cancelling scheduled reconnection, connecting immediately"),clearTimeout(this.wsReconnectTimer),this.wsReconnectTimer=null),this.isConnecting=!0;try{this.conn=this.wsConnect();let i=await this.conn;this.isConnecting=!1,await this.wsSendMessage(i,e);return}catch(i){throw this.isConnecting=!1,i}}let t=await this.conn;await this.wsSendMessage(t,e)}catch(t){throw console.error("Failed to establish WebSocket connection:",t),t}};track=e=>{let{company:t,user:i,event:r,traits:l,quantity:o=1}=e;if(!this.hasContext(t,i)){this.debug(`track: queuing event "${r}" until context is available`);let d={api_key:this.apiKey,body:{company:t,event:r,traits:l??{},user:i,quantity:o},sent_at:new Date().toISOString(),tracker_event_id:C(),tracker_user_id:this.getAnonymousId(),type:"track"};return this.contextDependentEventQueue.push(d),Promise.resolve()}let c={company:t??this.context.company,event:r,traits:l??{},user:i??this.context.user,quantity:o};return this.debug("track:",c),r in this.featureUsageEventMap&&this.optimisticallyUpdateFeatureUsage(r,o),this.handleEvent("track",c)};optimisticallyUpdateFeatureUsage=(e,t=1)=>{let i=this.featureUsageEventMap[e];i!=null&&(this.debug(`Optimistically updating feature usage for event: ${e}`,{quantity:t}),Object.entries(i).forEach(([r,l])=>{if(l===void 0)return;let o={...l};if(typeof o.featureUsage=="number"){if(o.featureUsage+=t,typeof o.featureAllocation=="number"){let d=o.featureUsageExceeded===!0,p=o.featureUsage>=o.featureAllocation;p!==d&&(o.featureUsageExceeded=p,p&&(o.value=!1),this.debug(`Usage limit status changed for flag: ${r}`,{was:d?"exceeded":"within limits",now:p?"exceeded":"within limits",featureUsage:o.featureUsage,featureAllocation:o.featureAllocation,value:o.value}))}this.featureUsageEventMap[e]!==void 0&&(this.featureUsageEventMap[e][r]=o);let c=R(this.context);this.checks[c]!==void 0&&this.checks[c]!==null&&(this.checks[c][r]=o),this.notifyFlagCheckListeners(r,o),this.notifyFlagValueListeners(r,o.value)}}))};hasContext=(e,t)=>{let i=e!=null&&Object.keys(e).length>0||t!=null&&Object.keys(t).length>0,r=this.context.company!==void 0&&this.context.company!==null&&Object.keys(this.context.company).length>0||this.context.user!==void 0&&this.context.user!==null&&Object.keys(this.context.user).length>0;return i||r};flushContextDependentEventQueue=()=>{for(this.debug(`flushing ${this.contextDependentEventQueue.length} context-dependent events`);this.contextDependentEventQueue.length>0;){let e=this.contextDependentEventQueue.shift();if(e)if(e.type==="track"&&typeof e.body=="object"&&e.body!==null){let t=e.body,i={...t,company:t.company??this.context.company,user:t.user??this.context.user},r={...e,body:i,sent_at:new Date().toISOString()};this.sendEvent(r)}else this.sendEvent(e)}};startRetryTimer=()=>{this.retryTimer===null&&(this.retryTimer=setInterval(()=>{this.flushEventQueue().catch(e=>{this.debug("Error in retry timer flush:",e)}),this.eventQueue.length===0&&this.stopRetryTimer()},5e3),this.debug("Started retry timer"))};stopRetryTimer=()=>{this.retryTimer!==null&&(clearInterval(this.retryTimer),this.retryTimer=null,this.debug("Stopped retry timer"))};flushEventQueue=async()=>{if(this.eventQueue.length===0)return;let e=Date.now(),t=[],i=[];for(let r of this.eventQueue)r.next_retry_at===void 0||r.next_retry_at<=e?t.push(r):i.push(r);if(t.length===0){this.debug(`No events ready for retry yet (${i.length} still in backoff)`);return}this.debug(`Flushing event queue: ${t.length} ready, ${i.length} waiting`),this.eventQueue=i;for(let r of t)try{await this.sendEvent(r),this.debug("Queued event sent successfully:",r.type)}catch(l){this.debug("Failed to send queued event:",l)}};getAnonymousId=()=>{if(!this.storage)return C();let e=this.storage.getItem(X);if(typeof e<"u")return e;let t=C();return this.storage.setItem(X,t),t};handleEvent=(e,t)=>{let i={api_key:this.apiKey,body:t,sent_at:new Date().toISOString(),tracker_event_id:C(),tracker_user_id:this.getAnonymousId(),type:e};return typeof document<"u"&&document?.hidden?this.storeEvent(i):this.sendEvent(i)};sendEvent=async e=>{let t=`${this.eventUrl}/e`,i=JSON.stringify(e);if(this.debug("sending event:",{url:t,event:e}),this.isOffline())return this.debug("event not sent (offline mode):",{event:e}),Promise.resolve();try{let r=await fetch(t,{method:"POST",headers:{...this.additionalHeaders??{},"Content-Type":"application/json;charset=UTF-8"},body:i});if(!r.ok)throw new Error(`HTTP ${r.status}: ${r.statusText}`);this.debug("event sent:",{status:r.status,statusText:r.statusText})}catch(r){let l=(e.retry_count??0)+1;if(l<=this.maxEventRetries){this.debug(`Event failed to send (attempt ${l}/${this.maxEventRetries}), queueing for retry:`,r);let o=this.eventRetryInitialDelay*Math.pow(2,l-1),c=Math.min(o,this.eventRetryMaxDelay),d=Date.now()+c,p={...e,retry_count:l,next_retry_at:d};this.eventQueue.length<this.maxEventQueueSize?(this.eventQueue.push(p),this.debug(`Event queued for retry in ${c}ms (${this.eventQueue.length}/${this.maxEventQueueSize})`)):(this.debug(`Event queue full (${this.maxEventQueueSize}), dropping oldest event`),this.eventQueue.shift(),this.eventQueue.push(p)),this.startRetryTimer()}else this.debug(`Event failed permanently after ${this.maxEventRetries} attempts, dropping:`,r)}return Promise.resolve()};storeEvent=e=>(this.eventQueue.push(e),Promise.resolve());cleanup=async()=>{if(this.isOffline())return this.debug("cleanup: skipped (offline mode)"),Promise.resolve();if(this.wsIntentionalDisconnect=!0,this.wsReconnectTimer!==null&&(clearTimeout(this.wsReconnectTimer),this.wsReconnectTimer=null),this.stopRetryTimer(),this.conn)try{let e=await this.conn;this.currentWebSocket===e&&(this.debug("Cleaning up current websocket tracking"),this.currentWebSocket=null),e.close()}catch(e){console.error("Error during cleanup:",e)}finally{this.conn=null,this.currentWebSocket=null,this.isConnecting=!1}};calculateReconnectDelay=()=>{let e=this.webSocketInitialRetryDelay*Math.pow(2,this.wsReconnectAttempts),t=Math.min(e,this.webSocketMaxRetryDelay),i=Math.random()*t*.5,r=t+i;return this.debug(`Reconnect delay calculated: ${r.toFixed(0)}ms (attempt ${this.wsReconnectAttempts+1}/${this.webSocketMaxReconnectAttempts})`),r};handleNetworkOffline=async()=>{if(this.conn!==null){try{let e=await this.conn;(e.readyState===WebSocket.OPEN||e.readyState===WebSocket.CONNECTING)&&e.close()}catch(e){this.debug("Error closing connection on offline:",e)}this.conn=null}this.wsReconnectTimer!==null&&(clearTimeout(this.wsReconnectTimer),this.wsReconnectTimer=null)};handleNetworkOnline=()=>{this.debug("Network online, attempting reconnection and flushing queued events"),this.wsReconnectAttempts=0,this.wsReconnectTimer!==null&&(clearTimeout(this.wsReconnectTimer),this.wsReconnectTimer=null),this.flushEventQueue().catch(e=>{this.debug("Error flushing event queue on network online:",e)}),this.attemptReconnect()};attemptReconnect=()=>{if(this.wsReconnectAttempts>=this.webSocketMaxReconnectAttempts){this.debug(`Maximum reconnection attempts (${this.webSocketMaxReconnectAttempts}) reached, giving up`);return}if(this.wsReconnectTimer!==null){this.debug("Reconnection attempt already scheduled, ignoring duplicate request");return}let e=this.calculateReconnectDelay();this.debug(`Scheduling reconnection attempt ${this.wsReconnectAttempts+1}/${this.webSocketMaxReconnectAttempts} in ${e.toFixed(0)}ms`),this.wsReconnectTimer=setTimeout(async()=>{this.wsReconnectTimer=null,this.wsReconnectAttempts++,this.debug(`Attempting to reconnect (attempt ${this.wsReconnectAttempts}/${this.webSocketMaxReconnectAttempts})`);try{if(this.conn!==null){this.debug("Cleaning up existing connection before reconnection");try{let t=await this.conn;this.currentWebSocket===t&&(this.debug("Existing websocket is current, will be replaced"),this.currentWebSocket=null),(t.readyState===WebSocket.OPEN||t.readyState===WebSocket.CONNECTING)&&t.close()}catch(t){this.debug("Error cleaning up existing connection:",t)}this.conn=null,this.currentWebSocket=null,this.isConnecting=!1}this.isConnecting=!0;try{this.conn=this.wsConnect();let t=await this.conn;this.isConnecting=!1,this.debug("Reconnection context check:",{hasCompany:this.context.company!==void 0,hasUser:this.context.user!==void 0,context:this.context}),this.context.company!==void 0||this.context.user!==void 0?(this.debug("Reconnected, force re-sending context"),await this.wsSendMessage(t,this.context,!0)):(this.debug("No context to re-send after reconnection - websocket ready for new context"),this.debug("Setting up tracking for reconnected websocket (no context to send)"),this.currentWebSocket=t),this.flushEventQueue().catch(i=>{this.debug("Error flushing event queue after websocket reconnection:",i)}),this.debug("Reconnection successful")}catch(t){throw this.isConnecting=!1,t}}catch(t){this.debug("Reconnection attempt failed:",t)}},e)};wsConnect=()=>this.isOffline()?(this.debug("wsConnect: skipped (offline mode)"),Promise.reject(new Error("WebSocket connection skipped in offline mode"))):new Promise((e,t)=>{let i=`${this.webSocketUrl}/flags/bootstrap?apiKey=${this.apiKey}`;this.debug("connecting to WebSocket:",i);let r=new WebSocket(i),l=Math.random().toString(36).substring(7);this.debug(`Creating WebSocket connection ${l} to ${i}`);let o=null,c=!1;o=setTimeout(()=>{c||(this.debug(`WebSocket connection timeout after ${this.webSocketConnectionTimeout}ms`),r.close(),t(new Error("WebSocket connection timeout")))},this.webSocketConnectionTimeout),r.onopen=()=>{c=!0,o!==null&&clearTimeout(o),this.wsReconnectAttempts=0,this.wsIntentionalDisconnect=!1,this.debug(`WebSocket connection ${l} opened successfully`),e(r)},r.onerror=d=>{c=!0,o!==null&&clearTimeout(o),this.debug(`WebSocket connection ${l} error:`,d),t(d)},r.onclose=()=>{c=!0,o!==null&&clearTimeout(o),this.debug(`WebSocket connection ${l} closed`),this.conn=null,this.currentWebSocket===r&&(this.currentWebSocket=null,this.isConnecting=!1),!this.wsIntentionalDisconnect&&this.webSocketReconnect&&this.attemptReconnect()}});wsSendMessage=(e,t,i=!1)=>this.isOffline()?(this.debug("wsSendMessage: skipped (offline mode)"),this.setIsPending(!1),Promise.resolve()):new Promise((r,l)=>{if(!i&&R(t)==R(this.context))return this.debug("WebSocket context unchanged, skipping update"),r(this.setIsPending(!1));this.debug(i?"WebSocket force sending context (reconnection):":"WebSocket context updated:",t),this.context=t;let o=()=>{let c=!1,d=this.createPersistentMessageHandler(t),p=k=>{d(k),c||(c=!0,r())};e.addEventListener("message",p),this.currentWebSocket=e;let E=this.additionalHeaders["X-Schematic-Client-Version"]??`schematic-js@${$}`,g={apiKey:this.apiKey,clientVersion:E,data:t};this.debug("WebSocket sending message:",g),e.send(JSON.stringify(g))};e.readyState===WebSocket.OPEN?(this.debug("WebSocket already open, sending message"),o()):e.readyState===WebSocket.CONNECTING?(this.debug("WebSocket connecting, waiting for open to send message"),e.addEventListener("open",o)):(this.debug("WebSocket is closed, cannot send message"),l("WebSocket is not open or connecting"))});getIsPending=()=>this.isPending;addIsPendingListener=e=>(this.isPendingListeners.add(e),()=>{this.isPendingListeners.delete(e)});setIsPending=e=>{this.isPending=e,this.isPendingListeners.forEach(t=>Ee(t,e))};getFlagCheck=e=>{let t=R(this.context);return(this.checks[t]??{})[e]};getFlagValue=e=>this.getFlagCheck(e)?.value;addFlagValueListener=(e,t)=>(e in this.flagValueListeners||(this.flagValueListeners[e]=new Set),this.flagValueListeners[e].add(t),()=>{this.flagValueListeners[e].delete(t)});addFlagCheckListener=(e,t)=>(e in this.flagCheckListeners||(this.flagCheckListeners[e]=new Set),this.flagCheckListeners[e].add(t),()=>{this.flagCheckListeners[e].delete(t)});notifyFlagCheckListeners=(e,t)=>{let i=this.flagCheckListeners?.[e]??[];i.size>0&&this.debug(`Notifying ${i.size} flag check listeners for ${e}`,t),typeof t.featureUsageEvent=="string"&&this.updateFeatureUsageEventMap(t),i.forEach(r=>Re(r,t))};updateFeatureUsageEventMap=e=>{if(typeof e.featureUsageEvent!="string")return;let t=e.featureUsageEvent;(this.featureUsageEventMap[t]===void 0||this.featureUsageEventMap[t]===null)&&(this.featureUsageEventMap[t]={}),this.featureUsageEventMap[t]!==void 0&&(this.featureUsageEventMap[t][e.flag]=e),this.debug(`Updated featureUsageEventMap for event: ${t}, flag: ${e.flag}`,e)};notifyFlagValueListeners=(e,t)=>{let i=this.flagValueListeners?.[e]??[];i.size>0&&this.debug(`Notifying ${i.size} flag value listeners for ${e}`,{value:t}),i.forEach((r,l)=>{this.debug(`Calling listener ${l} for flag ${e}`,{flagKey:e,value:t}),we(r,t),this.debug(`Listener ${l} for flag ${e} completed`,{flagKey:e,value:t})})}},Ee=(s,e)=>{s.length>0?s(e):s()},Re=(s,e)=>{s.length>0?s(e):s()},we=(s,e)=>{s.length>0?s(e):s()};window.Schematic=O;})();
3
3
  /* @preserve */
@@ -800,7 +800,7 @@ function contextString(context) {
800
800
  }
801
801
 
802
802
  // src/version.ts
803
- var version = "1.2.9";
803
+ var version = "1.2.10";
804
804
 
805
805
  // src/index.ts
806
806
  var anonymousIdKey = "schematicId";
@@ -832,6 +832,8 @@ var Schematic = class {
832
832
  wsReconnectAttempts = 0;
833
833
  wsReconnectTimer = null;
834
834
  wsIntentionalDisconnect = false;
835
+ currentWebSocket = null;
836
+ isConnecting = false;
835
837
  maxEventQueueSize = 100;
836
838
  // Prevent memory issues with very long network outages
837
839
  maxEventRetries = 5;
@@ -1109,6 +1111,49 @@ var Schematic = class {
1109
1111
  console.log(`[Schematic] ${message}`, ...args);
1110
1112
  }
1111
1113
  }
1114
+ /**
1115
+ * Create a persistent message handler for websocket flag updates
1116
+ */
1117
+ createPersistentMessageHandler(context) {
1118
+ return (event) => {
1119
+ const message = JSON.parse(event.data);
1120
+ this.debug(`WebSocket persistent message received:`, message);
1121
+ if (!(contextString(context) in this.checks)) {
1122
+ this.checks[contextString(context)] = {};
1123
+ }
1124
+ (message.flags ?? []).forEach((flag) => {
1125
+ const flagCheck = CheckFlagReturnFromJSON(flag);
1126
+ const contextStr = contextString(context);
1127
+ if (this.checks[contextStr] === void 0) {
1128
+ this.checks[contextStr] = {};
1129
+ }
1130
+ this.checks[contextStr][flagCheck.flag] = flagCheck;
1131
+ this.debug(`WebSocket flag update:`, {
1132
+ flag: flagCheck.flag,
1133
+ value: flagCheck.value,
1134
+ flagCheck
1135
+ });
1136
+ if (typeof flagCheck.featureUsageEvent === "string") {
1137
+ this.updateFeatureUsageEventMap(flagCheck);
1138
+ }
1139
+ if ((this.flagCheckListeners[flag.flag]?.size ?? 0) > 0 || (this.flagValueListeners[flag.flag]?.size ?? 0) > 0) {
1140
+ this.submitFlagCheckEvent(flagCheck.flag, flagCheck, context);
1141
+ }
1142
+ this.debug(`About to notify listeners for flag ${flag.flag}`, {
1143
+ flag: flag.flag,
1144
+ value: flagCheck.value
1145
+ });
1146
+ this.notifyFlagCheckListeners(flag.flag, flagCheck);
1147
+ this.notifyFlagValueListeners(flag.flag, flagCheck.value);
1148
+ this.debug(`Finished notifying listeners for flag ${flag.flag}`, {
1149
+ flag: flag.flag,
1150
+ value: flagCheck.value
1151
+ });
1152
+ });
1153
+ this.flushContextDependentEventQueue();
1154
+ this.setIsPending(false);
1155
+ };
1156
+ }
1112
1157
  /**
1113
1158
  * Helper function to check if client is in offline mode
1114
1159
  */
@@ -1260,6 +1305,19 @@ var Schematic = class {
1260
1305
  try {
1261
1306
  this.setIsPending(true);
1262
1307
  if (!this.conn) {
1308
+ if (this.isConnecting) {
1309
+ this.debug(
1310
+ `Connection already in progress, waiting for it to complete`
1311
+ );
1312
+ while (this.isConnecting && this.conn === null) {
1313
+ await new Promise((resolve) => setTimeout(resolve, 10));
1314
+ }
1315
+ if (this.conn !== null) {
1316
+ const socket2 = await this.conn;
1317
+ await this.wsSendMessage(socket2, context);
1318
+ return;
1319
+ }
1320
+ }
1263
1321
  if (this.wsReconnectTimer !== null) {
1264
1322
  this.debug(
1265
1323
  `Cancelling scheduled reconnection, connecting immediately`
@@ -1267,7 +1325,17 @@ var Schematic = class {
1267
1325
  clearTimeout(this.wsReconnectTimer);
1268
1326
  this.wsReconnectTimer = null;
1269
1327
  }
1270
- this.conn = this.wsConnect();
1328
+ this.isConnecting = true;
1329
+ try {
1330
+ this.conn = this.wsConnect();
1331
+ const socket2 = await this.conn;
1332
+ this.isConnecting = false;
1333
+ await this.wsSendMessage(socket2, context);
1334
+ return;
1335
+ } catch (error) {
1336
+ this.isConnecting = false;
1337
+ throw error;
1338
+ }
1271
1339
  }
1272
1340
  const socket = await this.conn;
1273
1341
  await this.wsSendMessage(socket, context);
@@ -1432,10 +1500,14 @@ var Schematic = class {
1432
1500
  }
1433
1501
  }
1434
1502
  if (readyEvents.length === 0) {
1435
- this.debug(`No events ready for retry yet (${notReadyEvents.length} still in backoff)`);
1503
+ this.debug(
1504
+ `No events ready for retry yet (${notReadyEvents.length} still in backoff)`
1505
+ );
1436
1506
  return;
1437
1507
  }
1438
- this.debug(`Flushing event queue: ${readyEvents.length} ready, ${notReadyEvents.length} waiting`);
1508
+ this.debug(
1509
+ `Flushing event queue: ${readyEvents.length} ready, ${notReadyEvents.length} waiting`
1510
+ );
1439
1511
  this.eventQueue = notReadyEvents;
1440
1512
  for (const event of readyEvents) {
1441
1513
  try {
@@ -1500,7 +1572,10 @@ var Schematic = class {
1500
1572
  } catch (error) {
1501
1573
  const retryCount = (event.retry_count ?? 0) + 1;
1502
1574
  if (retryCount <= this.maxEventRetries) {
1503
- this.debug(`Event failed to send (attempt ${retryCount}/${this.maxEventRetries}), queueing for retry:`, error);
1575
+ this.debug(
1576
+ `Event failed to send (attempt ${retryCount}/${this.maxEventRetries}), queueing for retry:`,
1577
+ error
1578
+ );
1504
1579
  const baseDelay = this.eventRetryInitialDelay * Math.pow(2, retryCount - 1);
1505
1580
  const jitterDelay = Math.min(baseDelay, this.eventRetryMaxDelay);
1506
1581
  const nextRetryAt = Date.now() + jitterDelay;
@@ -1511,15 +1586,22 @@ var Schematic = class {
1511
1586
  };
1512
1587
  if (this.eventQueue.length < this.maxEventQueueSize) {
1513
1588
  this.eventQueue.push(retryEvent);
1514
- this.debug(`Event queued for retry in ${jitterDelay}ms (${this.eventQueue.length}/${this.maxEventQueueSize})`);
1589
+ this.debug(
1590
+ `Event queued for retry in ${jitterDelay}ms (${this.eventQueue.length}/${this.maxEventQueueSize})`
1591
+ );
1515
1592
  } else {
1516
- this.debug(`Event queue full (${this.maxEventQueueSize}), dropping oldest event`);
1593
+ this.debug(
1594
+ `Event queue full (${this.maxEventQueueSize}), dropping oldest event`
1595
+ );
1517
1596
  this.eventQueue.shift();
1518
1597
  this.eventQueue.push(retryEvent);
1519
1598
  }
1520
1599
  this.startRetryTimer();
1521
1600
  } else {
1522
- this.debug(`Event failed permanently after ${this.maxEventRetries} attempts, dropping:`, error);
1601
+ this.debug(
1602
+ `Event failed permanently after ${this.maxEventRetries} attempts, dropping:`,
1603
+ error
1604
+ );
1523
1605
  }
1524
1606
  }
1525
1607
  return Promise.resolve();
@@ -1549,11 +1631,17 @@ var Schematic = class {
1549
1631
  if (this.conn) {
1550
1632
  try {
1551
1633
  const socket = await this.conn;
1634
+ if (this.currentWebSocket === socket) {
1635
+ this.debug(`Cleaning up current websocket tracking`);
1636
+ this.currentWebSocket = null;
1637
+ }
1552
1638
  socket.close();
1553
1639
  } catch (error) {
1554
1640
  console.error("Error during cleanup:", error);
1555
1641
  } finally {
1556
1642
  this.conn = null;
1643
+ this.currentWebSocket = null;
1644
+ this.isConnecting = false;
1557
1645
  }
1558
1646
  }
1559
1647
  };
@@ -1578,7 +1666,9 @@ var Schematic = class {
1578
1666
  if (this.conn !== null) {
1579
1667
  try {
1580
1668
  const socket = await this.conn;
1581
- socket.close();
1669
+ if (socket.readyState === WebSocket.OPEN || socket.readyState === WebSocket.CONNECTING) {
1670
+ socket.close();
1671
+ }
1582
1672
  } catch (error) {
1583
1673
  this.debug("Error closing connection on offline:", error);
1584
1674
  }
@@ -1593,7 +1683,9 @@ var Schematic = class {
1593
1683
  * Handle browser coming back online
1594
1684
  */
1595
1685
  handleNetworkOnline = () => {
1596
- this.debug("Network online, attempting reconnection and flushing queued events");
1686
+ this.debug(
1687
+ "Network online, attempting reconnection and flushing queued events"
1688
+ );
1597
1689
  this.wsReconnectAttempts = 0;
1598
1690
  if (this.wsReconnectTimer !== null) {
1599
1691
  clearTimeout(this.wsReconnectTimer);
@@ -1616,7 +1708,10 @@ var Schematic = class {
1616
1708
  return;
1617
1709
  }
1618
1710
  if (this.wsReconnectTimer !== null) {
1619
- clearTimeout(this.wsReconnectTimer);
1711
+ this.debug(
1712
+ `Reconnection attempt already scheduled, ignoring duplicate request`
1713
+ );
1714
+ return;
1620
1715
  }
1621
1716
  const delay = this.calculateReconnectDelay();
1622
1717
  this.debug(
@@ -1629,23 +1724,57 @@ var Schematic = class {
1629
1724
  `Attempting to reconnect (attempt ${this.wsReconnectAttempts}/${this.webSocketMaxReconnectAttempts})`
1630
1725
  );
1631
1726
  try {
1632
- this.conn = this.wsConnect();
1633
- const socket = await this.conn;
1634
- this.debug(`Reconnection context check:`, {
1635
- hasCompany: this.context.company !== void 0,
1636
- hasUser: this.context.user !== void 0,
1637
- context: this.context
1638
- });
1639
- if (this.context.company !== void 0 || this.context.user !== void 0) {
1640
- this.debug(`Reconnected, force re-sending context`);
1641
- await this.wsSendContextAfterReconnection(socket, this.context);
1642
- } else {
1643
- this.debug(`No context to re-send after reconnection - websocket ready for new context`);
1727
+ if (this.conn !== null) {
1728
+ this.debug(`Cleaning up existing connection before reconnection`);
1729
+ try {
1730
+ const existingSocket = await this.conn;
1731
+ if (this.currentWebSocket === existingSocket) {
1732
+ this.debug(`Existing websocket is current, will be replaced`);
1733
+ this.currentWebSocket = null;
1734
+ }
1735
+ if (existingSocket.readyState === WebSocket.OPEN || existingSocket.readyState === WebSocket.CONNECTING) {
1736
+ existingSocket.close();
1737
+ }
1738
+ } catch (error) {
1739
+ this.debug(`Error cleaning up existing connection:`, error);
1740
+ }
1741
+ this.conn = null;
1742
+ this.currentWebSocket = null;
1743
+ this.isConnecting = false;
1744
+ }
1745
+ this.isConnecting = true;
1746
+ try {
1747
+ this.conn = this.wsConnect();
1748
+ const socket = await this.conn;
1749
+ this.isConnecting = false;
1750
+ this.debug(`Reconnection context check:`, {
1751
+ hasCompany: this.context.company !== void 0,
1752
+ hasUser: this.context.user !== void 0,
1753
+ context: this.context
1754
+ });
1755
+ if (this.context.company !== void 0 || this.context.user !== void 0) {
1756
+ this.debug(`Reconnected, force re-sending context`);
1757
+ await this.wsSendMessage(socket, this.context, true);
1758
+ } else {
1759
+ this.debug(
1760
+ `No context to re-send after reconnection - websocket ready for new context`
1761
+ );
1762
+ this.debug(
1763
+ `Setting up tracking for reconnected websocket (no context to send)`
1764
+ );
1765
+ this.currentWebSocket = socket;
1766
+ }
1767
+ this.flushEventQueue().catch((error) => {
1768
+ this.debug(
1769
+ "Error flushing event queue after websocket reconnection:",
1770
+ error
1771
+ );
1772
+ });
1773
+ this.debug(`Reconnection successful`);
1774
+ } catch (error) {
1775
+ this.isConnecting = false;
1776
+ throw error;
1644
1777
  }
1645
- this.flushEventQueue().catch((error) => {
1646
- this.debug("Error flushing event queue after websocket reconnection:", error);
1647
- });
1648
- this.debug(`Reconnection successful`);
1649
1778
  } catch (error) {
1650
1779
  this.debug(`Reconnection attempt failed:`, error);
1651
1780
  }
@@ -1663,6 +1792,8 @@ var Schematic = class {
1663
1792
  const wsUrl = `${this.webSocketUrl}/flags/bootstrap?apiKey=${this.apiKey}`;
1664
1793
  this.debug(`connecting to WebSocket:`, wsUrl);
1665
1794
  const webSocket = new WebSocket(wsUrl);
1795
+ const connectionId = Math.random().toString(36).substring(7);
1796
+ this.debug(`Creating WebSocket connection ${connectionId} to ${wsUrl}`);
1666
1797
  let timeoutId = null;
1667
1798
  let isResolved = false;
1668
1799
  timeoutId = setTimeout(() => {
@@ -1681,7 +1812,7 @@ var Schematic = class {
1681
1812
  }
1682
1813
  this.wsReconnectAttempts = 0;
1683
1814
  this.wsIntentionalDisconnect = false;
1684
- this.debug(`WebSocket connection opened`);
1815
+ this.debug(`WebSocket connection ${connectionId} opened successfully`);
1685
1816
  resolve(webSocket);
1686
1817
  };
1687
1818
  webSocket.onerror = (error) => {
@@ -1689,7 +1820,7 @@ var Schematic = class {
1689
1820
  if (timeoutId !== null) {
1690
1821
  clearTimeout(timeoutId);
1691
1822
  }
1692
- this.debug(`WebSocket connection error:`, error);
1823
+ this.debug(`WebSocket connection ${connectionId} error:`, error);
1693
1824
  reject(error);
1694
1825
  };
1695
1826
  webSocket.onclose = () => {
@@ -1697,121 +1828,48 @@ var Schematic = class {
1697
1828
  if (timeoutId !== null) {
1698
1829
  clearTimeout(timeoutId);
1699
1830
  }
1700
- this.debug(`WebSocket connection closed`);
1831
+ this.debug(`WebSocket connection ${connectionId} closed`);
1701
1832
  this.conn = null;
1833
+ if (this.currentWebSocket === webSocket) {
1834
+ this.currentWebSocket = null;
1835
+ this.isConnecting = false;
1836
+ }
1702
1837
  if (!this.wsIntentionalDisconnect && this.webSocketReconnect) {
1703
1838
  this.attemptReconnect();
1704
1839
  }
1705
1840
  };
1706
1841
  });
1707
1842
  };
1708
- // Send a message on the websocket after reconnection, forcing the send even if context appears unchanged
1709
- // because the server has lost all state and needs the initial context
1710
- wsSendContextAfterReconnection = (socket, context) => {
1711
- if (this.isOffline()) {
1712
- this.debug("wsSendContextAfterReconnection: skipped (offline mode)");
1713
- this.setIsPending(false);
1714
- return Promise.resolve();
1715
- }
1716
- return new Promise((resolve) => {
1717
- this.debug(`WebSocket force sending context after reconnection:`, context);
1718
- this.context = context;
1719
- const sendMessage = () => {
1720
- let resolved = false;
1721
- const messageHandler = (event) => {
1722
- const message = JSON.parse(event.data);
1723
- this.debug(`WebSocket message received after reconnection:`, message);
1724
- if (!(contextString(context) in this.checks)) {
1725
- this.checks[contextString(context)] = {};
1726
- }
1727
- (message.flags ?? []).forEach((flag) => {
1728
- const flagCheck = CheckFlagReturnFromJSON(flag);
1729
- const contextStr = contextString(context);
1730
- if (this.checks[contextStr] === void 0) {
1731
- this.checks[contextStr] = {};
1732
- }
1733
- this.checks[contextStr][flagCheck.flag] = flagCheck;
1734
- });
1735
- this.useWebSocket = true;
1736
- socket.removeEventListener("message", messageHandler);
1737
- if (!resolved) {
1738
- resolved = true;
1739
- resolve(this.setIsPending(false));
1740
- }
1741
- };
1742
- socket.addEventListener("message", messageHandler);
1743
- const clientVersion = this.additionalHeaders["X-Schematic-Client-Version"] ?? `schematic-js@${version}`;
1744
- const messagePayload = {
1745
- apiKey: this.apiKey,
1746
- clientVersion,
1747
- data: context
1748
- };
1749
- this.debug(`WebSocket sending forced message after reconnection:`, messagePayload);
1750
- socket.send(JSON.stringify(messagePayload));
1751
- };
1752
- if (socket.readyState === WebSocket.OPEN) {
1753
- this.debug(`WebSocket already open, sending forced message after reconnection`);
1754
- sendMessage();
1755
- } else {
1756
- socket.addEventListener("open", () => {
1757
- this.debug(`WebSocket opened, sending forced message after reconnection`);
1758
- sendMessage();
1759
- });
1760
- }
1761
- });
1762
- };
1763
1843
  // Send a message on the websocket indicating interest in a particular evaluation context
1764
1844
  // and wait for the initial set of flag values to be returned
1765
- wsSendMessage = (socket, context) => {
1845
+ wsSendMessage = (socket, context, forceContextSend = false) => {
1766
1846
  if (this.isOffline()) {
1767
1847
  this.debug("wsSendMessage: skipped (offline mode)");
1768
1848
  this.setIsPending(false);
1769
1849
  return Promise.resolve();
1770
1850
  }
1771
1851
  return new Promise((resolve, reject) => {
1772
- if (contextString(context) == contextString(this.context)) {
1852
+ if (!forceContextSend && contextString(context) == contextString(this.context)) {
1773
1853
  this.debug(`WebSocket context unchanged, skipping update`);
1774
1854
  return resolve(this.setIsPending(false));
1775
1855
  }
1776
- this.debug(`WebSocket context updated:`, context);
1856
+ this.debug(
1857
+ forceContextSend ? `WebSocket force sending context (reconnection):` : `WebSocket context updated:`,
1858
+ context
1859
+ );
1777
1860
  this.context = context;
1778
1861
  const sendMessage = () => {
1779
1862
  let resolved = false;
1863
+ const persistentMessageHandler = this.createPersistentMessageHandler(context);
1780
1864
  const messageHandler = (event) => {
1781
- const message = JSON.parse(event.data);
1782
- this.debug(`WebSocket message received:`, message);
1783
- if (!(contextString(context) in this.checks)) {
1784
- this.checks[contextString(context)] = {};
1785
- }
1786
- (message.flags ?? []).forEach((flag) => {
1787
- const flagCheck = CheckFlagReturnFromJSON(flag);
1788
- const contextStr = contextString(context);
1789
- if (this.checks[contextStr] === void 0) {
1790
- this.checks[contextStr] = {};
1791
- }
1792
- this.checks[contextStr][flagCheck.flag] = flagCheck;
1793
- this.debug(`WebSocket flag update:`, {
1794
- flag: flagCheck.flag,
1795
- value: flagCheck.value,
1796
- flagCheck
1797
- });
1798
- if (typeof flagCheck.featureUsageEvent === "string") {
1799
- this.updateFeatureUsageEventMap(flagCheck);
1800
- }
1801
- if (this.flagCheckListeners[flag.flag]?.size > 0 || this.flagValueListeners[flag.flag]?.size > 0) {
1802
- this.submitFlagCheckEvent(flagCheck.flag, flagCheck, context);
1803
- }
1804
- this.notifyFlagCheckListeners(flag.flag, flagCheck);
1805
- this.notifyFlagValueListeners(flag.flag, flagCheck.value);
1806
- });
1807
- this.flushContextDependentEventQueue();
1808
- this.setIsPending(false);
1865
+ persistentMessageHandler(event);
1809
1866
  if (!resolved) {
1810
1867
  resolved = true;
1811
1868
  resolve();
1812
1869
  }
1813
1870
  };
1814
1871
  socket.addEventListener("message", messageHandler);
1872
+ this.currentWebSocket = socket;
1815
1873
  const clientVersion = this.additionalHeaders["X-Schematic-Client-Version"] ?? `schematic-js@${version}`;
1816
1874
  const messagePayload = {
1817
1875
  apiKey: this.apiKey,
@@ -1919,7 +1977,17 @@ var Schematic = class {
1919
1977
  { value }
1920
1978
  );
1921
1979
  }
1922
- listeners.forEach((listener) => notifyFlagValueListener(listener, value));
1980
+ listeners.forEach((listener, index) => {
1981
+ this.debug(`Calling listener ${index} for flag ${flagKey}`, {
1982
+ flagKey,
1983
+ value
1984
+ });
1985
+ notifyFlagValueListener(listener, value);
1986
+ this.debug(`Listener ${index} for flag ${flagKey} completed`, {
1987
+ flagKey,
1988
+ value
1989
+ });
1990
+ });
1923
1991
  };
1924
1992
  };
1925
1993
  var notifyPendingListener = (listener, value) => {
@@ -378,6 +378,8 @@ export declare class Schematic {
378
378
  private wsReconnectAttempts;
379
379
  private wsReconnectTimer;
380
380
  private wsIntentionalDisconnect;
381
+ private currentWebSocket;
382
+ private isConnecting;
381
383
  private maxEventQueueSize;
382
384
  private maxEventRetries;
383
385
  private eventRetryInitialDelay;
@@ -413,7 +415,11 @@ export declare class Schematic {
413
415
  * Helper function to log debug messages
414
416
  * Only logs if debug mode is enabled
415
417
  */
416
- private debug;
418
+ debug(message: string, ...args: unknown[]): void;
419
+ /**
420
+ * Create a persistent message handler for websocket flag updates
421
+ */
422
+ private createPersistentMessageHandler;
417
423
  /**
418
424
  * Helper function to check if client is in offline mode
419
425
  */
@@ -500,7 +506,6 @@ export declare class Schematic {
500
506
  */
501
507
  private attemptReconnect;
502
508
  private wsConnect;
503
- private wsSendContextAfterReconnection;
504
509
  private wsSendMessage;
505
510
  /**
506
511
  * State management
@@ -781,7 +781,7 @@ function contextString(context) {
781
781
  }
782
782
 
783
783
  // src/version.ts
784
- var version = "1.2.9";
784
+ var version = "1.2.10";
785
785
 
786
786
  // src/index.ts
787
787
  var anonymousIdKey = "schematicId";
@@ -813,6 +813,8 @@ var Schematic = class {
813
813
  wsReconnectAttempts = 0;
814
814
  wsReconnectTimer = null;
815
815
  wsIntentionalDisconnect = false;
816
+ currentWebSocket = null;
817
+ isConnecting = false;
816
818
  maxEventQueueSize = 100;
817
819
  // Prevent memory issues with very long network outages
818
820
  maxEventRetries = 5;
@@ -1090,6 +1092,49 @@ var Schematic = class {
1090
1092
  console.log(`[Schematic] ${message}`, ...args);
1091
1093
  }
1092
1094
  }
1095
+ /**
1096
+ * Create a persistent message handler for websocket flag updates
1097
+ */
1098
+ createPersistentMessageHandler(context) {
1099
+ return (event) => {
1100
+ const message = JSON.parse(event.data);
1101
+ this.debug(`WebSocket persistent message received:`, message);
1102
+ if (!(contextString(context) in this.checks)) {
1103
+ this.checks[contextString(context)] = {};
1104
+ }
1105
+ (message.flags ?? []).forEach((flag) => {
1106
+ const flagCheck = CheckFlagReturnFromJSON(flag);
1107
+ const contextStr = contextString(context);
1108
+ if (this.checks[contextStr] === void 0) {
1109
+ this.checks[contextStr] = {};
1110
+ }
1111
+ this.checks[contextStr][flagCheck.flag] = flagCheck;
1112
+ this.debug(`WebSocket flag update:`, {
1113
+ flag: flagCheck.flag,
1114
+ value: flagCheck.value,
1115
+ flagCheck
1116
+ });
1117
+ if (typeof flagCheck.featureUsageEvent === "string") {
1118
+ this.updateFeatureUsageEventMap(flagCheck);
1119
+ }
1120
+ if ((this.flagCheckListeners[flag.flag]?.size ?? 0) > 0 || (this.flagValueListeners[flag.flag]?.size ?? 0) > 0) {
1121
+ this.submitFlagCheckEvent(flagCheck.flag, flagCheck, context);
1122
+ }
1123
+ this.debug(`About to notify listeners for flag ${flag.flag}`, {
1124
+ flag: flag.flag,
1125
+ value: flagCheck.value
1126
+ });
1127
+ this.notifyFlagCheckListeners(flag.flag, flagCheck);
1128
+ this.notifyFlagValueListeners(flag.flag, flagCheck.value);
1129
+ this.debug(`Finished notifying listeners for flag ${flag.flag}`, {
1130
+ flag: flag.flag,
1131
+ value: flagCheck.value
1132
+ });
1133
+ });
1134
+ this.flushContextDependentEventQueue();
1135
+ this.setIsPending(false);
1136
+ };
1137
+ }
1093
1138
  /**
1094
1139
  * Helper function to check if client is in offline mode
1095
1140
  */
@@ -1241,6 +1286,19 @@ var Schematic = class {
1241
1286
  try {
1242
1287
  this.setIsPending(true);
1243
1288
  if (!this.conn) {
1289
+ if (this.isConnecting) {
1290
+ this.debug(
1291
+ `Connection already in progress, waiting for it to complete`
1292
+ );
1293
+ while (this.isConnecting && this.conn === null) {
1294
+ await new Promise((resolve) => setTimeout(resolve, 10));
1295
+ }
1296
+ if (this.conn !== null) {
1297
+ const socket2 = await this.conn;
1298
+ await this.wsSendMessage(socket2, context);
1299
+ return;
1300
+ }
1301
+ }
1244
1302
  if (this.wsReconnectTimer !== null) {
1245
1303
  this.debug(
1246
1304
  `Cancelling scheduled reconnection, connecting immediately`
@@ -1248,7 +1306,17 @@ var Schematic = class {
1248
1306
  clearTimeout(this.wsReconnectTimer);
1249
1307
  this.wsReconnectTimer = null;
1250
1308
  }
1251
- this.conn = this.wsConnect();
1309
+ this.isConnecting = true;
1310
+ try {
1311
+ this.conn = this.wsConnect();
1312
+ const socket2 = await this.conn;
1313
+ this.isConnecting = false;
1314
+ await this.wsSendMessage(socket2, context);
1315
+ return;
1316
+ } catch (error) {
1317
+ this.isConnecting = false;
1318
+ throw error;
1319
+ }
1252
1320
  }
1253
1321
  const socket = await this.conn;
1254
1322
  await this.wsSendMessage(socket, context);
@@ -1413,10 +1481,14 @@ var Schematic = class {
1413
1481
  }
1414
1482
  }
1415
1483
  if (readyEvents.length === 0) {
1416
- this.debug(`No events ready for retry yet (${notReadyEvents.length} still in backoff)`);
1484
+ this.debug(
1485
+ `No events ready for retry yet (${notReadyEvents.length} still in backoff)`
1486
+ );
1417
1487
  return;
1418
1488
  }
1419
- this.debug(`Flushing event queue: ${readyEvents.length} ready, ${notReadyEvents.length} waiting`);
1489
+ this.debug(
1490
+ `Flushing event queue: ${readyEvents.length} ready, ${notReadyEvents.length} waiting`
1491
+ );
1420
1492
  this.eventQueue = notReadyEvents;
1421
1493
  for (const event of readyEvents) {
1422
1494
  try {
@@ -1481,7 +1553,10 @@ var Schematic = class {
1481
1553
  } catch (error) {
1482
1554
  const retryCount = (event.retry_count ?? 0) + 1;
1483
1555
  if (retryCount <= this.maxEventRetries) {
1484
- this.debug(`Event failed to send (attempt ${retryCount}/${this.maxEventRetries}), queueing for retry:`, error);
1556
+ this.debug(
1557
+ `Event failed to send (attempt ${retryCount}/${this.maxEventRetries}), queueing for retry:`,
1558
+ error
1559
+ );
1485
1560
  const baseDelay = this.eventRetryInitialDelay * Math.pow(2, retryCount - 1);
1486
1561
  const jitterDelay = Math.min(baseDelay, this.eventRetryMaxDelay);
1487
1562
  const nextRetryAt = Date.now() + jitterDelay;
@@ -1492,15 +1567,22 @@ var Schematic = class {
1492
1567
  };
1493
1568
  if (this.eventQueue.length < this.maxEventQueueSize) {
1494
1569
  this.eventQueue.push(retryEvent);
1495
- this.debug(`Event queued for retry in ${jitterDelay}ms (${this.eventQueue.length}/${this.maxEventQueueSize})`);
1570
+ this.debug(
1571
+ `Event queued for retry in ${jitterDelay}ms (${this.eventQueue.length}/${this.maxEventQueueSize})`
1572
+ );
1496
1573
  } else {
1497
- this.debug(`Event queue full (${this.maxEventQueueSize}), dropping oldest event`);
1574
+ this.debug(
1575
+ `Event queue full (${this.maxEventQueueSize}), dropping oldest event`
1576
+ );
1498
1577
  this.eventQueue.shift();
1499
1578
  this.eventQueue.push(retryEvent);
1500
1579
  }
1501
1580
  this.startRetryTimer();
1502
1581
  } else {
1503
- this.debug(`Event failed permanently after ${this.maxEventRetries} attempts, dropping:`, error);
1582
+ this.debug(
1583
+ `Event failed permanently after ${this.maxEventRetries} attempts, dropping:`,
1584
+ error
1585
+ );
1504
1586
  }
1505
1587
  }
1506
1588
  return Promise.resolve();
@@ -1530,11 +1612,17 @@ var Schematic = class {
1530
1612
  if (this.conn) {
1531
1613
  try {
1532
1614
  const socket = await this.conn;
1615
+ if (this.currentWebSocket === socket) {
1616
+ this.debug(`Cleaning up current websocket tracking`);
1617
+ this.currentWebSocket = null;
1618
+ }
1533
1619
  socket.close();
1534
1620
  } catch (error) {
1535
1621
  console.error("Error during cleanup:", error);
1536
1622
  } finally {
1537
1623
  this.conn = null;
1624
+ this.currentWebSocket = null;
1625
+ this.isConnecting = false;
1538
1626
  }
1539
1627
  }
1540
1628
  };
@@ -1559,7 +1647,9 @@ var Schematic = class {
1559
1647
  if (this.conn !== null) {
1560
1648
  try {
1561
1649
  const socket = await this.conn;
1562
- socket.close();
1650
+ if (socket.readyState === WebSocket.OPEN || socket.readyState === WebSocket.CONNECTING) {
1651
+ socket.close();
1652
+ }
1563
1653
  } catch (error) {
1564
1654
  this.debug("Error closing connection on offline:", error);
1565
1655
  }
@@ -1574,7 +1664,9 @@ var Schematic = class {
1574
1664
  * Handle browser coming back online
1575
1665
  */
1576
1666
  handleNetworkOnline = () => {
1577
- this.debug("Network online, attempting reconnection and flushing queued events");
1667
+ this.debug(
1668
+ "Network online, attempting reconnection and flushing queued events"
1669
+ );
1578
1670
  this.wsReconnectAttempts = 0;
1579
1671
  if (this.wsReconnectTimer !== null) {
1580
1672
  clearTimeout(this.wsReconnectTimer);
@@ -1597,7 +1689,10 @@ var Schematic = class {
1597
1689
  return;
1598
1690
  }
1599
1691
  if (this.wsReconnectTimer !== null) {
1600
- clearTimeout(this.wsReconnectTimer);
1692
+ this.debug(
1693
+ `Reconnection attempt already scheduled, ignoring duplicate request`
1694
+ );
1695
+ return;
1601
1696
  }
1602
1697
  const delay = this.calculateReconnectDelay();
1603
1698
  this.debug(
@@ -1610,23 +1705,57 @@ var Schematic = class {
1610
1705
  `Attempting to reconnect (attempt ${this.wsReconnectAttempts}/${this.webSocketMaxReconnectAttempts})`
1611
1706
  );
1612
1707
  try {
1613
- this.conn = this.wsConnect();
1614
- const socket = await this.conn;
1615
- this.debug(`Reconnection context check:`, {
1616
- hasCompany: this.context.company !== void 0,
1617
- hasUser: this.context.user !== void 0,
1618
- context: this.context
1619
- });
1620
- if (this.context.company !== void 0 || this.context.user !== void 0) {
1621
- this.debug(`Reconnected, force re-sending context`);
1622
- await this.wsSendContextAfterReconnection(socket, this.context);
1623
- } else {
1624
- this.debug(`No context to re-send after reconnection - websocket ready for new context`);
1708
+ if (this.conn !== null) {
1709
+ this.debug(`Cleaning up existing connection before reconnection`);
1710
+ try {
1711
+ const existingSocket = await this.conn;
1712
+ if (this.currentWebSocket === existingSocket) {
1713
+ this.debug(`Existing websocket is current, will be replaced`);
1714
+ this.currentWebSocket = null;
1715
+ }
1716
+ if (existingSocket.readyState === WebSocket.OPEN || existingSocket.readyState === WebSocket.CONNECTING) {
1717
+ existingSocket.close();
1718
+ }
1719
+ } catch (error) {
1720
+ this.debug(`Error cleaning up existing connection:`, error);
1721
+ }
1722
+ this.conn = null;
1723
+ this.currentWebSocket = null;
1724
+ this.isConnecting = false;
1725
+ }
1726
+ this.isConnecting = true;
1727
+ try {
1728
+ this.conn = this.wsConnect();
1729
+ const socket = await this.conn;
1730
+ this.isConnecting = false;
1731
+ this.debug(`Reconnection context check:`, {
1732
+ hasCompany: this.context.company !== void 0,
1733
+ hasUser: this.context.user !== void 0,
1734
+ context: this.context
1735
+ });
1736
+ if (this.context.company !== void 0 || this.context.user !== void 0) {
1737
+ this.debug(`Reconnected, force re-sending context`);
1738
+ await this.wsSendMessage(socket, this.context, true);
1739
+ } else {
1740
+ this.debug(
1741
+ `No context to re-send after reconnection - websocket ready for new context`
1742
+ );
1743
+ this.debug(
1744
+ `Setting up tracking for reconnected websocket (no context to send)`
1745
+ );
1746
+ this.currentWebSocket = socket;
1747
+ }
1748
+ this.flushEventQueue().catch((error) => {
1749
+ this.debug(
1750
+ "Error flushing event queue after websocket reconnection:",
1751
+ error
1752
+ );
1753
+ });
1754
+ this.debug(`Reconnection successful`);
1755
+ } catch (error) {
1756
+ this.isConnecting = false;
1757
+ throw error;
1625
1758
  }
1626
- this.flushEventQueue().catch((error) => {
1627
- this.debug("Error flushing event queue after websocket reconnection:", error);
1628
- });
1629
- this.debug(`Reconnection successful`);
1630
1759
  } catch (error) {
1631
1760
  this.debug(`Reconnection attempt failed:`, error);
1632
1761
  }
@@ -1644,6 +1773,8 @@ var Schematic = class {
1644
1773
  const wsUrl = `${this.webSocketUrl}/flags/bootstrap?apiKey=${this.apiKey}`;
1645
1774
  this.debug(`connecting to WebSocket:`, wsUrl);
1646
1775
  const webSocket = new WebSocket(wsUrl);
1776
+ const connectionId = Math.random().toString(36).substring(7);
1777
+ this.debug(`Creating WebSocket connection ${connectionId} to ${wsUrl}`);
1647
1778
  let timeoutId = null;
1648
1779
  let isResolved = false;
1649
1780
  timeoutId = setTimeout(() => {
@@ -1662,7 +1793,7 @@ var Schematic = class {
1662
1793
  }
1663
1794
  this.wsReconnectAttempts = 0;
1664
1795
  this.wsIntentionalDisconnect = false;
1665
- this.debug(`WebSocket connection opened`);
1796
+ this.debug(`WebSocket connection ${connectionId} opened successfully`);
1666
1797
  resolve(webSocket);
1667
1798
  };
1668
1799
  webSocket.onerror = (error) => {
@@ -1670,7 +1801,7 @@ var Schematic = class {
1670
1801
  if (timeoutId !== null) {
1671
1802
  clearTimeout(timeoutId);
1672
1803
  }
1673
- this.debug(`WebSocket connection error:`, error);
1804
+ this.debug(`WebSocket connection ${connectionId} error:`, error);
1674
1805
  reject(error);
1675
1806
  };
1676
1807
  webSocket.onclose = () => {
@@ -1678,121 +1809,48 @@ var Schematic = class {
1678
1809
  if (timeoutId !== null) {
1679
1810
  clearTimeout(timeoutId);
1680
1811
  }
1681
- this.debug(`WebSocket connection closed`);
1812
+ this.debug(`WebSocket connection ${connectionId} closed`);
1682
1813
  this.conn = null;
1814
+ if (this.currentWebSocket === webSocket) {
1815
+ this.currentWebSocket = null;
1816
+ this.isConnecting = false;
1817
+ }
1683
1818
  if (!this.wsIntentionalDisconnect && this.webSocketReconnect) {
1684
1819
  this.attemptReconnect();
1685
1820
  }
1686
1821
  };
1687
1822
  });
1688
1823
  };
1689
- // Send a message on the websocket after reconnection, forcing the send even if context appears unchanged
1690
- // because the server has lost all state and needs the initial context
1691
- wsSendContextAfterReconnection = (socket, context) => {
1692
- if (this.isOffline()) {
1693
- this.debug("wsSendContextAfterReconnection: skipped (offline mode)");
1694
- this.setIsPending(false);
1695
- return Promise.resolve();
1696
- }
1697
- return new Promise((resolve) => {
1698
- this.debug(`WebSocket force sending context after reconnection:`, context);
1699
- this.context = context;
1700
- const sendMessage = () => {
1701
- let resolved = false;
1702
- const messageHandler = (event) => {
1703
- const message = JSON.parse(event.data);
1704
- this.debug(`WebSocket message received after reconnection:`, message);
1705
- if (!(contextString(context) in this.checks)) {
1706
- this.checks[contextString(context)] = {};
1707
- }
1708
- (message.flags ?? []).forEach((flag) => {
1709
- const flagCheck = CheckFlagReturnFromJSON(flag);
1710
- const contextStr = contextString(context);
1711
- if (this.checks[contextStr] === void 0) {
1712
- this.checks[contextStr] = {};
1713
- }
1714
- this.checks[contextStr][flagCheck.flag] = flagCheck;
1715
- });
1716
- this.useWebSocket = true;
1717
- socket.removeEventListener("message", messageHandler);
1718
- if (!resolved) {
1719
- resolved = true;
1720
- resolve(this.setIsPending(false));
1721
- }
1722
- };
1723
- socket.addEventListener("message", messageHandler);
1724
- const clientVersion = this.additionalHeaders["X-Schematic-Client-Version"] ?? `schematic-js@${version}`;
1725
- const messagePayload = {
1726
- apiKey: this.apiKey,
1727
- clientVersion,
1728
- data: context
1729
- };
1730
- this.debug(`WebSocket sending forced message after reconnection:`, messagePayload);
1731
- socket.send(JSON.stringify(messagePayload));
1732
- };
1733
- if (socket.readyState === WebSocket.OPEN) {
1734
- this.debug(`WebSocket already open, sending forced message after reconnection`);
1735
- sendMessage();
1736
- } else {
1737
- socket.addEventListener("open", () => {
1738
- this.debug(`WebSocket opened, sending forced message after reconnection`);
1739
- sendMessage();
1740
- });
1741
- }
1742
- });
1743
- };
1744
1824
  // Send a message on the websocket indicating interest in a particular evaluation context
1745
1825
  // and wait for the initial set of flag values to be returned
1746
- wsSendMessage = (socket, context) => {
1826
+ wsSendMessage = (socket, context, forceContextSend = false) => {
1747
1827
  if (this.isOffline()) {
1748
1828
  this.debug("wsSendMessage: skipped (offline mode)");
1749
1829
  this.setIsPending(false);
1750
1830
  return Promise.resolve();
1751
1831
  }
1752
1832
  return new Promise((resolve, reject) => {
1753
- if (contextString(context) == contextString(this.context)) {
1833
+ if (!forceContextSend && contextString(context) == contextString(this.context)) {
1754
1834
  this.debug(`WebSocket context unchanged, skipping update`);
1755
1835
  return resolve(this.setIsPending(false));
1756
1836
  }
1757
- this.debug(`WebSocket context updated:`, context);
1837
+ this.debug(
1838
+ forceContextSend ? `WebSocket force sending context (reconnection):` : `WebSocket context updated:`,
1839
+ context
1840
+ );
1758
1841
  this.context = context;
1759
1842
  const sendMessage = () => {
1760
1843
  let resolved = false;
1844
+ const persistentMessageHandler = this.createPersistentMessageHandler(context);
1761
1845
  const messageHandler = (event) => {
1762
- const message = JSON.parse(event.data);
1763
- this.debug(`WebSocket message received:`, message);
1764
- if (!(contextString(context) in this.checks)) {
1765
- this.checks[contextString(context)] = {};
1766
- }
1767
- (message.flags ?? []).forEach((flag) => {
1768
- const flagCheck = CheckFlagReturnFromJSON(flag);
1769
- const contextStr = contextString(context);
1770
- if (this.checks[contextStr] === void 0) {
1771
- this.checks[contextStr] = {};
1772
- }
1773
- this.checks[contextStr][flagCheck.flag] = flagCheck;
1774
- this.debug(`WebSocket flag update:`, {
1775
- flag: flagCheck.flag,
1776
- value: flagCheck.value,
1777
- flagCheck
1778
- });
1779
- if (typeof flagCheck.featureUsageEvent === "string") {
1780
- this.updateFeatureUsageEventMap(flagCheck);
1781
- }
1782
- if (this.flagCheckListeners[flag.flag]?.size > 0 || this.flagValueListeners[flag.flag]?.size > 0) {
1783
- this.submitFlagCheckEvent(flagCheck.flag, flagCheck, context);
1784
- }
1785
- this.notifyFlagCheckListeners(flag.flag, flagCheck);
1786
- this.notifyFlagValueListeners(flag.flag, flagCheck.value);
1787
- });
1788
- this.flushContextDependentEventQueue();
1789
- this.setIsPending(false);
1846
+ persistentMessageHandler(event);
1790
1847
  if (!resolved) {
1791
1848
  resolved = true;
1792
1849
  resolve();
1793
1850
  }
1794
1851
  };
1795
1852
  socket.addEventListener("message", messageHandler);
1853
+ this.currentWebSocket = socket;
1796
1854
  const clientVersion = this.additionalHeaders["X-Schematic-Client-Version"] ?? `schematic-js@${version}`;
1797
1855
  const messagePayload = {
1798
1856
  apiKey: this.apiKey,
@@ -1900,7 +1958,17 @@ var Schematic = class {
1900
1958
  { value }
1901
1959
  );
1902
1960
  }
1903
- listeners.forEach((listener) => notifyFlagValueListener(listener, value));
1961
+ listeners.forEach((listener, index) => {
1962
+ this.debug(`Calling listener ${index} for flag ${flagKey}`, {
1963
+ flagKey,
1964
+ value
1965
+ });
1966
+ notifyFlagValueListener(listener, value);
1967
+ this.debug(`Listener ${index} for flag ${flagKey} completed`, {
1968
+ flagKey,
1969
+ value
1970
+ });
1971
+ });
1904
1972
  };
1905
1973
  };
1906
1974
  var notifyPendingListener = (listener, value) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@schematichq/schematic-js",
3
- "version": "1.2.9",
3
+ "version": "1.2.10",
4
4
  "main": "dist/schematic.cjs.js",
5
5
  "module": "dist/schematic.esm.js",
6
6
  "types": "dist/schematic.d.ts",
@@ -39,7 +39,7 @@
39
39
  "devDependencies": {
40
40
  "@eslint/js": "^9.39.1",
41
41
  "@microsoft/api-extractor": "^7.55.0",
42
- "@openapitools/openapi-generator-cli": "^2.25.1",
42
+ "@openapitools/openapi-generator-cli": "^2.25.2",
43
43
  "@vitest/browser": "^4.0.10",
44
44
  "esbuild": "^0.27.0",
45
45
  "eslint": "^9.39.1",