@schematichq/schematic-js 1.2.10 → 1.2.12

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=(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;})();
1
+ "use strict";(()=>{var re=Object.create;var Q=Object.defineProperty;var ie=Object.getOwnPropertyDescriptor;var se=Object.getOwnPropertyNames;var ae=Object.getPrototypeOf,oe=Object.prototype.hasOwnProperty;var ce=(s,e)=>()=>(e||s((e={exports:{}}).exports,e),e.exports);var le=(s,e,t,i)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of se(e))!oe.call(s,n)&&n!==t&&Q(s,n,{get:()=>e[n],enumerable:!(i=ie(e,n))||i.enumerable});return s};var ue=(s,e,t)=>(t=s!=null?re(ae(s)):{},le(e||!s||!s.__esModule?Q(t,"default",{value:s,enumerable:!0}):t,s));var G=ce(z=>{(function(s){var e=(function(t){var i=typeof globalThis<"u"&&globalThis||typeof s<"u"&&s||typeof global<"u"&&global||{},n={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 c(r){return r&&DataView.prototype.isPrototypeOf(r)}if(n.arrayBuffer)var o=["[object Int8Array]","[object Uint8Array]","[object Uint8ClampedArray]","[object Int16Array]","[object Uint16Array]","[object Int32Array]","[object Uint32Array]","[object Float32Array]","[object Float64Array]"],l=ArrayBuffer.isView||function(r){return r&&o.indexOf(Object.prototype.toString.call(r))>-1};function d(r){if(typeof r!="string"&&(r=String(r)),/[^a-z0-9\-#$%&'*+.^_`|~!]/i.test(r)||r==="")throw new TypeError('Invalid character in header field name: "'+r+'"');return r.toLowerCase()}function p(r){return typeof r!="string"&&(r=String(r)),r}function E(r){var a={next:function(){var u=r.shift();return{done:u===void 0,value:u}}};return n.iterable&&(a[Symbol.iterator]=function(){return a}),a}function g(r){this.map={},r instanceof g?r.forEach(function(a,u){this.append(u,a)},this):Array.isArray(r)?r.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):r&&Object.getOwnPropertyNames(r).forEach(function(a){this.append(a,r[a])},this)}g.prototype.append=function(r,a){r=d(r),a=p(a);var u=this.map[r];this.map[r]=u?u+", "+a:a},g.prototype.delete=function(r){delete this.map[d(r)]},g.prototype.get=function(r){return r=d(r),this.has(r)?this.map[r]:null},g.prototype.has=function(r){return this.map.hasOwnProperty(d(r))},g.prototype.set=function(r,a){this.map[d(r)]=p(a)},g.prototype.forEach=function(r,a){for(var u in this.map)this.map.hasOwnProperty(u)&&r.call(a,this.map[u],u,this)},g.prototype.keys=function(){var r=[];return this.forEach(function(a,u){r.push(u)}),E(r)},g.prototype.values=function(){var r=[];return this.forEach(function(a){r.push(a)}),E(r)},g.prototype.entries=function(){var r=[];return this.forEach(function(a,u){r.push([u,a])}),E(r)},n.iterable&&(g.prototype[Symbol.iterator]=g.prototype.entries);function k(r){if(!r._noBody){if(r.bodyUsed)return Promise.reject(new TypeError("Already read"));r.bodyUsed=!0}}function x(r){return new Promise(function(a,u){r.onload=function(){a(r.result)},r.onerror=function(){u(r.error)}})}function T(r){var a=new FileReader,u=x(a);return a.readAsArrayBuffer(r),u}function V(r){var a=new FileReader,u=x(a),h=/charset=([A-Za-z0-9_-]+)/.exec(r.type),y=h?h[1]:"utf-8";return a.readAsText(r,y),u}function Y(r){for(var a=new Uint8Array(r),u=new Array(a.length),h=0;h<a.length;h++)u[h]=String.fromCharCode(a[h]);return u.join("")}function W(r){if(r.slice)return r.slice(0);var a=new Uint8Array(r.byteLength);return a.set(new Uint8Array(r)),a.buffer}function J(){return this.bodyUsed=!1,this._initBody=function(r){this.bodyUsed=this.bodyUsed,this._bodyInit=r,r?typeof r=="string"?this._bodyText=r:n.blob&&Blob.prototype.isPrototypeOf(r)?this._bodyBlob=r:n.formData&&FormData.prototype.isPrototypeOf(r)?this._bodyFormData=r:n.searchParams&&URLSearchParams.prototype.isPrototypeOf(r)?this._bodyText=r.toString():n.arrayBuffer&&n.blob&&c(r)?(this._bodyArrayBuffer=W(r.buffer),this._bodyInit=new Blob([this._bodyArrayBuffer])):n.arrayBuffer&&(ArrayBuffer.prototype.isPrototypeOf(r)||l(r))?this._bodyArrayBuffer=W(r):this._bodyText=r=Object.prototype.toString.call(r):(this._noBody=!0,this._bodyText=""),this.headers.get("content-type")||(typeof r=="string"?this.headers.set("content-type","text/plain;charset=UTF-8"):this._bodyBlob&&this._bodyBlob.type?this.headers.set("content-type",this._bodyBlob.type):n.searchParams&&URLSearchParams.prototype.isPrototypeOf(r)&&this.headers.set("content-type","application/x-www-form-urlencoded;charset=UTF-8"))},n.blob&&(this.blob=function(){var r=k(this);if(r)return r;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 r=k(this);return r||(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(n.blob)return this.blob().then(T);throw new Error("could not read as ArrayBuffer")}},this.text=function(){var r=k(this);if(r)return r;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)},n.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(r){var a=r.toUpperCase();return Z.indexOf(a)>-1?a:r}function R(r,a){if(!(this instanceof R))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(r instanceof R){if(r.bodyUsed)throw new TypeError("Already read");this.url=r.url,this.credentials=r.credentials,a.headers||(this.headers=new g(r.headers)),this.method=r.method,this.mode=r.mode,this.signal=r.signal,!u&&r._bodyInit!=null&&(u=r._bodyInit,r.bodyUsed=!0)}else this.url=String(r);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()}}}R.prototype.clone=function(){return new R(this,{body:this._bodyInit})};function ee(r){var a=new FormData;return r.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(r){var a=new g,u=r.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(R.prototype);function v(r,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(r)}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 r=new v(null,{status:200,statusText:""});return r.ok=!1,r.status=0,r.type="error",r};var ne=[301,302,303,307,308];v.redirect=function(r,a){if(ne.indexOf(a)===-1)throw new RangeError("Invalid status code");return new v(null,{status:a,headers:{location:r}})},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(r,a){return new Promise(function(u,h){var y=new R(r,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 S="response"in f?f.response:f.responseText;setTimeout(function(){u(new v(S,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&&(n.blob?f.responseType="blob":n.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,S){q.indexOf(S)===-1&&f.setRequestHeader(S,m)})}else y.headers.forEach(function(m,S){f.setRequestHeader(S,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=R,i.Response=v),t.Headers=g,t.Request=R,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 N(){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),B={randomUUID:fe};function he(s,e,t){s=s||{};let i=s.random??s.rng?.()??N();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 n=0;n<16;++n)e[t+n]=i[n];return e}return H(i)}function ge(s,e,t){return B.randomUUID&&!e&&!s?B.randomUUID():he(s,e,t)}var C=ge;var it=ue(G());function F(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 L(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:F(s.data),params:s.params}}function K(s){return ve(s,!1)}function ve(s,e){return s==null?s:{flags:s.flags.map(F)}}function M(s){return ke(s,!1)}function ke(s,e){return s==null?s:{data:K(s.data),params:s.params}}var O=s=>{let{companyId:e,error:t,featureAllocation:i,featureUsage:n,featureUsageEvent:c,featureUsagePeriod:o,featureUsageResetAt:l,flag:d,flagId:p,reason:E,ruleId:g,ruleType:k,userId:x,value:T}=F(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:n??void 0,featureUsageEvent:c===null?void 0:c,featureUsagePeriod:o??void 0,featureUsageResetAt:l??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 w(s){let e=Object.keys(s).reduce((t,i)=>{let c=Object.keys(s[i]||{}).sort().reduce((o,l)=>(o[l]=s[i][l],o),{});return t[i]=c,t},{});return JSON.stringify(e)}var $="1.2.12";var X="schematicId";var U=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),n=i.get("schematic_debug");n!==null&&(n===""||n==="true"||n==="1")&&(this.debugEnabled=!0);let c=i.get("schematic_offline");c!==null&&(c===""||c==="true"||c==="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",n){if(t!==void 0)return{flag:e,value:t,reason:i,error:n};if(e in this.flagCheckDefaults){let c=this.flagCheckDefaults[e];return{...c,flag:e,reason:n!==void 0?i:c.reason,error:n}}return e in this.flagValueDefaults?{flag:e,value:this.flagValueDefaults[e],reason:i,error:n}:{flag:e,value:!1,reason:i,error:n}}async checkFlag(e){let{fallback:t,key:i}=e,n=e.context||this.context,c=w(n);if(this.debug(`checkFlag: ${i}`,{context:n,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(n)}).then(l=>{if(!l.ok)throw new Error("Network response was not ok");return l.json()}).then(l=>{let d=_(l);this.debug(`checkFlag result: ${i}`,d);let p=O(d.data);return typeof p.featureUsageEvent=="string"&&this.updateFeatureUsageEventMap(p),this.submitFlagCheckEvent(i,p,n),p.value}).catch(l=>{console.error("There was a problem with the fetch operation:",l);let d=this.resolveFallbackCheckFlagReturn(i,t,"API request failed",l instanceof Error?l.message:String(l));return this.submitFlagCheckEvent(i,d,n),d.value})}try{let o=this.checks[c];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(n)}catch(E){return console.error("WebSocket connection failed, falling back to REST:",E),this.fallbackToRest(i,n,t)}let d=(this.checks[c]??{})[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,n),p}catch(o){console.error("Unexpected error in checkFlag:",o);let l=this.resolveFallbackCheckFlagReturn(i,t,"Unexpected error in flag check",o instanceof Error?o.message:String(o));return this.submitFlagCheckEvent(i,l,n),l.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),w(e)in this.checks||(this.checks[w(e)]={}),(i.flags??[]).forEach(n=>{let c=O(n),o=w(e);this.checks[o]===void 0&&(this.checks[o]={}),this.checks[o][c.flag]=c,this.debug("WebSocket flag update:",{flag:c.flag,value:c.value,flagCheck:c}),typeof c.featureUsageEvent=="string"&&this.updateFeatureUsageEventMap(c),((this.flagCheckListeners[n.flag]?.size??0)>0||(this.flagValueListeners[n.flag]?.size??0)>0)&&this.submitFlagCheckEvent(c.flag,c,e),this.debug(`About to notify listeners for flag ${n.flag}`,{flag:n.flag,value:c.value}),this.notifyFlagCheckListeners(n.flag,c),this.notifyFlagValueListeners(n.flag,c.value),this.debug(`Finished notifying listeners for flag ${n.flag}`,{flag:n.flag,value:c.value})}),this.flushContextDependentEventQueue(),this.setIsPending(!1)}}isOffline(){return this.offlineEnabled}submitFlagCheckEvent(e,t,i){let n={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:",n),this.handleEvent("flag_check",L(n))}async fallbackToRest(e,t,i){if(this.isOffline()){let n=this.resolveFallbackValue(e,i);return this.debug(`fallbackToRest offline result: ${e}`,{value:n,offlineMode:!0}),n}try{let n=`${this.apiUrl}/flags/${e}/check`,c=await fetch(n,{method:"POST",headers:{...this.additionalHeaders??{},"Content-Type":"application/json;charset=UTF-8","X-Schematic-Api-Key":this.apiKey},body:JSON.stringify(t)});if(!c.ok)throw new Error("Network response was not ok");let o=await c.json(),l=_(o);this.debug(`fallbackToRest result: ${e}`,l);let d=O(l.data);return typeof d.featureUsageEvent=="string"&&this.updateFeatureUsageEventMap(d),this.submitFlagCheckEvent(e,d,t),d.value}catch(n){console.error("REST API call failed, using fallback value:",n);let c=this.resolveFallbackCheckFlagReturn(e,i,"API request failed (fallback)",n instanceof Error?n.message:String(n));return this.submitFlagCheckEvent(e,c,t),c.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(n=>{if(!n.ok)throw new Error("Network response was not ok");return n.json()}).then(n=>{let c=M(n);return this.debug("checkFlags result:",c),(c?.data?.flags??[]).reduce((o,l)=>(o[l.flag]=l.value,o),{})}).catch(n=>(console.error("There was a problem with the fetch operation:",n),{}))};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:n,traits:c,quantity:o=1}=e;if(!this.hasContext(t,i)){this.debug(`track: queuing event "${n}" until context is available`);let d={api_key:this.apiKey,body:{company:t,event:n,traits:c??{},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 l={company:t??this.context.company,event:n,traits:c??{},user:i??this.context.user,quantity:o};return this.debug("track:",l),n in this.featureUsageEventMap&&this.optimisticallyUpdateFeatureUsage(n,o),this.handleEvent("track",l)};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(([n,c])=>{if(c===void 0)return;let o={...c};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: ${n}`,{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][n]=o);let l=w(this.context);this.checks[l]!==void 0&&this.checks[l]!==null&&(this.checks[l][n]=o),this.notifyFlagCheckListeners(n,o),this.notifyFlagValueListeners(n,o.value)}}))};hasContext=(e,t)=>{let i=e!=null&&Object.keys(e).length>0||t!=null&&Object.keys(t).length>0,n=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||n};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},n={...e,body:i,sent_at:new Date().toISOString()};this.sendEvent(n)}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 n of this.eventQueue)n.next_retry_at===void 0||n.next_retry_at<=e?t.push(n):i.push(n);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 n of t)try{await this.sendEvent(n),this.debug("Queued event sent successfully:",n.type)}catch(c){this.debug("Failed to send queued event:",c)}};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 n=await fetch(t,{method:"POST",headers:{...this.additionalHeaders??{},"Content-Type":"application/json;charset=UTF-8"},body:i});if(!n.ok)throw new Error(`HTTP ${n.status}: ${n.statusText}`);this.debug("event sent:",{status:n.status,statusText:n.statusText})}catch(n){let c=(e.retry_count??0)+1;if(c<=this.maxEventRetries){this.debug(`Event failed to send (attempt ${c}/${this.maxEventRetries}), queueing for retry:`,n);let o=this.eventRetryInitialDelay*Math.pow(2,c-1),l=Math.min(o,this.eventRetryMaxDelay),d=Date.now()+l,p={...e,retry_count:c,next_retry_at:d};this.eventQueue.length<this.maxEventQueueSize?(this.eventQueue.push(p),this.debug(`Event queued for retry in ${l}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:`,n)}return Promise.resolve()};storeEvent=e=>(this.eventQueue.push(e),Promise.resolve());forceReconnect=async()=>this.reconnect({force:!0});reconnectIfNeeded=async()=>this.reconnect({force:!1});reconnect=async e=>{let{force:t}=e,i=t?"forceReconnect":"reconnectIfNeeded";if(this.isOffline())return this.debug(`${i}: skipped (offline mode)`),Promise.resolve();if(!t&&this.conn!==null)try{if((await this.conn).readyState===WebSocket.OPEN)return this.debug(`${i}: connection is healthy, skipping`),Promise.resolve()}catch{}if(this.debug(`${i}: ${t?"forcing immediate reconnection":"reconnecting"}`),this.wsIntentionalDisconnect=!1,this.wsReconnectTimer!==null&&(this.debug(`${i}: cancelling pending reconnection timer`),clearTimeout(this.wsReconnectTimer),this.wsReconnectTimer=null),this.wsReconnectAttempts=0,this.conn!==null){this.debug(`${i}: closing existing connection`);try{let n=await this.conn;this.currentWebSocket===n&&(this.currentWebSocket=null),(n.readyState===WebSocket.OPEN||n.readyState===WebSocket.CONNECTING)&&n.close()}catch(n){this.debug(`${i}: error closing existing connection:`,n)}this.conn=null,this.isConnecting=!1}if(this.context.company!==void 0||this.context.user!==void 0){this.debug(`${i}: reconnecting with existing context`);try{this.isConnecting=!0,this.conn=this.wsConnect();let n=await this.conn;this.isConnecting=!1,await this.wsSendMessage(n,this.context,!0),this.debug(`${i}: reconnection successful`)}catch(n){this.isConnecting=!1,this.debug(`${i}: reconnection failed:`,n)}}else this.debug(`${i}: no context set, skipping reconnection`);return 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,n=t+i;return this.debug(`Reconnect delay calculated: ${n.toFixed(0)}ms (attempt ${this.wsReconnectAttempts+1}/${this.webSocketMaxReconnectAttempts})`),n};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 n=new WebSocket(i),c=Math.random().toString(36).substring(7);this.debug(`Creating WebSocket connection ${c} to ${i}`);let o=null,l=!1;o=setTimeout(()=>{l||(this.debug(`WebSocket connection timeout after ${this.webSocketConnectionTimeout}ms`),n.close(),t(new Error("WebSocket connection timeout")))},this.webSocketConnectionTimeout),n.onopen=()=>{l=!0,o!==null&&clearTimeout(o),this.wsReconnectAttempts=0,this.wsIntentionalDisconnect=!1,this.debug(`WebSocket connection ${c} opened successfully`),e(n)},n.onerror=d=>{l=!0,o!==null&&clearTimeout(o),this.debug(`WebSocket connection ${c} error:`,d),t(d)},n.onclose=()=>{l=!0,o!==null&&clearTimeout(o),this.debug(`WebSocket connection ${c} closed`),this.conn=null,this.currentWebSocket===n&&(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((n,c)=>{if(!i&&w(t)==w(this.context))return this.debug("WebSocket context unchanged, skipping update"),n(this.setIsPending(!1));this.debug(i?"WebSocket force sending context (reconnection):":"WebSocket context updated:",t),this.context=t;let o=()=>{let l=!1,d=this.createPersistentMessageHandler(t),p=k=>{d(k),l||(l=!0,n())};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"),c("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=w(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(n=>we(n,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((n,c)=>{this.debug(`Calling listener ${c} for flag ${e}`,{flagKey:e,value:t}),Re(n,t),this.debug(`Listener ${c} for flag ${e} completed`,{flagKey:e,value:t})})}},Ee=(s,e)=>{s.length>0?s(e):s()},we=(s,e)=>{s.length>0?s(e):s()},Re=(s,e)=>{s.length>0?s(e):s()};window.Schematic=U;})();
3
3
  /* @preserve */
@@ -800,7 +800,7 @@ function contextString(context) {
800
800
  }
801
801
 
802
802
  // src/version.ts
803
- var version = "1.2.10";
803
+ var version = "1.2.12";
804
804
 
805
805
  // src/index.ts
806
806
  var anonymousIdKey = "schematicId";
@@ -1613,6 +1613,130 @@ var Schematic = class {
1613
1613
  /**
1614
1614
  * Websocket management
1615
1615
  */
1616
+ /**
1617
+ * Force an immediate WebSocket reconnection.
1618
+ * This is useful when the application returns from a background state (e.g., mobile app
1619
+ * coming back to foreground) and wants to immediately re-establish the connection
1620
+ * rather than waiting for the exponential backoff timer.
1621
+ *
1622
+ * This method will:
1623
+ * - Cancel any pending reconnection timer
1624
+ * - Reset the reconnection attempt counter
1625
+ * - Close any existing connection
1626
+ * - Immediately attempt to reconnect
1627
+ * - Re-send the current context to get fresh flag values
1628
+ *
1629
+ * Use this when you need guaranteed fresh values (e.g., after an in-app purchase).
1630
+ *
1631
+ * @example
1632
+ * ```typescript
1633
+ * // React Native example: reconnect when app comes to foreground
1634
+ * useEffect(() => {
1635
+ * const subscription = AppState.addEventListener("change", (state) => {
1636
+ * if (state === "active") {
1637
+ * client.forceReconnect();
1638
+ * }
1639
+ * });
1640
+ * return () => subscription.remove();
1641
+ * }, [client]);
1642
+ * ```
1643
+ */
1644
+ forceReconnect = async () => {
1645
+ return this.reconnect({ force: true });
1646
+ };
1647
+ /**
1648
+ * Reconnect the WebSocket connection only if the current connection is unhealthy.
1649
+ * This is useful when the application returns from a background state and wants to
1650
+ * ensure a healthy connection exists, but doesn't need to force a reconnection if
1651
+ * the connection is still active.
1652
+ *
1653
+ * This method will:
1654
+ * - Check if an existing connection is healthy (readyState === OPEN)
1655
+ * - If healthy, return immediately without reconnecting
1656
+ * - If unhealthy, perform the same reconnection logic as forceReconnect()
1657
+ *
1658
+ * Use this when you want efficient reconnection that avoids unnecessary disconnects.
1659
+ *
1660
+ * @example
1661
+ * ```typescript
1662
+ * // React Native example: reconnect only if needed when app comes to foreground
1663
+ * useEffect(() => {
1664
+ * const subscription = AppState.addEventListener("change", (state) => {
1665
+ * if (state === "active") {
1666
+ * client.reconnectIfNeeded();
1667
+ * }
1668
+ * });
1669
+ * return () => subscription.remove();
1670
+ * }, [client]);
1671
+ * ```
1672
+ */
1673
+ reconnectIfNeeded = async () => {
1674
+ return this.reconnect({ force: false });
1675
+ };
1676
+ /**
1677
+ * Internal method to handle reconnection logic for both forceReconnect and reconnectIfNeeded.
1678
+ */
1679
+ reconnect = async (options) => {
1680
+ const { force } = options;
1681
+ const methodName = force ? "forceReconnect" : "reconnectIfNeeded";
1682
+ if (this.isOffline()) {
1683
+ this.debug(`${methodName}: skipped (offline mode)`);
1684
+ return Promise.resolve();
1685
+ }
1686
+ if (!force && this.conn !== null) {
1687
+ try {
1688
+ const existingSocket = await this.conn;
1689
+ if (existingSocket.readyState === WebSocket.OPEN) {
1690
+ this.debug(`${methodName}: connection is healthy, skipping`);
1691
+ return Promise.resolve();
1692
+ }
1693
+ } catch {
1694
+ }
1695
+ }
1696
+ this.debug(
1697
+ `${methodName}: ${force ? "forcing immediate reconnection" : "reconnecting"}`
1698
+ );
1699
+ this.wsIntentionalDisconnect = false;
1700
+ if (this.wsReconnectTimer !== null) {
1701
+ this.debug(`${methodName}: cancelling pending reconnection timer`);
1702
+ clearTimeout(this.wsReconnectTimer);
1703
+ this.wsReconnectTimer = null;
1704
+ }
1705
+ this.wsReconnectAttempts = 0;
1706
+ if (this.conn !== null) {
1707
+ this.debug(`${methodName}: closing existing connection`);
1708
+ try {
1709
+ const existingSocket = await this.conn;
1710
+ if (this.currentWebSocket === existingSocket) {
1711
+ this.currentWebSocket = null;
1712
+ }
1713
+ if (existingSocket.readyState === WebSocket.OPEN || existingSocket.readyState === WebSocket.CONNECTING) {
1714
+ existingSocket.close();
1715
+ }
1716
+ } catch (error) {
1717
+ this.debug(`${methodName}: error closing existing connection:`, error);
1718
+ }
1719
+ this.conn = null;
1720
+ this.isConnecting = false;
1721
+ }
1722
+ if (this.context.company !== void 0 || this.context.user !== void 0) {
1723
+ this.debug(`${methodName}: reconnecting with existing context`);
1724
+ try {
1725
+ this.isConnecting = true;
1726
+ this.conn = this.wsConnect();
1727
+ const socket = await this.conn;
1728
+ this.isConnecting = false;
1729
+ await this.wsSendMessage(socket, this.context, true);
1730
+ this.debug(`${methodName}: reconnection successful`);
1731
+ } catch (error) {
1732
+ this.isConnecting = false;
1733
+ this.debug(`${methodName}: reconnection failed:`, error);
1734
+ }
1735
+ } else {
1736
+ this.debug(`${methodName}: no context set, skipping reconnection`);
1737
+ }
1738
+ return Promise.resolve();
1739
+ };
1616
1740
  /**
1617
1741
  * If using websocket mode, close the connection when done.
1618
1742
  * In offline mode, this is a no-op.
@@ -482,6 +482,66 @@ export declare class Schematic {
482
482
  /**
483
483
  * Websocket management
484
484
  */
485
+ /**
486
+ * Force an immediate WebSocket reconnection.
487
+ * This is useful when the application returns from a background state (e.g., mobile app
488
+ * coming back to foreground) and wants to immediately re-establish the connection
489
+ * rather than waiting for the exponential backoff timer.
490
+ *
491
+ * This method will:
492
+ * - Cancel any pending reconnection timer
493
+ * - Reset the reconnection attempt counter
494
+ * - Close any existing connection
495
+ * - Immediately attempt to reconnect
496
+ * - Re-send the current context to get fresh flag values
497
+ *
498
+ * Use this when you need guaranteed fresh values (e.g., after an in-app purchase).
499
+ *
500
+ * @example
501
+ * ```typescript
502
+ * // React Native example: reconnect when app comes to foreground
503
+ * useEffect(() => {
504
+ * const subscription = AppState.addEventListener("change", (state) => {
505
+ * if (state === "active") {
506
+ * client.forceReconnect();
507
+ * }
508
+ * });
509
+ * return () => subscription.remove();
510
+ * }, [client]);
511
+ * ```
512
+ */
513
+ forceReconnect: () => Promise<void>;
514
+ /**
515
+ * Reconnect the WebSocket connection only if the current connection is unhealthy.
516
+ * This is useful when the application returns from a background state and wants to
517
+ * ensure a healthy connection exists, but doesn't need to force a reconnection if
518
+ * the connection is still active.
519
+ *
520
+ * This method will:
521
+ * - Check if an existing connection is healthy (readyState === OPEN)
522
+ * - If healthy, return immediately without reconnecting
523
+ * - If unhealthy, perform the same reconnection logic as forceReconnect()
524
+ *
525
+ * Use this when you want efficient reconnection that avoids unnecessary disconnects.
526
+ *
527
+ * @example
528
+ * ```typescript
529
+ * // React Native example: reconnect only if needed when app comes to foreground
530
+ * useEffect(() => {
531
+ * const subscription = AppState.addEventListener("change", (state) => {
532
+ * if (state === "active") {
533
+ * client.reconnectIfNeeded();
534
+ * }
535
+ * });
536
+ * return () => subscription.remove();
537
+ * }, [client]);
538
+ * ```
539
+ */
540
+ reconnectIfNeeded: () => Promise<void>;
541
+ /**
542
+ * Internal method to handle reconnection logic for both forceReconnect and reconnectIfNeeded.
543
+ */
544
+ private reconnect;
485
545
  /**
486
546
  * If using websocket mode, close the connection when done.
487
547
  * In offline mode, this is a no-op.
@@ -781,7 +781,7 @@ function contextString(context) {
781
781
  }
782
782
 
783
783
  // src/version.ts
784
- var version = "1.2.10";
784
+ var version = "1.2.12";
785
785
 
786
786
  // src/index.ts
787
787
  var anonymousIdKey = "schematicId";
@@ -1594,6 +1594,130 @@ var Schematic = class {
1594
1594
  /**
1595
1595
  * Websocket management
1596
1596
  */
1597
+ /**
1598
+ * Force an immediate WebSocket reconnection.
1599
+ * This is useful when the application returns from a background state (e.g., mobile app
1600
+ * coming back to foreground) and wants to immediately re-establish the connection
1601
+ * rather than waiting for the exponential backoff timer.
1602
+ *
1603
+ * This method will:
1604
+ * - Cancel any pending reconnection timer
1605
+ * - Reset the reconnection attempt counter
1606
+ * - Close any existing connection
1607
+ * - Immediately attempt to reconnect
1608
+ * - Re-send the current context to get fresh flag values
1609
+ *
1610
+ * Use this when you need guaranteed fresh values (e.g., after an in-app purchase).
1611
+ *
1612
+ * @example
1613
+ * ```typescript
1614
+ * // React Native example: reconnect when app comes to foreground
1615
+ * useEffect(() => {
1616
+ * const subscription = AppState.addEventListener("change", (state) => {
1617
+ * if (state === "active") {
1618
+ * client.forceReconnect();
1619
+ * }
1620
+ * });
1621
+ * return () => subscription.remove();
1622
+ * }, [client]);
1623
+ * ```
1624
+ */
1625
+ forceReconnect = async () => {
1626
+ return this.reconnect({ force: true });
1627
+ };
1628
+ /**
1629
+ * Reconnect the WebSocket connection only if the current connection is unhealthy.
1630
+ * This is useful when the application returns from a background state and wants to
1631
+ * ensure a healthy connection exists, but doesn't need to force a reconnection if
1632
+ * the connection is still active.
1633
+ *
1634
+ * This method will:
1635
+ * - Check if an existing connection is healthy (readyState === OPEN)
1636
+ * - If healthy, return immediately without reconnecting
1637
+ * - If unhealthy, perform the same reconnection logic as forceReconnect()
1638
+ *
1639
+ * Use this when you want efficient reconnection that avoids unnecessary disconnects.
1640
+ *
1641
+ * @example
1642
+ * ```typescript
1643
+ * // React Native example: reconnect only if needed when app comes to foreground
1644
+ * useEffect(() => {
1645
+ * const subscription = AppState.addEventListener("change", (state) => {
1646
+ * if (state === "active") {
1647
+ * client.reconnectIfNeeded();
1648
+ * }
1649
+ * });
1650
+ * return () => subscription.remove();
1651
+ * }, [client]);
1652
+ * ```
1653
+ */
1654
+ reconnectIfNeeded = async () => {
1655
+ return this.reconnect({ force: false });
1656
+ };
1657
+ /**
1658
+ * Internal method to handle reconnection logic for both forceReconnect and reconnectIfNeeded.
1659
+ */
1660
+ reconnect = async (options) => {
1661
+ const { force } = options;
1662
+ const methodName = force ? "forceReconnect" : "reconnectIfNeeded";
1663
+ if (this.isOffline()) {
1664
+ this.debug(`${methodName}: skipped (offline mode)`);
1665
+ return Promise.resolve();
1666
+ }
1667
+ if (!force && this.conn !== null) {
1668
+ try {
1669
+ const existingSocket = await this.conn;
1670
+ if (existingSocket.readyState === WebSocket.OPEN) {
1671
+ this.debug(`${methodName}: connection is healthy, skipping`);
1672
+ return Promise.resolve();
1673
+ }
1674
+ } catch {
1675
+ }
1676
+ }
1677
+ this.debug(
1678
+ `${methodName}: ${force ? "forcing immediate reconnection" : "reconnecting"}`
1679
+ );
1680
+ this.wsIntentionalDisconnect = false;
1681
+ if (this.wsReconnectTimer !== null) {
1682
+ this.debug(`${methodName}: cancelling pending reconnection timer`);
1683
+ clearTimeout(this.wsReconnectTimer);
1684
+ this.wsReconnectTimer = null;
1685
+ }
1686
+ this.wsReconnectAttempts = 0;
1687
+ if (this.conn !== null) {
1688
+ this.debug(`${methodName}: closing existing connection`);
1689
+ try {
1690
+ const existingSocket = await this.conn;
1691
+ if (this.currentWebSocket === existingSocket) {
1692
+ this.currentWebSocket = null;
1693
+ }
1694
+ if (existingSocket.readyState === WebSocket.OPEN || existingSocket.readyState === WebSocket.CONNECTING) {
1695
+ existingSocket.close();
1696
+ }
1697
+ } catch (error) {
1698
+ this.debug(`${methodName}: error closing existing connection:`, error);
1699
+ }
1700
+ this.conn = null;
1701
+ this.isConnecting = false;
1702
+ }
1703
+ if (this.context.company !== void 0 || this.context.user !== void 0) {
1704
+ this.debug(`${methodName}: reconnecting with existing context`);
1705
+ try {
1706
+ this.isConnecting = true;
1707
+ this.conn = this.wsConnect();
1708
+ const socket = await this.conn;
1709
+ this.isConnecting = false;
1710
+ await this.wsSendMessage(socket, this.context, true);
1711
+ this.debug(`${methodName}: reconnection successful`);
1712
+ } catch (error) {
1713
+ this.isConnecting = false;
1714
+ this.debug(`${methodName}: reconnection failed:`, error);
1715
+ }
1716
+ } else {
1717
+ this.debug(`${methodName}: no context set, skipping reconnection`);
1718
+ }
1719
+ return Promise.resolve();
1720
+ };
1597
1721
  /**
1598
1722
  * If using websocket mode, close the connection when done.
1599
1723
  * In offline mode, this is a no-op.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@schematichq/schematic-js",
3
- "version": "1.2.10",
3
+ "version": "1.2.12",
4
4
  "main": "dist/schematic.cjs.js",
5
5
  "module": "dist/schematic.esm.js",
6
6
  "types": "dist/schematic.d.ts",