@schematichq/schematic-js 1.2.0 → 1.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -82,6 +82,36 @@ await schematic.checkFlag("some-flag-key");
82
82
  schematic.cleanup();
83
83
  ```
84
84
 
85
+ ## Troubleshooting
86
+
87
+ For debugging and development, Schematic supports two special modes:
88
+
89
+ ### Debug Mode
90
+
91
+ Enables console logging of all Schematic operations:
92
+
93
+ ```typescript
94
+ // Enable at initialization
95
+ const schematic = new Schematic("your-api-key", { debug: true });
96
+
97
+ // Or via URL parameter
98
+ // https://yoursite.com/?schematic_debug=true
99
+ ```
100
+
101
+ ### Offline Mode
102
+
103
+ Prevents network requests and returns fallback values for all flag checks:
104
+
105
+ ```typescript
106
+ // Enable at initialization
107
+ const schematic = new Schematic("your-api-key", { offline: true });
108
+
109
+ // Or via URL parameter
110
+ // https://yoursite.com/?schematic_offline=true
111
+ ```
112
+
113
+ Offline mode automatically enables debug mode to help with troubleshooting.
114
+
85
115
  ## License
86
116
 
87
117
  MIT
@@ -1,3 +1,3 @@
1
- "use strict";(()=>{var te=Object.create;var H=Object.defineProperty;var re=Object.getOwnPropertyDescriptor;var ne=Object.getOwnPropertyNames;var ae=Object.getPrototypeOf,se=Object.prototype.hasOwnProperty;var oe=(r,t)=>()=>(t||r((t={exports:{}}).exports,t),t.exports);var ie=(r,t,n,o)=>{if(t&&typeof t=="object"||typeof t=="function")for(let s of ne(t))!se.call(r,s)&&s!==n&&H(r,s,{get:()=>t[s],enumerable:!(o=re(t,s))||o.enumerable});return r};var le=(r,t,n)=>(n=r!=null?te(ae(r)):{},ie(t||!r||!r.__esModule?H(n,"default",{value:r,enumerable:!0}):n,r));var W=oe(q=>{(function(r){var t=function(n){var o=typeof globalThis<"u"&&globalThis||typeof r<"u"&&r||typeof global<"u"&&global||{},s={searchParams:"URLSearchParams"in o,iterable:"Symbol"in o&&"iterator"in Symbol,blob:"FileReader"in o&&"Blob"in o&&function(){try{return new Blob,!0}catch{return!1}}(),formData:"FormData"in o,arrayBuffer:"ArrayBuffer"in o};function h(e){return e&&DataView.prototype.isPrototypeOf(e)}if(s.arrayBuffer)var d=["[object Int8Array]","[object Uint8Array]","[object Uint8ClampedArray]","[object Int16Array]","[object Uint16Array]","[object Int32Array]","[object Uint32Array]","[object Float32Array]","[object Float64Array]"],p=ArrayBuffer.isView||function(e){return e&&d.indexOf(Object.prototype.toString.call(e))>-1};function m(e){if(typeof e!="string"&&(e=String(e)),/[^a-z0-9\-#$%&'*+.^_`|~!]/i.test(e)||e==="")throw new TypeError('Invalid character in header field name: "'+e+'"');return e.toLowerCase()}function E(e){return typeof e!="string"&&(e=String(e)),e}function k(e){var a={next:function(){var i=e.shift();return{done:i===void 0,value:i}}};return s.iterable&&(a[Symbol.iterator]=function(){return a}),a}function c(e){this.map={},e instanceof c?e.forEach(function(a,i){this.append(i,a)},this):Array.isArray(e)?e.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):e&&Object.getOwnPropertyNames(e).forEach(function(a){this.append(a,e[a])},this)}c.prototype.append=function(e,a){e=m(e),a=E(a);var i=this.map[e];this.map[e]=i?i+", "+a:a},c.prototype.delete=function(e){delete this.map[m(e)]},c.prototype.get=function(e){return e=m(e),this.has(e)?this.map[e]:null},c.prototype.has=function(e){return this.map.hasOwnProperty(m(e))},c.prototype.set=function(e,a){this.map[m(e)]=E(a)},c.prototype.forEach=function(e,a){for(var i in this.map)this.map.hasOwnProperty(i)&&e.call(a,this.map[i],i,this)},c.prototype.keys=function(){var e=[];return this.forEach(function(a,i){e.push(i)}),k(e)},c.prototype.values=function(){var e=[];return this.forEach(function(a){e.push(a)}),k(e)},c.prototype.entries=function(){var e=[];return this.forEach(function(a,i){e.push([i,a])}),k(e)},s.iterable&&(c.prototype[Symbol.iterator]=c.prototype.entries);function R(e){if(!e._noBody){if(e.bodyUsed)return Promise.reject(new TypeError("Already read"));e.bodyUsed=!0}}function x(e){return new Promise(function(a,i){e.onload=function(){a(e.result)},e.onerror=function(){i(e.error)}})}function N(e){var a=new FileReader,i=x(a);return a.readAsArrayBuffer(e),i}function $(e){var a=new FileReader,i=x(a),u=/charset=([A-Za-z0-9_-]+)/.exec(e.type),f=u?u[1]:"utf-8";return a.readAsText(e,f),i}function Q(e){for(var a=new Uint8Array(e),i=new Array(a.length),u=0;u<a.length;u++)i[u]=String.fromCharCode(a[u]);return i.join("")}function B(e){if(e.slice)return e.slice(0);var a=new Uint8Array(e.byteLength);return a.set(new Uint8Array(e)),a.buffer}function J(){return this.bodyUsed=!1,this._initBody=function(e){this.bodyUsed=this.bodyUsed,this._bodyInit=e,e?typeof e=="string"?this._bodyText=e:s.blob&&Blob.prototype.isPrototypeOf(e)?this._bodyBlob=e:s.formData&&FormData.prototype.isPrototypeOf(e)?this._bodyFormData=e:s.searchParams&&URLSearchParams.prototype.isPrototypeOf(e)?this._bodyText=e.toString():s.arrayBuffer&&s.blob&&h(e)?(this._bodyArrayBuffer=B(e.buffer),this._bodyInit=new Blob([this._bodyArrayBuffer])):s.arrayBuffer&&(ArrayBuffer.prototype.isPrototypeOf(e)||p(e))?this._bodyArrayBuffer=B(e):this._bodyText=e=Object.prototype.toString.call(e):(this._noBody=!0,this._bodyText=""),this.headers.get("content-type")||(typeof e=="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(e)&&this.headers.set("content-type","application/x-www-form-urlencoded;charset=UTF-8"))},s.blob&&(this.blob=function(){var e=R(this);if(e)return e;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 e=R(this);return e||(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(N);throw new Error("could not read as ArrayBuffer")}},this.text=function(){var e=R(this);if(e)return e;if(this._bodyBlob)return $(this._bodyBlob);if(this._bodyArrayBuffer)return Promise.resolve(Q(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(j)}),this.json=function(){return this.text().then(JSON.parse)},this}var z=["CONNECT","DELETE","GET","HEAD","OPTIONS","PATCH","POST","PUT","TRACE"];function Y(e){var a=e.toUpperCase();return z.indexOf(a)>-1?a:e}function F(e,a){if(!(this instanceof F))throw new TypeError('Please use the "new" operator, this DOM object constructor cannot be called as a function.');a=a||{};var i=a.body;if(e instanceof F){if(e.bodyUsed)throw new TypeError("Already read");this.url=e.url,this.credentials=e.credentials,a.headers||(this.headers=new c(e.headers)),this.method=e.method,this.mode=e.mode,this.signal=e.signal,!i&&e._bodyInit!=null&&(i=e._bodyInit,e.bodyUsed=!0)}else this.url=String(e);if(this.credentials=a.credentials||this.credentials||"same-origin",(a.headers||!this.headers)&&(this.headers=new c(a.headers)),this.method=Y(a.method||this.method||"GET"),this.mode=a.mode||this.mode||null,this.signal=a.signal||this.signal||function(){if("AbortController"in o){var l=new AbortController;return l.signal}}(),this.referrer=null,(this.method==="GET"||this.method==="HEAD")&&i)throw new TypeError("Body not allowed for GET or HEAD requests");if(this._initBody(i),(this.method==="GET"||this.method==="HEAD")&&(a.cache==="no-store"||a.cache==="no-cache")){var u=/([?&])_=[^&]*/;if(u.test(this.url))this.url=this.url.replace(u,"$1_="+new Date().getTime());else{var f=/\?/;this.url+=(f.test(this.url)?"&":"?")+"_="+new Date().getTime()}}}F.prototype.clone=function(){return new F(this,{body:this._bodyInit})};function j(e){var a=new FormData;return e.trim().split("&").forEach(function(i){if(i){var u=i.split("="),f=u.shift().replace(/\+/g," "),l=u.join("=").replace(/\+/g," ");a.append(decodeURIComponent(f),decodeURIComponent(l))}}),a}function Z(e){var a=new c,i=e.replace(/\r?\n[\t ]+/g," ");return i.split("\r").map(function(u){return u.indexOf(`
2
- `)===0?u.substr(1,u.length):u}).forEach(function(u){var f=u.split(":"),l=f.shift().trim();if(l){var T=f.join(":").trim();try{a.append(l,T)}catch(A){console.warn("Response "+A.message)}}}),a}J.call(F.prototype);function b(e,a){if(!(this instanceof b))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 c(a.headers),this.url=a.url||"",this._initBody(e)}J.call(b.prototype),b.prototype.clone=function(){return new b(this._bodyInit,{status:this.status,statusText:this.statusText,headers:new c(this.headers),url:this.url})},b.error=function(){var e=new b(null,{status:200,statusText:""});return e.ok=!1,e.status=0,e.type="error",e};var ee=[301,302,303,307,308];b.redirect=function(e,a){if(ee.indexOf(a)===-1)throw new RangeError("Invalid status code");return new b(null,{status:a,headers:{location:e}})},n.DOMException=o.DOMException;try{new n.DOMException}catch{n.DOMException=function(a,i){this.message=a,this.name=i;var u=Error(a);this.stack=u.stack},n.DOMException.prototype=Object.create(Error.prototype),n.DOMException.prototype.constructor=n.DOMException}function O(e,a){return new Promise(function(i,u){var f=new F(e,a);if(f.signal&&f.signal.aborted)return u(new n.DOMException("Aborted","AbortError"));var l=new XMLHttpRequest;function T(){l.abort()}l.onload=function(){var y={statusText:l.statusText,headers:Z(l.getAllResponseHeaders()||"")};f.url.indexOf("file://")===0&&(l.status<200||l.status>599)?y.status=200:y.status=l.status,y.url="responseURL"in l?l.responseURL:y.headers.get("X-Request-URL");var C="response"in l?l.response:l.responseText;setTimeout(function(){i(new b(C,y))},0)},l.onerror=function(){setTimeout(function(){u(new TypeError("Network request failed"))},0)},l.ontimeout=function(){setTimeout(function(){u(new TypeError("Network request timed out"))},0)},l.onabort=function(){setTimeout(function(){u(new n.DOMException("Aborted","AbortError"))},0)};function A(y){try{return y===""&&o.location.href?o.location.href:y}catch{return y}}if(l.open(f.method,A(f.url),!0),f.credentials==="include"?l.withCredentials=!0:f.credentials==="omit"&&(l.withCredentials=!1),"responseType"in l&&(s.blob?l.responseType="blob":s.arrayBuffer&&(l.responseType="arraybuffer")),a&&typeof a.headers=="object"&&!(a.headers instanceof c||o.Headers&&a.headers instanceof o.Headers)){var V=[];Object.getOwnPropertyNames(a.headers).forEach(function(y){V.push(m(y)),l.setRequestHeader(y,E(a.headers[y]))}),f.headers.forEach(function(y,C){V.indexOf(C)===-1&&l.setRequestHeader(C,y)})}else f.headers.forEach(function(y,C){l.setRequestHeader(C,y)});f.signal&&(f.signal.addEventListener("abort",T),l.onreadystatechange=function(){l.readyState===4&&f.signal.removeEventListener("abort",T)}),l.send(typeof f._bodyInit>"u"?null:f._bodyInit)})}return O.polyfill=!0,o.fetch||(o.fetch=O,o.Headers=c,o.Request=F,o.Response=b),n.Headers=c,n.Request=F,n.Response=b,n.fetch=O,n}({})})(typeof self<"u"?self:q)});var g=[];for(let r=0;r<256;++r)g.push((r+256).toString(16).slice(1));function M(r,t=0){return(g[r[t+0]]+g[r[t+1]]+g[r[t+2]]+g[r[t+3]]+"-"+g[r[t+4]]+g[r[t+5]]+"-"+g[r[t+6]]+g[r[t+7]]+"-"+g[r[t+8]]+g[r[t+9]]+"-"+g[r[t+10]]+g[r[t+11]]+g[r[t+12]]+g[r[t+13]]+g[r[t+14]]+g[r[t+15]]).toLowerCase()}var U,ce=new Uint8Array(16);function L(){if(!U){if(typeof crypto>"u"||!crypto.getRandomValues)throw new Error("crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported");U=crypto.getRandomValues.bind(crypto)}return U(ce)}var ue=typeof crypto<"u"&&crypto.randomUUID&&crypto.randomUUID.bind(crypto),P={randomUUID:ue};function fe(r,t,n){if(P.randomUUID&&!t&&!r)return P.randomUUID();r=r||{};let o=r.random??r.rng?.()??L();if(o.length<16)throw new Error("Random bytes length must be >= 16");if(o[6]=o[6]&15|64,o[8]=o[8]&63|128,t){if(n=n||0,n<0||n+16>t.length)throw new RangeError(`UUID byte range ${n}:${n+15} is out of buffer bounds`);for(let s=0;s<16;++s)t[n+s]=o[s];return t}return M(o)}var w=fe;var ze=le(W());function S(r){return he(r,!1)}function he(r,t){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,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 _(r){return ye(r,!1)}function ye(r,t){return r==null?r:{data:S(r.data),params:r.params}}function K(r){return ge(r,!1)}function ge(r,t){return r==null?r:{flags:r.flags.map(S)}}function I(r){return me(r,!1)}function me(r,t){return r==null?r:{data:K(r.data),params:r.params}}var G=r=>{let{companyId:t,error:n,featureAllocation:o,featureUsage:s,featureUsagePeriod:h,featureUsageResetAt:d,flag:p,flagId:m,reason:E,ruleId:k,ruleType:c,userId:R,value:x}=S(r);return{featureUsageExceeded:!x&&(c=="company_override_usage_exceeded"||c=="plan_entitlement_usage_exceeded"),companyId:t??void 0,error:n??void 0,featureAllocation:o??void 0,featureUsage:s??void 0,featureUsagePeriod:h??void 0,featureUsageResetAt:d??void 0,flag:p,flagId:m??void 0,reason:E,ruleId:k??void 0,ruleType:c??void 0,userId:R??void 0,value:x}};function v(r){let t=Object.keys(r).reduce((n,o)=>{let h=Object.keys(r[o]||{}).sort().reduce((d,p)=>(d[p]=r[o][p],d),{});return n[o]=h,n},{});return JSON.stringify(t)}var X="schematicId";var D=class{additionalHeaders={};apiKey;apiUrl="https://api.schematichq.com";conn=null;context={};eventQueue;eventUrl="https://c.schematichq.com";flagCheckListeners={};flagValueListeners={};isPending=!0;isPendingListeners=new Set;storage;useWebSocket=!1;checks={};webSocketUrl="wss://api.schematichq.com";constructor(t,n){this.apiKey=t,this.eventQueue=[],this.useWebSocket=n?.useWebSocket??!1,n?.additionalHeaders&&(this.additionalHeaders=n.additionalHeaders),n?.storage?this.storage=n.storage:typeof localStorage<"u"&&(this.storage=localStorage),n?.apiUrl!==void 0&&(this.apiUrl=n.apiUrl),n?.eventUrl!==void 0&&(this.eventUrl=n.eventUrl),n?.webSocketUrl!==void 0&&(this.webSocketUrl=n.webSocketUrl),typeof window<"u"&&window?.addEventListener&&window.addEventListener("beforeunload",()=>{this.flushEventQueue()})}async checkFlag(t){let{fallback:n=!1,key:o}=t,s=t.context||this.context,h=v(s);if(!this.useWebSocket){let d=`${this.apiUrl}/flags/${o}/check`;return fetch(d,{method:"POST",headers:{...this.additionalHeaders??{},"Content-Type":"application/json;charset=UTF-8","X-Schematic-Api-Key":this.apiKey},body:JSON.stringify(s)}).then(p=>{if(!p.ok)throw new Error("Network response was not ok");return p.json()}).then(p=>_(p).data.value).catch(p=>(console.error("There was a problem with the fetch operation:",p),n))}try{let d=this.checks[h];if(this.conn&&typeof d<"u"&&typeof d[o]<"u")return d[o].value;try{await this.setContext(s)}catch(m){return console.error("WebSocket connection failed, falling back to REST:",m),this.fallbackToRest(o,s,n)}return(this.checks[h]??{})[o]?.value??n}catch(d){return console.error("Unexpected error in checkFlag:",d),n}}async fallbackToRest(t,n,o){try{let s=`${this.apiUrl}/flags/${t}/check`,h=await fetch(s,{method:"POST",headers:{...this.additionalHeaders??{},"Content-Type":"application/json;charset=UTF-8","X-Schematic-Api-Key":this.apiKey},body:JSON.stringify(n)});if(!h.ok)throw new Error("Network response was not ok");return _(await h.json())?.data?.value??!1}catch(s){return console.error("REST API call failed, using fallback value:",s),o}}checkFlags=async t=>{t=t||this.context;let n=`${this.apiUrl}/flags/check`,o=JSON.stringify(t);return fetch(n,{method:"POST",headers:{...this.additionalHeaders??{},"Content-Type":"application/json;charset=UTF-8","X-Schematic-Api-Key":this.apiKey},body:o}).then(s=>{if(!s.ok)throw new Error("Network response was not ok");return s.json()}).then(s=>(I(s)?.data?.flags??[]).reduce((d,p)=>(d[p.flag]=p.value,d),{})).catch(s=>(console.error("There was a problem with the fetch operation:",s),{}))};identify=t=>{try{this.setContext({company:t.company?.keys,user:t.keys})}catch(n){console.error("Error setting context:",n)}return this.handleEvent("identify",t)};setContext=async t=>{if(!this.useWebSocket)return this.context=t,Promise.resolve();try{this.setIsPending(!0),this.conn||(this.conn=this.wsConnect());let n=await this.conn;await this.wsSendMessage(n,t)}catch(n){throw console.error("Failed to establish WebSocket connection:",n),n}};track=t=>{let{company:n,user:o,event:s,traits:h}=t;return this.handleEvent("track",{company:n??this.context.company,event:s,traits:h??{},user:o??this.context.user})};flushEventQueue=()=>{for(;this.eventQueue.length>0;){let t=this.eventQueue.shift();t&&this.sendEvent(t)}};getAnonymousId=()=>{if(!this.storage)return w();let t=this.storage.getItem(X);if(typeof t<"u")return t;let n=w();return this.storage.setItem(X,n),n};handleEvent=(t,n)=>{let o={api_key:this.apiKey,body:n,sent_at:new Date().toISOString(),tracker_event_id:w(),tracker_user_id:this.getAnonymousId(),type:t};return document?.hidden?this.storeEvent(o):this.sendEvent(o)};sendEvent=async t=>{let n=`${this.eventUrl}/e`,o=JSON.stringify(t);try{await fetch(n,{method:"POST",headers:{...this.additionalHeaders??{},"Content-Type":"application/json;charset=UTF-8"},body:o})}catch(s){console.error("Error sending Schematic event: ",s)}return Promise.resolve()};storeEvent=t=>(this.eventQueue.push(t),Promise.resolve());cleanup=async()=>{if(this.conn)try{(await this.conn).close()}catch(t){console.error("Error during cleanup:",t)}finally{this.conn=null}};wsConnect=()=>new Promise((t,n)=>{let o=`${this.webSocketUrl}/flags/bootstrap`,s=new WebSocket(o);s.onopen=()=>{t(s)},s.onerror=h=>{n(h)},s.onclose=()=>{this.conn=null}});wsSendMessage=(t,n)=>new Promise((o,s)=>{if(v(n)==v(this.context))return o(this.setIsPending(!1));this.context=n;let h=()=>{let d=!1,p=m=>{let E=JSON.parse(m.data);v(n)in this.checks||(this.checks[v(n)]={}),(E.flags??[]).forEach(k=>{let c=G(k);this.checks[v(n)][c.flag]=c,this.notifyFlagCheckListeners(k.flag,c),this.notifyFlagValueListeners(k.flag,c.value)}),this.setIsPending(!1),d||(d=!0,o())};t.addEventListener("message",p),t.send(JSON.stringify({apiKey:this.apiKey,data:n}))};t.readyState===WebSocket.OPEN?h():t.readyState===WebSocket.CONNECTING?t.addEventListener("open",h):s("WebSocket is not open or connecting")});getIsPending=()=>this.isPending;addIsPendingListener=t=>(this.isPendingListeners.add(t),()=>{this.isPendingListeners.delete(t)});setIsPending=t=>{this.isPending=t,this.isPendingListeners.forEach(n=>be(n,t))};getFlagCheck=t=>{let n=v(this.context);return(this.checks[n]??{})[t]};getFlagValue=t=>this.getFlagCheck(t)?.value;addFlagValueListener=(t,n)=>(t in this.flagValueListeners||(this.flagValueListeners[t]=new Set),this.flagValueListeners[t].add(n),()=>{this.flagValueListeners[t].delete(n)});addFlagCheckListener=(t,n)=>(t in this.flagCheckListeners||(this.flagCheckListeners[t]=new Set),this.flagCheckListeners[t].add(n),()=>{this.flagCheckListeners[t].delete(n)});notifyFlagCheckListeners=(t,n)=>{(this.flagCheckListeners?.[t]??[]).forEach(s=>ke(s,n))};notifyFlagValueListeners=(t,n)=>{(this.flagValueListeners?.[t]??[]).forEach(s=>Fe(s,n))}},be=(r,t)=>{r.length>0?r(t):r()},ke=(r,t)=>{r.length>0?r(t):r()},Fe=(r,t)=>{r.length>0?r(t):r()};window.Schematic=D;})();
1
+ "use strict";(()=>{var ne=Object.create;var M=Object.defineProperty;var se=Object.getOwnPropertyDescriptor;var ae=Object.getOwnPropertyNames;var ie=Object.getPrototypeOf,oe=Object.prototype.hasOwnProperty;var le=(r,t)=>()=>(t||r((t={exports:{}}).exports,t),t.exports);var ce=(r,t,n,s)=>{if(t&&typeof t=="object"||typeof t=="function")for(let i of ae(t))!oe.call(r,i)&&i!==n&&M(r,i,{get:()=>t[i],enumerable:!(s=se(t,i))||s.enumerable});return r};var ue=(r,t,n)=>(n=r!=null?ne(ie(r)):{},ce(t||!r||!r.__esModule?M(n,"default",{value:r,enumerable:!0}):n,r));var z=le(K=>{(function(r){var t=function(n){var s=typeof globalThis<"u"&&globalThis||typeof r<"u"&&r||typeof global<"u"&&global||{},i={searchParams:"URLSearchParams"in s,iterable:"Symbol"in s&&"iterator"in Symbol,blob:"FileReader"in s&&"Blob"in s&&function(){try{return new Blob,!0}catch{return!1}}(),formData:"FormData"in s,arrayBuffer:"ArrayBuffer"in s};function c(e){return e&&DataView.prototype.isPrototypeOf(e)}if(i.arrayBuffer)var u=["[object Int8Array]","[object Uint8Array]","[object Uint8ClampedArray]","[object Int16Array]","[object Uint16Array]","[object Int32Array]","[object Uint32Array]","[object Float32Array]","[object Float64Array]"],f=ArrayBuffer.isView||function(e){return e&&u.indexOf(Object.prototype.toString.call(e))>-1};function p(e){if(typeof e!="string"&&(e=String(e)),/[^a-z0-9\-#$%&'*+.^_`|~!]/i.test(e)||e==="")throw new TypeError('Invalid character in header field name: "'+e+'"');return e.toLowerCase()}function k(e){return typeof e!="string"&&(e=String(e)),e}function E(e){var a={next:function(){var o=e.shift();return{done:o===void 0,value:o}}};return i.iterable&&(a[Symbol.iterator]=function(){return a}),a}function d(e){this.map={},e instanceof d?e.forEach(function(a,o){this.append(o,a)},this):Array.isArray(e)?e.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):e&&Object.getOwnPropertyNames(e).forEach(function(a){this.append(a,e[a])},this)}d.prototype.append=function(e,a){e=p(e),a=k(a);var o=this.map[e];this.map[e]=o?o+", "+a:a},d.prototype.delete=function(e){delete this.map[p(e)]},d.prototype.get=function(e){return e=p(e),this.has(e)?this.map[e]:null},d.prototype.has=function(e){return this.map.hasOwnProperty(p(e))},d.prototype.set=function(e,a){this.map[p(e)]=k(a)},d.prototype.forEach=function(e,a){for(var o in this.map)this.map.hasOwnProperty(o)&&e.call(a,this.map[o],o,this)},d.prototype.keys=function(){var e=[];return this.forEach(function(a,o){e.push(o)}),E(e)},d.prototype.values=function(){var e=[];return this.forEach(function(a){e.push(a)}),E(e)},d.prototype.entries=function(){var e=[];return this.forEach(function(a,o){e.push([o,a])}),E(e)},i.iterable&&(d.prototype[Symbol.iterator]=d.prototype.entries);function F(e){if(!e._noBody){if(e.bodyUsed)return Promise.reject(new TypeError("Already read"));e.bodyUsed=!0}}function b(e){return new Promise(function(a,o){e.onload=function(){a(e.result)},e.onerror=function(){o(e.error)}})}function V(e){var a=new FileReader,o=b(a);return a.readAsArrayBuffer(e),o}function Q(e){var a=new FileReader,o=b(a),h=/charset=([A-Za-z0-9_-]+)/.exec(e.type),g=h?h[1]:"utf-8";return a.readAsText(e,g),o}function Y(e){for(var a=new Uint8Array(e),o=new Array(a.length),h=0;h<a.length;h++)o[h]=String.fromCharCode(a[h]);return o.join("")}function q(e){if(e.slice)return e.slice(0);var a=new Uint8Array(e.byteLength);return a.set(new Uint8Array(e)),a.buffer}function H(){return this.bodyUsed=!1,this._initBody=function(e){this.bodyUsed=this.bodyUsed,this._bodyInit=e,e?typeof e=="string"?this._bodyText=e:i.blob&&Blob.prototype.isPrototypeOf(e)?this._bodyBlob=e:i.formData&&FormData.prototype.isPrototypeOf(e)?this._bodyFormData=e:i.searchParams&&URLSearchParams.prototype.isPrototypeOf(e)?this._bodyText=e.toString():i.arrayBuffer&&i.blob&&c(e)?(this._bodyArrayBuffer=q(e.buffer),this._bodyInit=new Blob([this._bodyArrayBuffer])):i.arrayBuffer&&(ArrayBuffer.prototype.isPrototypeOf(e)||f(e))?this._bodyArrayBuffer=q(e):this._bodyText=e=Object.prototype.toString.call(e):(this._noBody=!0,this._bodyText=""),this.headers.get("content-type")||(typeof e=="string"?this.headers.set("content-type","text/plain;charset=UTF-8"):this._bodyBlob&&this._bodyBlob.type?this.headers.set("content-type",this._bodyBlob.type):i.searchParams&&URLSearchParams.prototype.isPrototypeOf(e)&&this.headers.set("content-type","application/x-www-form-urlencoded;charset=UTF-8"))},i.blob&&(this.blob=function(){var e=F(this);if(e)return e;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 e=F(this);return e||(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(i.blob)return this.blob().then(V);throw new Error("could not read as ArrayBuffer")}},this.text=function(){var e=F(this);if(e)return e;if(this._bodyBlob)return Q(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)},i.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(e){var a=e.toUpperCase();return Z.indexOf(a)>-1?a:e}function C(e,a){if(!(this instanceof C))throw new TypeError('Please use the "new" operator, this DOM object constructor cannot be called as a function.');a=a||{};var o=a.body;if(e instanceof C){if(e.bodyUsed)throw new TypeError("Already read");this.url=e.url,this.credentials=e.credentials,a.headers||(this.headers=new d(e.headers)),this.method=e.method,this.mode=e.mode,this.signal=e.signal,!o&&e._bodyInit!=null&&(o=e._bodyInit,e.bodyUsed=!0)}else this.url=String(e);if(this.credentials=a.credentials||this.credentials||"same-origin",(a.headers||!this.headers)&&(this.headers=new d(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 s){var l=new AbortController;return l.signal}}(),this.referrer=null,(this.method==="GET"||this.method==="HEAD")&&o)throw new TypeError("Body not allowed for GET or HEAD requests");if(this._initBody(o),(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 g=/\?/;this.url+=(g.test(this.url)?"&":"?")+"_="+new Date().getTime()}}}C.prototype.clone=function(){return new C(this,{body:this._bodyInit})};function ee(e){var a=new FormData;return e.trim().split("&").forEach(function(o){if(o){var h=o.split("="),g=h.shift().replace(/\+/g," "),l=h.join("=").replace(/\+/g," ");a.append(decodeURIComponent(g),decodeURIComponent(l))}}),a}function te(e){var a=new d,o=e.replace(/\r?\n[\t ]+/g," ");return o.split("\r").map(function(h){return h.indexOf(`
2
+ `)===0?h.substr(1,h.length):h}).forEach(function(h){var g=h.split(":"),l=g.shift().trim();if(l){var x=g.join(":").trim();try{a.append(l,x)}catch(U){console.warn("Response "+U.message)}}}),a}H.call(C.prototype);function v(e,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 d(a.headers),this.url=a.url||"",this._initBody(e)}H.call(v.prototype),v.prototype.clone=function(){return new v(this._bodyInit,{status:this.status,statusText:this.statusText,headers:new d(this.headers),url:this.url})},v.error=function(){var e=new v(null,{status:200,statusText:""});return e.ok=!1,e.status=0,e.type="error",e};var re=[301,302,303,307,308];v.redirect=function(e,a){if(re.indexOf(a)===-1)throw new RangeError("Invalid status code");return new v(null,{status:a,headers:{location:e}})},n.DOMException=s.DOMException;try{new n.DOMException}catch{n.DOMException=function(a,o){this.message=a,this.name=o;var h=Error(a);this.stack=h.stack},n.DOMException.prototype=Object.create(Error.prototype),n.DOMException.prototype.constructor=n.DOMException}function I(e,a){return new Promise(function(o,h){var g=new C(e,a);if(g.signal&&g.signal.aborted)return h(new n.DOMException("Aborted","AbortError"));var l=new XMLHttpRequest;function x(){l.abort()}l.onload=function(){var y={statusText:l.statusText,headers:te(l.getAllResponseHeaders()||"")};g.url.indexOf("file://")===0&&(l.status<200||l.status>599)?y.status=200:y.status=l.status,y.url="responseURL"in l?l.responseURL:y.headers.get("X-Request-URL");var R="response"in l?l.response:l.responseText;setTimeout(function(){o(new v(R,y))},0)},l.onerror=function(){setTimeout(function(){h(new TypeError("Network request failed"))},0)},l.ontimeout=function(){setTimeout(function(){h(new TypeError("Network request timed out"))},0)},l.onabort=function(){setTimeout(function(){h(new n.DOMException("Aborted","AbortError"))},0)};function U(y){try{return y===""&&s.location.href?s.location.href:y}catch{return y}}if(l.open(g.method,U(g.url),!0),g.credentials==="include"?l.withCredentials=!0:g.credentials==="omit"&&(l.withCredentials=!1),"responseType"in l&&(i.blob?l.responseType="blob":i.arrayBuffer&&(l.responseType="arraybuffer")),a&&typeof a.headers=="object"&&!(a.headers instanceof d||s.Headers&&a.headers instanceof s.Headers)){var W=[];Object.getOwnPropertyNames(a.headers).forEach(function(y){W.push(p(y)),l.setRequestHeader(y,k(a.headers[y]))}),g.headers.forEach(function(y,R){W.indexOf(R)===-1&&l.setRequestHeader(R,y)})}else g.headers.forEach(function(y,R){l.setRequestHeader(R,y)});g.signal&&(g.signal.addEventListener("abort",x),l.onreadystatechange=function(){l.readyState===4&&g.signal.removeEventListener("abort",x)}),l.send(typeof g._bodyInit>"u"?null:g._bodyInit)})}return I.polyfill=!0,s.fetch||(s.fetch=I,s.Headers=d,s.Request=C,s.Response=v),n.Headers=d,n.Request=C,n.Response=v,n.fetch=I,n}({})})(typeof self<"u"?self:K)});var m=[];for(let r=0;r<256;++r)m.push((r+256).toString(16).slice(1));function $(r,t=0){return(m[r[t+0]]+m[r[t+1]]+m[r[t+2]]+m[r[t+3]]+"-"+m[r[t+4]]+m[r[t+5]]+"-"+m[r[t+6]]+m[r[t+7]]+"-"+m[r[t+8]]+m[r[t+9]]+"-"+m[r[t+10]]+m[r[t+11]]+m[r[t+12]]+m[r[t+13]]+m[r[t+14]]+m[r[t+15]]).toLowerCase()}var P,de=new Uint8Array(16);function A(){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(r,t,n){if(L.randomUUID&&!t&&!r)return L.randomUUID();r=r||{};let s=r.random??r.rng?.()??A();if(s.length<16)throw new Error("Random bytes length must be >= 16");if(s[6]=s[6]&15|64,s[8]=s[8]&63|128,t){if(n=n||0,n<0||n+16>t.length)throw new RangeError(`UUID byte range ${n}:${n+15} is out of buffer bounds`);for(let i=0;i<16;++i)t[n+i]=s[i];return t}return $(s)}var _=he;var rt=ue(z());function w(r){return ge(r,!1)}function ge(r,t){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,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 B(r){return ye(r,!1)}function ye(r,t=!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 T(r){return be(r,!1)}function be(r,t){return r==null?r:{data:w(r.data),params:r.params}}function X(r){return ke(r,!1)}function ke(r,t){return r==null?r:{flags:r.flags.map(w)}}function N(r){return Fe(r,!1)}function Fe(r,t){return r==null?r:{data:X(r.data),params:r.params}}var O=r=>{let{companyId:t,error:n,featureAllocation:s,featureUsage:i,featureUsagePeriod:c,featureUsageResetAt:u,flag:f,flagId:p,reason:k,ruleId:E,ruleType:d,userId:F,value:b}=w(r);return{featureUsageExceeded:!b&&(d=="company_override_usage_exceeded"||d=="plan_entitlement_usage_exceeded"),companyId:t??void 0,error:n??void 0,featureAllocation:s??void 0,featureUsage:i??void 0,featureUsagePeriod:c??void 0,featureUsageResetAt:u??void 0,flag:f,flagId:p??void 0,reason:k,ruleId:E??void 0,ruleType:d??void 0,userId:F??void 0,value:b}};function S(r){let t=Object.keys(r).reduce((n,s)=>{let c=Object.keys(r[s]||{}).sort().reduce((u,f)=>(u[f]=r[s][f],u),{});return n[s]=c,n},{});return JSON.stringify(t)}var J="1.2.2";var G="schematicId";var D=class{additionalHeaders={};apiKey;apiUrl="https://api.schematichq.com";conn=null;context={};debugEnabled=!1;offlineEnabled=!1;eventQueue;eventUrl="https://c.schematichq.com";flagCheckListeners={};flagValueListeners={};isPending=!0;isPendingListeners=new Set;storage;useWebSocket=!1;checks={};webSocketUrl="wss://api.schematichq.com";constructor(t,n){if(this.apiKey=t,this.eventQueue=[],this.useWebSocket=n?.useWebSocket??!1,this.debugEnabled=n?.debug??!1,this.offlineEnabled=n?.offline??!1,typeof window<"u"&&typeof window.location<"u"){let s=new URLSearchParams(window.location.search),i=s.get("schematic_debug");i!==null&&(i===""||i==="true"||i==="1")&&(this.debugEnabled=!0);let c=s.get("schematic_offline");c!==null&&(c===""||c==="true"||c==="1")&&(this.offlineEnabled=!0,this.debugEnabled=!0)}this.offlineEnabled&&n?.debug!==!1&&(this.debugEnabled=!0),this.offlineEnabled&&this.setIsPending(!1),this.additionalHeaders={"X-Schematic-Client-Version":`schematic-js@${J}`,...n?.additionalHeaders??{}},n?.storage?this.storage=n.storage:typeof localStorage<"u"&&(this.storage=localStorage),n?.apiUrl!==void 0&&(this.apiUrl=n.apiUrl),n?.eventUrl!==void 0&&(this.eventUrl=n.eventUrl),n?.webSocketUrl!==void 0&&(this.webSocketUrl=n.webSocketUrl),typeof window<"u"&&window?.addEventListener&&window.addEventListener("beforeunload",()=>{this.flushEventQueue()}),this.offlineEnabled?this.debug("Initialized with offline mode enabled - no network requests will be made"):this.debugEnabled&&this.debug("Initialized with debug mode enabled")}async checkFlag(t){let{fallback:n=!1,key:s}=t,i=t.context||this.context,c=S(i);if(this.debug(`checkFlag: ${s}`,{context:i,fallback:n}),this.isOffline())return this.debug(`checkFlag offline result: ${s}`,{value:n,offlineMode:!0}),n;if(!this.useWebSocket){let u=`${this.apiUrl}/flags/${s}/check`;return fetch(u,{method:"POST",headers:{...this.additionalHeaders??{},"Content-Type":"application/json;charset=UTF-8","X-Schematic-Api-Key":this.apiKey},body:JSON.stringify(i)}).then(f=>{if(!f.ok)throw new Error("Network response was not ok");return f.json()}).then(f=>{let p=T(f);this.debug(`checkFlag result: ${s}`,p);let k=O(p.data);return this.submitFlagCheckEvent(s,k,i),k.value}).catch(f=>{console.error("There was a problem with the fetch operation:",f);let p={flag:s,value:n,reason:"API request failed",error:f instanceof Error?f.message:String(f)};return this.submitFlagCheckEvent(s,p,i),n})}try{let u=this.checks[c];if(this.conn!==null&&typeof u<"u"&&typeof u[s]<"u")return this.debug(`checkFlag cached result: ${s}`,u[s]),u[s].value;if(this.isOffline())return n;try{await this.setContext(i)}catch(E){return console.error("WebSocket connection failed, falling back to REST:",E),this.fallbackToRest(s,i,n)}let p=(this.checks[c]??{})[s],k=p?.value??n;return this.debug(`checkFlag WebSocket result: ${s}`,typeof p<"u"?p:{value:n,fallbackUsed:!0}),typeof p<"u"&&this.submitFlagCheckEvent(s,p,i),k}catch(u){console.error("Unexpected error in checkFlag:",u);let f={flag:s,value:n,reason:"Unexpected error in flag check",error:u instanceof Error?u.message:String(u)};return this.submitFlagCheckEvent(s,f,i),n}}debug(t,...n){this.debugEnabled&&console.log(`[Schematic] ${t}`,...n)}isOffline(){return this.offlineEnabled}submitFlagCheckEvent(t,n,s){let i={flagKey:t,value:n.value,reason:n.reason,flagId:n.flagId,ruleId:n.ruleId,companyId:n.companyId,userId:n.userId,error:n.error,reqCompany:s.company,reqUser:s.user};return this.debug("submitting flag check event:",i),this.handleEvent("flag_check",B(i))}async fallbackToRest(t,n,s){if(this.isOffline())return this.debug(`fallbackToRest offline result: ${t}`,{value:s,offlineMode:!0}),s;try{let i=`${this.apiUrl}/flags/${t}/check`,c=await fetch(i,{method:"POST",headers:{...this.additionalHeaders??{},"Content-Type":"application/json;charset=UTF-8","X-Schematic-Api-Key":this.apiKey},body:JSON.stringify(n)});if(!c.ok)throw new Error("Network response was not ok");let u=await c.json(),f=T(u);this.debug(`fallbackToRest result: ${t}`,f);let p=O(f.data);return this.submitFlagCheckEvent(t,p,n),p.value}catch(i){console.error("REST API call failed, using fallback value:",i);let c={flag:t,value:s,reason:"API request failed (fallback)",error:i instanceof Error?i.message:String(i)};return this.submitFlagCheckEvent(t,c,n),s}}checkFlags=async t=>{if(t=t||this.context,this.debug("checkFlags",{context:t}),this.isOffline())return this.debug("checkFlags offline result: returning empty object"),{};let n=`${this.apiUrl}/flags/check`,s=JSON.stringify(t);return fetch(n,{method:"POST",headers:{...this.additionalHeaders??{},"Content-Type":"application/json;charset=UTF-8","X-Schematic-Api-Key":this.apiKey},body:s}).then(i=>{if(!i.ok)throw new Error("Network response was not ok");return i.json()}).then(i=>{let c=N(i);return this.debug("checkFlags result:",c),(c?.data?.flags??[]).reduce((u,f)=>(u[f.flag]=f.value,u),{})}).catch(i=>(console.error("There was a problem with the fetch operation:",i),{}))};identify=t=>{this.debug("identify:",t);try{this.setContext({company:t.company?.keys,user:t.keys})}catch(n){console.error("Error setting context:",n)}return this.handleEvent("identify",t)};setContext=async t=>{if(this.isOffline())return this.context=t,this.setIsPending(!1),Promise.resolve();if(!this.useWebSocket)return Promise.resolve();try{this.setIsPending(!0),this.conn||(this.conn=this.wsConnect());let n=await this.conn;await this.wsSendMessage(n,t)}catch(n){throw console.error("Failed to establish WebSocket connection:",n),n}};track=t=>{let{company:n,user:s,event:i,traits:c}=t,u={company:n??this.context.company,event:i,traits:c??{},user:s??this.context.user};return this.debug("track:",u),this.handleEvent("track",u)};flushEventQueue=()=>{for(;this.eventQueue.length>0;){let t=this.eventQueue.shift();t&&this.sendEvent(t)}};getAnonymousId=()=>{if(!this.storage)return _();let t=this.storage.getItem(G);if(typeof t<"u")return t;let n=_();return this.storage.setItem(G,n),n};handleEvent=(t,n)=>{let s={api_key:this.apiKey,body:n,sent_at:new Date().toISOString(),tracker_event_id:_(),tracker_user_id:this.getAnonymousId(),type:t};return document?.hidden?this.storeEvent(s):this.sendEvent(s)};sendEvent=async t=>{let n=`${this.eventUrl}/e`,s=JSON.stringify(t);if(this.debug("sending event:",{url:n,event:t}),this.isOffline())return this.debug("event not sent (offline mode):",{event:t}),Promise.resolve();try{let i=await fetch(n,{method:"POST",headers:{...this.additionalHeaders??{},"Content-Type":"application/json;charset=UTF-8"},body:s});this.debug("event sent:",{status:i.status,statusText:i.statusText})}catch(i){console.error("Error sending Schematic event: ",i)}return Promise.resolve()};storeEvent=t=>(this.eventQueue.push(t),Promise.resolve());cleanup=async()=>{if(this.isOffline())return this.debug("cleanup: skipped (offline mode)"),Promise.resolve();if(this.conn)try{(await this.conn).close()}catch(t){console.error("Error during cleanup:",t)}finally{this.conn=null}};wsConnect=()=>this.isOffline()?(this.debug("wsConnect: skipped (offline mode)"),Promise.reject(new Error("WebSocket connection skipped in offline mode"))):new Promise((t,n)=>{let s=`${this.webSocketUrl}/flags/bootstrap?apiKey=${this.apiKey}`;this.debug("connecting to WebSocket:",s);let i=new WebSocket(s);i.onopen=()=>{this.debug("WebSocket connection opened"),t(i)},i.onerror=c=>{this.debug("WebSocket connection error:",c),n(c)},i.onclose=()=>{this.debug("WebSocket connection closed"),this.conn=null}});wsSendMessage=(t,n)=>this.isOffline()?(this.debug("wsSendMessage: skipped (offline mode)"),this.setIsPending(!1),Promise.resolve()):new Promise((s,i)=>{if(S(n)==S(this.context))return this.debug("WebSocket context unchanged, skipping update"),s(this.setIsPending(!1));this.debug("WebSocket context updated:",n),this.context=n;let c=()=>{let u=!1,f=E=>{let d=JSON.parse(E.data);this.debug("WebSocket message received:",d),S(n)in this.checks||(this.checks[S(n)]={}),(d.flags??[]).forEach(F=>{let b=O(F);this.checks[S(n)][b.flag]=b,this.debug("WebSocket flag update:",{flag:b.flag,value:b.value,flagCheck:b}),(this.flagCheckListeners[F.flag]?.size>0||this.flagValueListeners[F.flag]?.size>0)&&this.submitFlagCheckEvent(b.flag,b,n),this.notifyFlagCheckListeners(F.flag,b),this.notifyFlagValueListeners(F.flag,b.value)}),this.setIsPending(!1),u||(u=!0,s())};t.addEventListener("message",f);let p=this.additionalHeaders["X-Schematic-Client-Version"]??`schematic-js@${J}`,k={apiKey:this.apiKey,clientVersion:p,data:n};this.debug("WebSocket sending message:",k),t.send(JSON.stringify(k))};t.readyState===WebSocket.OPEN?(this.debug("WebSocket already open, sending message"),c()):t.readyState===WebSocket.CONNECTING?(this.debug("WebSocket connecting, waiting for open to send message"),t.addEventListener("open",c)):(this.debug("WebSocket is closed, cannot send message"),i("WebSocket is not open or connecting"))});getIsPending=()=>this.isPending;addIsPendingListener=t=>(this.isPendingListeners.add(t),()=>{this.isPendingListeners.delete(t)});setIsPending=t=>{this.isPending=t,this.isPendingListeners.forEach(n=>ve(n,t))};getFlagCheck=t=>{let n=S(this.context);return(this.checks[n]??{})[t]};getFlagValue=t=>this.getFlagCheck(t)?.value;addFlagValueListener=(t,n)=>(t in this.flagValueListeners||(this.flagValueListeners[t]=new Set),this.flagValueListeners[t].add(n),()=>{this.flagValueListeners[t].delete(n)});addFlagCheckListener=(t,n)=>(t in this.flagCheckListeners||(this.flagCheckListeners[t]=new Set),this.flagCheckListeners[t].add(n),()=>{this.flagCheckListeners[t].delete(n)});notifyFlagCheckListeners=(t,n)=>{let s=this.flagCheckListeners?.[t]??[];s.size>0&&this.debug(`Notifying ${s.size} flag check listeners for ${t}`,n),s.forEach(i=>Ee(i,n))};notifyFlagValueListeners=(t,n)=>{let s=this.flagValueListeners?.[t]??[];s.size>0&&this.debug(`Notifying ${s.size} flag value listeners for ${t}`,{value:n}),s.forEach(i=>Ce(i,n))}},ve=(r,t)=>{r.length>0?r(t):r()},Ee=(r,t)=>{r.length>0?r(t):r()},Ce=(r,t)=>{r.length>0?r(t):r()};window.Schematic=D;})();
3
3
  /* @preserve */
@@ -572,6 +572,7 @@ __export(index_exports, {
572
572
  CheckFlagResponseFromJSON: () => CheckFlagResponseFromJSON,
573
573
  CheckFlagReturnFromJSON: () => CheckFlagReturnFromJSON,
574
574
  CheckFlagsResponseFromJSON: () => CheckFlagsResponseFromJSON,
575
+ EventBodyFlagCheckToJSON: () => EventBodyFlagCheckToJSON,
575
576
  RuleType: () => RuleType,
576
577
  Schematic: () => Schematic,
577
578
  UsagePeriod: () => UsagePeriod
@@ -658,6 +659,28 @@ function CheckFlagResponseDataFromJSONTyped(json, ignoreDiscriminator) {
658
659
  };
659
660
  }
660
661
 
662
+ // src/types/api/models/EventBodyFlagCheck.ts
663
+ function EventBodyFlagCheckToJSON(json) {
664
+ return EventBodyFlagCheckToJSONTyped(json, false);
665
+ }
666
+ function EventBodyFlagCheckToJSONTyped(value, ignoreDiscriminator = false) {
667
+ if (value == null) {
668
+ return value;
669
+ }
670
+ return {
671
+ company_id: value["companyId"],
672
+ error: value["error"],
673
+ flag_id: value["flagId"],
674
+ flag_key: value["flagKey"],
675
+ reason: value["reason"],
676
+ req_company: value["reqCompany"],
677
+ req_user: value["reqUser"],
678
+ rule_id: value["ruleId"],
679
+ user_id: value["userId"],
680
+ value: value["value"]
681
+ };
682
+ }
683
+
661
684
  // src/types/api/models/CheckFlagResponse.ts
662
685
  function CheckFlagResponseFromJSON(json) {
663
686
  return CheckFlagResponseFromJSONTyped(json, false);
@@ -770,6 +793,9 @@ function contextString(context) {
770
793
  return JSON.stringify(sortedContext);
771
794
  }
772
795
 
796
+ // src/version.ts
797
+ var version = "1.2.2";
798
+
773
799
  // src/index.ts
774
800
  var anonymousIdKey = "schematicId";
775
801
  var Schematic = class {
@@ -778,6 +804,8 @@ var Schematic = class {
778
804
  apiUrl = "https://api.schematichq.com";
779
805
  conn = null;
780
806
  context = {};
807
+ debugEnabled = false;
808
+ offlineEnabled = false;
781
809
  eventQueue;
782
810
  eventUrl = "https://c.schematichq.com";
783
811
  flagCheckListeners = {};
@@ -792,9 +820,30 @@ var Schematic = class {
792
820
  this.apiKey = apiKey;
793
821
  this.eventQueue = [];
794
822
  this.useWebSocket = options?.useWebSocket ?? false;
795
- if (options?.additionalHeaders) {
796
- this.additionalHeaders = options.additionalHeaders;
823
+ this.debugEnabled = options?.debug ?? false;
824
+ this.offlineEnabled = options?.offline ?? false;
825
+ if (typeof window !== "undefined" && typeof window.location !== "undefined") {
826
+ const params = new URLSearchParams(window.location.search);
827
+ const debugParam = params.get("schematic_debug");
828
+ if (debugParam !== null && (debugParam === "" || debugParam === "true" || debugParam === "1")) {
829
+ this.debugEnabled = true;
830
+ }
831
+ const offlineParam = params.get("schematic_offline");
832
+ if (offlineParam !== null && (offlineParam === "" || offlineParam === "true" || offlineParam === "1")) {
833
+ this.offlineEnabled = true;
834
+ this.debugEnabled = true;
835
+ }
836
+ }
837
+ if (this.offlineEnabled && options?.debug !== false) {
838
+ this.debugEnabled = true;
797
839
  }
840
+ if (this.offlineEnabled) {
841
+ this.setIsPending(false);
842
+ }
843
+ this.additionalHeaders = {
844
+ "X-Schematic-Client-Version": `schematic-js@${version}`,
845
+ ...options?.additionalHeaders ?? {}
846
+ };
798
847
  if (options?.storage) {
799
848
  this.storage = options.storage;
800
849
  } else if (typeof localStorage !== "undefined") {
@@ -814,6 +863,13 @@ var Schematic = class {
814
863
  this.flushEventQueue();
815
864
  });
816
865
  }
866
+ if (this.offlineEnabled) {
867
+ this.debug(
868
+ "Initialized with offline mode enabled - no network requests will be made"
869
+ );
870
+ } else if (this.debugEnabled) {
871
+ this.debug("Initialized with debug mode enabled");
872
+ }
817
873
  }
818
874
  /**
819
875
  * Get value for a single flag.
@@ -826,6 +882,14 @@ var Schematic = class {
826
882
  const { fallback = false, key } = options;
827
883
  const context = options.context || this.context;
828
884
  const contextStr = contextString(context);
885
+ this.debug(`checkFlag: ${key}`, { context, fallback });
886
+ if (this.isOffline()) {
887
+ this.debug(`checkFlag offline result: ${key}`, {
888
+ value: fallback,
889
+ offlineMode: true
890
+ });
891
+ return fallback;
892
+ }
829
893
  if (!this.useWebSocket) {
830
894
  const requestUrl = `${this.apiUrl}/flags/${key}/check`;
831
895
  return fetch(requestUrl, {
@@ -842,17 +906,32 @@ var Schematic = class {
842
906
  }
843
907
  return response.json();
844
908
  }).then((response) => {
845
- return CheckFlagResponseFromJSON(response).data.value;
909
+ const parsedResponse = CheckFlagResponseFromJSON(response);
910
+ this.debug(`checkFlag result: ${key}`, parsedResponse);
911
+ const result = CheckFlagReturnFromJSON(parsedResponse.data);
912
+ this.submitFlagCheckEvent(key, result, context);
913
+ return result.value;
846
914
  }).catch((error) => {
847
915
  console.error("There was a problem with the fetch operation:", error);
916
+ const errorResult = {
917
+ flag: key,
918
+ value: fallback,
919
+ reason: "API request failed",
920
+ error: error instanceof Error ? error.message : String(error)
921
+ };
922
+ this.submitFlagCheckEvent(key, errorResult, context);
848
923
  return fallback;
849
924
  });
850
925
  }
851
926
  try {
852
927
  const existingVals = this.checks[contextStr];
853
- if (this.conn && typeof existingVals !== "undefined" && typeof existingVals[key] !== "undefined") {
928
+ if (this.conn !== null && typeof existingVals !== "undefined" && typeof existingVals[key] !== "undefined") {
929
+ this.debug(`checkFlag cached result: ${key}`, existingVals[key]);
854
930
  return existingVals[key].value;
855
931
  }
932
+ if (this.isOffline()) {
933
+ return fallback;
934
+ }
856
935
  try {
857
936
  await this.setContext(context);
858
937
  } catch (error) {
@@ -863,16 +942,74 @@ var Schematic = class {
863
942
  return this.fallbackToRest(key, context, fallback);
864
943
  }
865
944
  const contextVals = this.checks[contextStr] ?? {};
866
- return contextVals[key]?.value ?? fallback;
945
+ const flagCheck = contextVals[key];
946
+ const result = flagCheck?.value ?? fallback;
947
+ this.debug(
948
+ `checkFlag WebSocket result: ${key}`,
949
+ typeof flagCheck !== "undefined" ? flagCheck : { value: fallback, fallbackUsed: true }
950
+ );
951
+ if (typeof flagCheck !== "undefined") {
952
+ this.submitFlagCheckEvent(key, flagCheck, context);
953
+ }
954
+ return result;
867
955
  } catch (error) {
868
956
  console.error("Unexpected error in checkFlag:", error);
957
+ const errorResult = {
958
+ flag: key,
959
+ value: fallback,
960
+ reason: "Unexpected error in flag check",
961
+ error: error instanceof Error ? error.message : String(error)
962
+ };
963
+ this.submitFlagCheckEvent(key, errorResult, context);
869
964
  return fallback;
870
965
  }
871
966
  }
967
+ /**
968
+ * Helper function to log debug messages
969
+ * Only logs if debug mode is enabled
970
+ */
971
+ debug(message, ...args) {
972
+ if (this.debugEnabled) {
973
+ console.log(`[Schematic] ${message}`, ...args);
974
+ }
975
+ }
976
+ /**
977
+ * Helper function to check if client is in offline mode
978
+ */
979
+ isOffline() {
980
+ return this.offlineEnabled;
981
+ }
982
+ /**
983
+ * Submit a flag check event
984
+ * Records data about a flag check for analytics
985
+ */
986
+ submitFlagCheckEvent(flagKey, result, context) {
987
+ const eventBody = {
988
+ flagKey,
989
+ value: result.value,
990
+ reason: result.reason,
991
+ flagId: result.flagId,
992
+ ruleId: result.ruleId,
993
+ companyId: result.companyId,
994
+ userId: result.userId,
995
+ error: result.error,
996
+ reqCompany: context.company,
997
+ reqUser: context.user
998
+ };
999
+ this.debug(`submitting flag check event:`, eventBody);
1000
+ return this.handleEvent("flag_check", EventBodyFlagCheckToJSON(eventBody));
1001
+ }
872
1002
  /**
873
1003
  * Helper method for falling back to REST API when WebSocket connection fails
874
1004
  */
875
1005
  async fallbackToRest(key, context, fallback) {
1006
+ if (this.isOffline()) {
1007
+ this.debug(`fallbackToRest offline result: ${key}`, {
1008
+ value: fallback,
1009
+ offlineMode: true
1010
+ });
1011
+ return fallback;
1012
+ }
876
1013
  try {
877
1014
  const requestUrl = `${this.apiUrl}/flags/${key}/check`;
878
1015
  const response = await fetch(requestUrl, {
@@ -887,19 +1024,36 @@ var Schematic = class {
887
1024
  if (!response.ok) {
888
1025
  throw new Error("Network response was not ok");
889
1026
  }
890
- const data = CheckFlagResponseFromJSON(await response.json());
891
- return data?.data?.value ?? false;
1027
+ const responseJson = await response.json();
1028
+ const data = CheckFlagResponseFromJSON(responseJson);
1029
+ this.debug(`fallbackToRest result: ${key}`, data);
1030
+ const result = CheckFlagReturnFromJSON(data.data);
1031
+ this.submitFlagCheckEvent(key, result, context);
1032
+ return result.value;
892
1033
  } catch (error) {
893
1034
  console.error("REST API call failed, using fallback value:", error);
1035
+ const errorResult = {
1036
+ flag: key,
1037
+ value: fallback,
1038
+ reason: "API request failed (fallback)",
1039
+ error: error instanceof Error ? error.message : String(error)
1040
+ };
1041
+ this.submitFlagCheckEvent(key, errorResult, context);
894
1042
  return fallback;
895
1043
  }
896
1044
  }
897
1045
  /**
898
1046
  * Make an API call to fetch all flag values for a given context.
899
1047
  * Recommended for use in REST mode only.
1048
+ * In offline mode, returns an empty object.
900
1049
  */
901
1050
  checkFlags = async (context) => {
902
1051
  context = context || this.context;
1052
+ this.debug(`checkFlags`, { context });
1053
+ if (this.isOffline()) {
1054
+ this.debug(`checkFlags offline result: returning empty object`);
1055
+ return {};
1056
+ }
903
1057
  const requestUrl = `${this.apiUrl}/flags/check`;
904
1058
  const requestBody = JSON.stringify(context);
905
1059
  return fetch(requestUrl, {
@@ -917,6 +1071,7 @@ var Schematic = class {
917
1071
  return response.json();
918
1072
  }).then((responseJson) => {
919
1073
  const resp = CheckFlagsResponseFromJSON(responseJson);
1074
+ this.debug(`checkFlags result:`, resp);
920
1075
  return (resp?.data?.flags ?? []).reduce(
921
1076
  (accum, flag) => {
922
1077
  accum[flag.flag] = flag.value;
@@ -935,6 +1090,7 @@ var Schematic = class {
935
1090
  * send an identify event to the Schematic API which will upsert a user and company.
936
1091
  */
937
1092
  identify = (body) => {
1093
+ this.debug(`identify:`, body);
938
1094
  try {
939
1095
  this.setContext({
940
1096
  company: body.company?.keys,
@@ -952,10 +1108,15 @@ var Schematic = class {
952
1108
  * 2. Send the context to the server
953
1109
  * 3. Wait for initial flag values to be returned
954
1110
  * The promise resolves when initial flag values are received.
1111
+ * In offline mode, this will just set the context locally without connecting.
955
1112
  */
956
1113
  setContext = async (context) => {
957
- if (!this.useWebSocket) {
1114
+ if (this.isOffline()) {
958
1115
  this.context = context;
1116
+ this.setIsPending(false);
1117
+ return Promise.resolve();
1118
+ }
1119
+ if (!this.useWebSocket) {
959
1120
  return Promise.resolve();
960
1121
  }
961
1122
  try {
@@ -976,12 +1137,14 @@ var Schematic = class {
976
1137
  */
977
1138
  track = (body) => {
978
1139
  const { company, user, event, traits } = body;
979
- return this.handleEvent("track", {
1140
+ const trackData = {
980
1141
  company: company ?? this.context.company,
981
1142
  event,
982
1143
  traits: traits ?? {},
983
1144
  user: user ?? this.context.user
984
- });
1145
+ };
1146
+ this.debug(`track:`, trackData);
1147
+ return this.handleEvent("track", trackData);
985
1148
  };
986
1149
  /**
987
1150
  * Event processing
@@ -1024,8 +1187,13 @@ var Schematic = class {
1024
1187
  sendEvent = async (event) => {
1025
1188
  const captureUrl = `${this.eventUrl}/e`;
1026
1189
  const payload = JSON.stringify(event);
1190
+ this.debug(`sending event:`, { url: captureUrl, event });
1191
+ if (this.isOffline()) {
1192
+ this.debug(`event not sent (offline mode):`, { event });
1193
+ return Promise.resolve();
1194
+ }
1027
1195
  try {
1028
- await fetch(captureUrl, {
1196
+ const response = await fetch(captureUrl, {
1029
1197
  method: "POST",
1030
1198
  headers: {
1031
1199
  ...this.additionalHeaders ?? {},
@@ -1033,6 +1201,10 @@ var Schematic = class {
1033
1201
  },
1034
1202
  body: payload
1035
1203
  });
1204
+ this.debug(`event sent:`, {
1205
+ status: response.status,
1206
+ statusText: response.statusText
1207
+ });
1036
1208
  } catch (error) {
1037
1209
  console.error("Error sending Schematic event: ", error);
1038
1210
  }
@@ -1047,8 +1219,13 @@ var Schematic = class {
1047
1219
  */
1048
1220
  /**
1049
1221
  * If using websocket mode, close the connection when done.
1222
+ * In offline mode, this is a no-op.
1050
1223
  */
1051
1224
  cleanup = async () => {
1225
+ if (this.isOffline()) {
1226
+ this.debug("cleanup: skipped (offline mode)");
1227
+ return Promise.resolve();
1228
+ }
1052
1229
  if (this.conn) {
1053
1230
  try {
1054
1231
  const socket = await this.conn;
@@ -1062,16 +1239,26 @@ var Schematic = class {
1062
1239
  };
1063
1240
  // Open a websocket connection
1064
1241
  wsConnect = () => {
1242
+ if (this.isOffline()) {
1243
+ this.debug("wsConnect: skipped (offline mode)");
1244
+ return Promise.reject(
1245
+ new Error("WebSocket connection skipped in offline mode")
1246
+ );
1247
+ }
1065
1248
  return new Promise((resolve, reject) => {
1066
- const wsUrl = `${this.webSocketUrl}/flags/bootstrap`;
1249
+ const wsUrl = `${this.webSocketUrl}/flags/bootstrap?apiKey=${this.apiKey}`;
1250
+ this.debug(`connecting to WebSocket:`, wsUrl);
1067
1251
  const webSocket = new WebSocket(wsUrl);
1068
1252
  webSocket.onopen = () => {
1253
+ this.debug(`WebSocket connection opened`);
1069
1254
  resolve(webSocket);
1070
1255
  };
1071
1256
  webSocket.onerror = (error) => {
1257
+ this.debug(`WebSocket connection error:`, error);
1072
1258
  reject(error);
1073
1259
  };
1074
1260
  webSocket.onclose = () => {
1261
+ this.debug(`WebSocket connection closed`);
1075
1262
  this.conn = null;
1076
1263
  };
1077
1264
  });
@@ -1079,21 +1266,37 @@ var Schematic = class {
1079
1266
  // Send a message on the websocket indicating interest in a particular evaluation context
1080
1267
  // and wait for the initial set of flag values to be returned
1081
1268
  wsSendMessage = (socket, context) => {
1269
+ if (this.isOffline()) {
1270
+ this.debug("wsSendMessage: skipped (offline mode)");
1271
+ this.setIsPending(false);
1272
+ return Promise.resolve();
1273
+ }
1082
1274
  return new Promise((resolve, reject) => {
1083
1275
  if (contextString(context) == contextString(this.context)) {
1276
+ this.debug(`WebSocket context unchanged, skipping update`);
1084
1277
  return resolve(this.setIsPending(false));
1085
1278
  }
1279
+ this.debug(`WebSocket context updated:`, context);
1086
1280
  this.context = context;
1087
1281
  const sendMessage = () => {
1088
1282
  let resolved = false;
1089
1283
  const messageHandler = (event) => {
1090
1284
  const message = JSON.parse(event.data);
1285
+ this.debug(`WebSocket message received:`, message);
1091
1286
  if (!(contextString(context) in this.checks)) {
1092
1287
  this.checks[contextString(context)] = {};
1093
1288
  }
1094
1289
  (message.flags ?? []).forEach((flag) => {
1095
1290
  const flagCheck = CheckFlagReturnFromJSON(flag);
1096
1291
  this.checks[contextString(context)][flagCheck.flag] = flagCheck;
1292
+ this.debug(`WebSocket flag update:`, {
1293
+ flag: flagCheck.flag,
1294
+ value: flagCheck.value,
1295
+ flagCheck
1296
+ });
1297
+ if (this.flagCheckListeners[flag.flag]?.size > 0 || this.flagValueListeners[flag.flag]?.size > 0) {
1298
+ this.submitFlagCheckEvent(flagCheck.flag, flagCheck, context);
1299
+ }
1097
1300
  this.notifyFlagCheckListeners(flag.flag, flagCheck);
1098
1301
  this.notifyFlagValueListeners(flag.flag, flagCheck.value);
1099
1302
  });
@@ -1104,18 +1307,23 @@ var Schematic = class {
1104
1307
  }
1105
1308
  };
1106
1309
  socket.addEventListener("message", messageHandler);
1107
- socket.send(
1108
- JSON.stringify({
1109
- apiKey: this.apiKey,
1110
- data: context
1111
- })
1112
- );
1310
+ const clientVersion = this.additionalHeaders["X-Schematic-Client-Version"] ?? `schematic-js@${version}`;
1311
+ const messagePayload = {
1312
+ apiKey: this.apiKey,
1313
+ clientVersion,
1314
+ data: context
1315
+ };
1316
+ this.debug(`WebSocket sending message:`, messagePayload);
1317
+ socket.send(JSON.stringify(messagePayload));
1113
1318
  };
1114
1319
  if (socket.readyState === WebSocket.OPEN) {
1320
+ this.debug(`WebSocket already open, sending message`);
1115
1321
  sendMessage();
1116
1322
  } else if (socket.readyState === WebSocket.CONNECTING) {
1323
+ this.debug(`WebSocket connecting, waiting for open to send message`);
1117
1324
  socket.addEventListener("open", sendMessage);
1118
1325
  } else {
1326
+ this.debug(`WebSocket is closed, cannot send message`);
1119
1327
  reject("WebSocket is not open or connecting");
1120
1328
  }
1121
1329
  });
@@ -1172,10 +1380,22 @@ var Schematic = class {
1172
1380
  };
1173
1381
  notifyFlagCheckListeners = (flagKey, check) => {
1174
1382
  const listeners = this.flagCheckListeners?.[flagKey] ?? [];
1383
+ if (listeners.size > 0) {
1384
+ this.debug(
1385
+ `Notifying ${listeners.size} flag check listeners for ${flagKey}`,
1386
+ check
1387
+ );
1388
+ }
1175
1389
  listeners.forEach((listener) => notifyFlagCheckListener(listener, check));
1176
1390
  };
1177
1391
  notifyFlagValueListeners = (flagKey, value) => {
1178
1392
  const listeners = this.flagValueListeners?.[flagKey] ?? [];
1393
+ if (listeners.size > 0) {
1394
+ this.debug(
1395
+ `Notifying ${listeners.size} flag value listeners for ${flagKey}`,
1396
+ { value }
1397
+ );
1398
+ }
1179
1399
  listeners.forEach((listener) => notifyFlagValueListener(listener, value));
1180
1400
  };
1181
1401
  };
@@ -208,7 +208,92 @@ declare type Event_2 = {
208
208
  };
209
209
  export { Event_2 as Event }
210
210
 
211
- export declare type EventBody = EventBodyIdentify | EventBodyTrack;
211
+ export declare type EventBody = EventBodyIdentify | EventBodyTrack | EventBodyFlagCheck;
212
+
213
+ /**
214
+ * Schematic API
215
+ * Schematic API
216
+ *
217
+ * The version of the OpenAPI document: 0.1
218
+ *
219
+ *
220
+ * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
221
+ * https://openapi-generator.tech
222
+ * Do not edit the class manually.
223
+ */
224
+ /**
225
+ *
226
+ * @export
227
+ * @interface EventBodyFlagCheck
228
+ */
229
+ export declare interface EventBodyFlagCheck {
230
+ /**
231
+ * Schematic company ID (starting with 'comp_') of the company evaluated, if any
232
+ * @type {string}
233
+ * @memberof EventBodyFlagCheck
234
+ */
235
+ companyId?: string | null;
236
+ /**
237
+ * Report an error that occurred during the flag check
238
+ * @type {string}
239
+ * @memberof EventBodyFlagCheck
240
+ */
241
+ error?: string | null;
242
+ /**
243
+ * Schematic flag ID (starting with 'flag_') for the flag matching the key, if any
244
+ * @type {string}
245
+ * @memberof EventBodyFlagCheck
246
+ */
247
+ flagId?: string | null;
248
+ /**
249
+ * The key of the flag being checked
250
+ * @type {string}
251
+ * @memberof EventBodyFlagCheck
252
+ */
253
+ flagKey: string;
254
+ /**
255
+ * The reason why the value was returned
256
+ * @type {string}
257
+ * @memberof EventBodyFlagCheck
258
+ */
259
+ reason: string;
260
+ /**
261
+ * Key-value pairs used to to identify company for which the flag was checked
262
+ * @type {{ [key: string]: string; }}
263
+ * @memberof EventBodyFlagCheck
264
+ */
265
+ reqCompany?: {
266
+ [key: string]: string;
267
+ } | null;
268
+ /**
269
+ * Key-value pairs used to to identify user for which the flag was checked
270
+ * @type {{ [key: string]: string; }}
271
+ * @memberof EventBodyFlagCheck
272
+ */
273
+ reqUser?: {
274
+ [key: string]: string;
275
+ } | null;
276
+ /**
277
+ * Schematic rule ID (starting with 'rule_') of the rule that matched for the flag, if any
278
+ * @type {string}
279
+ * @memberof EventBodyFlagCheck
280
+ */
281
+ ruleId?: string | null;
282
+ /**
283
+ * Schematic user ID (starting with 'user_') of the user evaluated, if any
284
+ * @type {string}
285
+ * @memberof EventBodyFlagCheck
286
+ */
287
+ userId?: string | null;
288
+ /**
289
+ * The value of the flag for the given company and/or user
290
+ * @type {boolean}
291
+ * @memberof EventBodyFlagCheck
292
+ */
293
+ value: boolean;
294
+ }
295
+
296
+ export declare function EventBodyFlagCheckToJSON(json: any): EventBodyFlagCheck;
212
297
 
213
298
  export declare type EventBodyIdentify = {
214
299
  company?: {
@@ -227,7 +312,7 @@ export declare type EventBodyTrack = SchematicContext & {
227
312
  traits?: Traits;
228
313
  };
229
314
 
230
- export declare type EventType = "identify" | "track";
315
+ export declare type EventType = "identify" | "track" | "flag_check";
231
316
 
232
317
  export declare type FlagCheckListenerFn = CheckFlagReturnListenerFn | EmptyListenerFn;
233
318
 
@@ -261,6 +346,8 @@ export declare class Schematic {
261
346
  private apiUrl;
262
347
  private conn;
263
348
  private context;
349
+ private debugEnabled;
350
+ private offlineEnabled;
264
351
  private eventQueue;
265
352
  private eventUrl;
266
353
  private flagCheckListeners;
@@ -280,6 +367,20 @@ export declare class Schematic {
280
367
  * In REST mode, makes an API call for each check.
281
368
  */
282
369
  checkFlag(options: CheckOptions): Promise<boolean>;
370
+ /**
371
+ * Helper function to log debug messages
372
+ * Only logs if debug mode is enabled
373
+ */
374
+ private debug;
375
+ /**
376
+ * Helper function to check if client is in offline mode
377
+ */
378
+ private isOffline;
379
+ /**
380
+ * Submit a flag check event
381
+ * Records data about a flag check for analytics
382
+ */
383
+ private submitFlagCheckEvent;
283
384
  /**
284
385
  * Helper method for falling back to REST API when WebSocket connection fails
285
386
  */
@@ -287,6 +388,7 @@ export declare class Schematic {
287
388
  /**
288
389
  * Make an API call to fetch all flag values for a given context.
289
390
  * Recommended for use in REST mode only.
391
+ * In offline mode, returns an empty object.
290
392
  */
291
393
  checkFlags: (context?: SchematicContext) => Promise<Record<string, boolean>>;
292
394
  /**
@@ -302,6 +404,7 @@ export declare class Schematic {
302
404
  * 2. Send the context to the server
303
405
  * 3. Wait for initial flag values to be returned
304
406
  * The promise resolves when initial flag values are received.
407
+ * In offline mode, this will just set the context locally without connecting.
305
408
  */
306
409
  setContext: (context: SchematicContext) => Promise<void>;
307
410
  /**
@@ -322,6 +425,7 @@ export declare class Schematic {
322
425
  */
323
426
  /**
324
427
  * If using websocket mode, close the connection when done.
428
+ * In offline mode, this is a no-op.
325
429
  */
326
430
  cleanup: () => Promise<void>;
327
431
  private wsConnect;
@@ -353,8 +457,15 @@ export declare type SchematicOptions = {
353
457
  additionalHeaders?: Record<string, string>;
354
458
  /** Optionally provide a custom API URL */
355
459
  apiUrl?: string;
460
+ /** Enable debug mode to log flag check results and events to the console.
461
+ * Can also be enabled at runtime via URL query parameter "schematic_debug=true" */
462
+ debug?: boolean;
356
463
  /** Optionally provide a custom event URL */
357
464
  eventUrl?: string;
465
+ /** Enable offline mode to prevent all network requests.
466
+ * When enabled, events are only logged not sent, and flag checks return fallback values.
467
+ * Can also be enabled at runtime via URL query parameter "schematic_offline=true" */
468
+ offline?: boolean;
358
469
  /** Optionally provide a custom storage persister for client-side storage */
359
470
  storage?: StoragePersister;
360
471
  /** Use a WebSocket connection for real-time flag checks; if using this, run the cleanup function to close the connection */
@@ -640,6 +640,28 @@ function CheckFlagResponseDataFromJSONTyped(json, ignoreDiscriminator) {
640
640
  };
641
641
  }
642
642
 
643
+ // src/types/api/models/EventBodyFlagCheck.ts
644
+ function EventBodyFlagCheckToJSON(json) {
645
+ return EventBodyFlagCheckToJSONTyped(json, false);
646
+ }
647
+ function EventBodyFlagCheckToJSONTyped(value, ignoreDiscriminator = false) {
648
+ if (value == null) {
649
+ return value;
650
+ }
651
+ return {
652
+ company_id: value["companyId"],
653
+ error: value["error"],
654
+ flag_id: value["flagId"],
655
+ flag_key: value["flagKey"],
656
+ reason: value["reason"],
657
+ req_company: value["reqCompany"],
658
+ req_user: value["reqUser"],
659
+ rule_id: value["ruleId"],
660
+ user_id: value["userId"],
661
+ value: value["value"]
662
+ };
663
+ }
664
+
643
665
  // src/types/api/models/CheckFlagResponse.ts
644
666
  function CheckFlagResponseFromJSON(json) {
645
667
  return CheckFlagResponseFromJSONTyped(json, false);
@@ -752,6 +774,9 @@ function contextString(context) {
752
774
  return JSON.stringify(sortedContext);
753
775
  }
754
776
 
777
+ // src/version.ts
778
+ var version = "1.2.2";
779
+
755
780
  // src/index.ts
756
781
  var anonymousIdKey = "schematicId";
757
782
  var Schematic = class {
@@ -760,6 +785,8 @@ var Schematic = class {
760
785
  apiUrl = "https://api.schematichq.com";
761
786
  conn = null;
762
787
  context = {};
788
+ debugEnabled = false;
789
+ offlineEnabled = false;
763
790
  eventQueue;
764
791
  eventUrl = "https://c.schematichq.com";
765
792
  flagCheckListeners = {};
@@ -774,9 +801,30 @@ var Schematic = class {
774
801
  this.apiKey = apiKey;
775
802
  this.eventQueue = [];
776
803
  this.useWebSocket = options?.useWebSocket ?? false;
777
- if (options?.additionalHeaders) {
778
- this.additionalHeaders = options.additionalHeaders;
804
+ this.debugEnabled = options?.debug ?? false;
805
+ this.offlineEnabled = options?.offline ?? false;
806
+ if (typeof window !== "undefined" && typeof window.location !== "undefined") {
807
+ const params = new URLSearchParams(window.location.search);
808
+ const debugParam = params.get("schematic_debug");
809
+ if (debugParam !== null && (debugParam === "" || debugParam === "true" || debugParam === "1")) {
810
+ this.debugEnabled = true;
811
+ }
812
+ const offlineParam = params.get("schematic_offline");
813
+ if (offlineParam !== null && (offlineParam === "" || offlineParam === "true" || offlineParam === "1")) {
814
+ this.offlineEnabled = true;
815
+ this.debugEnabled = true;
816
+ }
817
+ }
818
+ if (this.offlineEnabled && options?.debug !== false) {
819
+ this.debugEnabled = true;
779
820
  }
821
+ if (this.offlineEnabled) {
822
+ this.setIsPending(false);
823
+ }
824
+ this.additionalHeaders = {
825
+ "X-Schematic-Client-Version": `schematic-js@${version}`,
826
+ ...options?.additionalHeaders ?? {}
827
+ };
780
828
  if (options?.storage) {
781
829
  this.storage = options.storage;
782
830
  } else if (typeof localStorage !== "undefined") {
@@ -796,6 +844,13 @@ var Schematic = class {
796
844
  this.flushEventQueue();
797
845
  });
798
846
  }
847
+ if (this.offlineEnabled) {
848
+ this.debug(
849
+ "Initialized with offline mode enabled - no network requests will be made"
850
+ );
851
+ } else if (this.debugEnabled) {
852
+ this.debug("Initialized with debug mode enabled");
853
+ }
799
854
  }
800
855
  /**
801
856
  * Get value for a single flag.
@@ -808,6 +863,14 @@ var Schematic = class {
808
863
  const { fallback = false, key } = options;
809
864
  const context = options.context || this.context;
810
865
  const contextStr = contextString(context);
866
+ this.debug(`checkFlag: ${key}`, { context, fallback });
867
+ if (this.isOffline()) {
868
+ this.debug(`checkFlag offline result: ${key}`, {
869
+ value: fallback,
870
+ offlineMode: true
871
+ });
872
+ return fallback;
873
+ }
811
874
  if (!this.useWebSocket) {
812
875
  const requestUrl = `${this.apiUrl}/flags/${key}/check`;
813
876
  return fetch(requestUrl, {
@@ -824,17 +887,32 @@ var Schematic = class {
824
887
  }
825
888
  return response.json();
826
889
  }).then((response) => {
827
- return CheckFlagResponseFromJSON(response).data.value;
890
+ const parsedResponse = CheckFlagResponseFromJSON(response);
891
+ this.debug(`checkFlag result: ${key}`, parsedResponse);
892
+ const result = CheckFlagReturnFromJSON(parsedResponse.data);
893
+ this.submitFlagCheckEvent(key, result, context);
894
+ return result.value;
828
895
  }).catch((error) => {
829
896
  console.error("There was a problem with the fetch operation:", error);
897
+ const errorResult = {
898
+ flag: key,
899
+ value: fallback,
900
+ reason: "API request failed",
901
+ error: error instanceof Error ? error.message : String(error)
902
+ };
903
+ this.submitFlagCheckEvent(key, errorResult, context);
830
904
  return fallback;
831
905
  });
832
906
  }
833
907
  try {
834
908
  const existingVals = this.checks[contextStr];
835
- if (this.conn && typeof existingVals !== "undefined" && typeof existingVals[key] !== "undefined") {
909
+ if (this.conn !== null && typeof existingVals !== "undefined" && typeof existingVals[key] !== "undefined") {
910
+ this.debug(`checkFlag cached result: ${key}`, existingVals[key]);
836
911
  return existingVals[key].value;
837
912
  }
913
+ if (this.isOffline()) {
914
+ return fallback;
915
+ }
838
916
  try {
839
917
  await this.setContext(context);
840
918
  } catch (error) {
@@ -845,16 +923,74 @@ var Schematic = class {
845
923
  return this.fallbackToRest(key, context, fallback);
846
924
  }
847
925
  const contextVals = this.checks[contextStr] ?? {};
848
- return contextVals[key]?.value ?? fallback;
926
+ const flagCheck = contextVals[key];
927
+ const result = flagCheck?.value ?? fallback;
928
+ this.debug(
929
+ `checkFlag WebSocket result: ${key}`,
930
+ typeof flagCheck !== "undefined" ? flagCheck : { value: fallback, fallbackUsed: true }
931
+ );
932
+ if (typeof flagCheck !== "undefined") {
933
+ this.submitFlagCheckEvent(key, flagCheck, context);
934
+ }
935
+ return result;
849
936
  } catch (error) {
850
937
  console.error("Unexpected error in checkFlag:", error);
938
+ const errorResult = {
939
+ flag: key,
940
+ value: fallback,
941
+ reason: "Unexpected error in flag check",
942
+ error: error instanceof Error ? error.message : String(error)
943
+ };
944
+ this.submitFlagCheckEvent(key, errorResult, context);
851
945
  return fallback;
852
946
  }
853
947
  }
948
+ /**
949
+ * Helper function to log debug messages
950
+ * Only logs if debug mode is enabled
951
+ */
952
+ debug(message, ...args) {
953
+ if (this.debugEnabled) {
954
+ console.log(`[Schematic] ${message}`, ...args);
955
+ }
956
+ }
957
+ /**
958
+ * Helper function to check if client is in offline mode
959
+ */
960
+ isOffline() {
961
+ return this.offlineEnabled;
962
+ }
963
+ /**
964
+ * Submit a flag check event
965
+ * Records data about a flag check for analytics
966
+ */
967
+ submitFlagCheckEvent(flagKey, result, context) {
968
+ const eventBody = {
969
+ flagKey,
970
+ value: result.value,
971
+ reason: result.reason,
972
+ flagId: result.flagId,
973
+ ruleId: result.ruleId,
974
+ companyId: result.companyId,
975
+ userId: result.userId,
976
+ error: result.error,
977
+ reqCompany: context.company,
978
+ reqUser: context.user
979
+ };
980
+ this.debug(`submitting flag check event:`, eventBody);
981
+ return this.handleEvent("flag_check", EventBodyFlagCheckToJSON(eventBody));
982
+ }
854
983
  /**
855
984
  * Helper method for falling back to REST API when WebSocket connection fails
856
985
  */
857
986
  async fallbackToRest(key, context, fallback) {
987
+ if (this.isOffline()) {
988
+ this.debug(`fallbackToRest offline result: ${key}`, {
989
+ value: fallback,
990
+ offlineMode: true
991
+ });
992
+ return fallback;
993
+ }
858
994
  try {
859
995
  const requestUrl = `${this.apiUrl}/flags/${key}/check`;
860
996
  const response = await fetch(requestUrl, {
@@ -869,19 +1005,36 @@ var Schematic = class {
869
1005
  if (!response.ok) {
870
1006
  throw new Error("Network response was not ok");
871
1007
  }
872
- const data = CheckFlagResponseFromJSON(await response.json());
873
- return data?.data?.value ?? false;
1008
+ const responseJson = await response.json();
1009
+ const data = CheckFlagResponseFromJSON(responseJson);
1010
+ this.debug(`fallbackToRest result: ${key}`, data);
1011
+ const result = CheckFlagReturnFromJSON(data.data);
1012
+ this.submitFlagCheckEvent(key, result, context);
1013
+ return result.value;
874
1014
  } catch (error) {
875
1015
  console.error("REST API call failed, using fallback value:", error);
1016
+ const errorResult = {
1017
+ flag: key,
1018
+ value: fallback,
1019
+ reason: "API request failed (fallback)",
1020
+ error: error instanceof Error ? error.message : String(error)
1021
+ };
1022
+ this.submitFlagCheckEvent(key, errorResult, context);
876
1023
  return fallback;
877
1024
  }
878
1025
  }
879
1026
  /**
880
1027
  * Make an API call to fetch all flag values for a given context.
881
1028
  * Recommended for use in REST mode only.
1029
+ * In offline mode, returns an empty object.
882
1030
  */
883
1031
  checkFlags = async (context) => {
884
1032
  context = context || this.context;
1033
+ this.debug(`checkFlags`, { context });
1034
+ if (this.isOffline()) {
1035
+ this.debug(`checkFlags offline result: returning empty object`);
1036
+ return {};
1037
+ }
885
1038
  const requestUrl = `${this.apiUrl}/flags/check`;
886
1039
  const requestBody = JSON.stringify(context);
887
1040
  return fetch(requestUrl, {
@@ -899,6 +1052,7 @@ var Schematic = class {
899
1052
  return response.json();
900
1053
  }).then((responseJson) => {
901
1054
  const resp = CheckFlagsResponseFromJSON(responseJson);
1055
+ this.debug(`checkFlags result:`, resp);
902
1056
  return (resp?.data?.flags ?? []).reduce(
903
1057
  (accum, flag) => {
904
1058
  accum[flag.flag] = flag.value;
@@ -917,6 +1071,7 @@ var Schematic = class {
917
1071
  * send an identify event to the Schematic API which will upsert a user and company.
918
1072
  */
919
1073
  identify = (body) => {
1074
+ this.debug(`identify:`, body);
920
1075
  try {
921
1076
  this.setContext({
922
1077
  company: body.company?.keys,
@@ -934,10 +1089,15 @@ var Schematic = class {
934
1089
  * 2. Send the context to the server
935
1090
  * 3. Wait for initial flag values to be returned
936
1091
  * The promise resolves when initial flag values are received.
1092
+ * In offline mode, this will just set the context locally without connecting.
937
1093
  */
938
1094
  setContext = async (context) => {
939
- if (!this.useWebSocket) {
1095
+ if (this.isOffline()) {
940
1096
  this.context = context;
1097
+ this.setIsPending(false);
1098
+ return Promise.resolve();
1099
+ }
1100
+ if (!this.useWebSocket) {
941
1101
  return Promise.resolve();
942
1102
  }
943
1103
  try {
@@ -958,12 +1118,14 @@ var Schematic = class {
958
1118
  */
959
1119
  track = (body) => {
960
1120
  const { company, user, event, traits } = body;
961
- return this.handleEvent("track", {
1121
+ const trackData = {
962
1122
  company: company ?? this.context.company,
963
1123
  event,
964
1124
  traits: traits ?? {},
965
1125
  user: user ?? this.context.user
966
- });
1126
+ };
1127
+ this.debug(`track:`, trackData);
1128
+ return this.handleEvent("track", trackData);
967
1129
  };
968
1130
  /**
969
1131
  * Event processing
@@ -1006,8 +1168,13 @@ var Schematic = class {
1006
1168
  sendEvent = async (event) => {
1007
1169
  const captureUrl = `${this.eventUrl}/e`;
1008
1170
  const payload = JSON.stringify(event);
1171
+ this.debug(`sending event:`, { url: captureUrl, event });
1172
+ if (this.isOffline()) {
1173
+ this.debug(`event not sent (offline mode):`, { event });
1174
+ return Promise.resolve();
1175
+ }
1009
1176
  try {
1010
- await fetch(captureUrl, {
1177
+ const response = await fetch(captureUrl, {
1011
1178
  method: "POST",
1012
1179
  headers: {
1013
1180
  ...this.additionalHeaders ?? {},
@@ -1015,6 +1182,10 @@ var Schematic = class {
1015
1182
  },
1016
1183
  body: payload
1017
1184
  });
1185
+ this.debug(`event sent:`, {
1186
+ status: response.status,
1187
+ statusText: response.statusText
1188
+ });
1018
1189
  } catch (error) {
1019
1190
  console.error("Error sending Schematic event: ", error);
1020
1191
  }
@@ -1029,8 +1200,13 @@ var Schematic = class {
1029
1200
  */
1030
1201
  /**
1031
1202
  * If using websocket mode, close the connection when done.
1203
+ * In offline mode, this is a no-op.
1032
1204
  */
1033
1205
  cleanup = async () => {
1206
+ if (this.isOffline()) {
1207
+ this.debug("cleanup: skipped (offline mode)");
1208
+ return Promise.resolve();
1209
+ }
1034
1210
  if (this.conn) {
1035
1211
  try {
1036
1212
  const socket = await this.conn;
@@ -1044,16 +1220,26 @@ var Schematic = class {
1044
1220
  };
1045
1221
  // Open a websocket connection
1046
1222
  wsConnect = () => {
1223
+ if (this.isOffline()) {
1224
+ this.debug("wsConnect: skipped (offline mode)");
1225
+ return Promise.reject(
1226
+ new Error("WebSocket connection skipped in offline mode")
1227
+ );
1228
+ }
1047
1229
  return new Promise((resolve, reject) => {
1048
- const wsUrl = `${this.webSocketUrl}/flags/bootstrap`;
1230
+ const wsUrl = `${this.webSocketUrl}/flags/bootstrap?apiKey=${this.apiKey}`;
1231
+ this.debug(`connecting to WebSocket:`, wsUrl);
1049
1232
  const webSocket = new WebSocket(wsUrl);
1050
1233
  webSocket.onopen = () => {
1234
+ this.debug(`WebSocket connection opened`);
1051
1235
  resolve(webSocket);
1052
1236
  };
1053
1237
  webSocket.onerror = (error) => {
1238
+ this.debug(`WebSocket connection error:`, error);
1054
1239
  reject(error);
1055
1240
  };
1056
1241
  webSocket.onclose = () => {
1242
+ this.debug(`WebSocket connection closed`);
1057
1243
  this.conn = null;
1058
1244
  };
1059
1245
  });
@@ -1061,21 +1247,37 @@ var Schematic = class {
1061
1247
  // Send a message on the websocket indicating interest in a particular evaluation context
1062
1248
  // and wait for the initial set of flag values to be returned
1063
1249
  wsSendMessage = (socket, context) => {
1250
+ if (this.isOffline()) {
1251
+ this.debug("wsSendMessage: skipped (offline mode)");
1252
+ this.setIsPending(false);
1253
+ return Promise.resolve();
1254
+ }
1064
1255
  return new Promise((resolve, reject) => {
1065
1256
  if (contextString(context) == contextString(this.context)) {
1257
+ this.debug(`WebSocket context unchanged, skipping update`);
1066
1258
  return resolve(this.setIsPending(false));
1067
1259
  }
1260
+ this.debug(`WebSocket context updated:`, context);
1068
1261
  this.context = context;
1069
1262
  const sendMessage = () => {
1070
1263
  let resolved = false;
1071
1264
  const messageHandler = (event) => {
1072
1265
  const message = JSON.parse(event.data);
1266
+ this.debug(`WebSocket message received:`, message);
1073
1267
  if (!(contextString(context) in this.checks)) {
1074
1268
  this.checks[contextString(context)] = {};
1075
1269
  }
1076
1270
  (message.flags ?? []).forEach((flag) => {
1077
1271
  const flagCheck = CheckFlagReturnFromJSON(flag);
1078
1272
  this.checks[contextString(context)][flagCheck.flag] = flagCheck;
1273
+ this.debug(`WebSocket flag update:`, {
1274
+ flag: flagCheck.flag,
1275
+ value: flagCheck.value,
1276
+ flagCheck
1277
+ });
1278
+ if (this.flagCheckListeners[flag.flag]?.size > 0 || this.flagValueListeners[flag.flag]?.size > 0) {
1279
+ this.submitFlagCheckEvent(flagCheck.flag, flagCheck, context);
1280
+ }
1079
1281
  this.notifyFlagCheckListeners(flag.flag, flagCheck);
1080
1282
  this.notifyFlagValueListeners(flag.flag, flagCheck.value);
1081
1283
  });
@@ -1086,18 +1288,23 @@ var Schematic = class {
1086
1288
  }
1087
1289
  };
1088
1290
  socket.addEventListener("message", messageHandler);
1089
- socket.send(
1090
- JSON.stringify({
1091
- apiKey: this.apiKey,
1092
- data: context
1093
- })
1094
- );
1291
+ const clientVersion = this.additionalHeaders["X-Schematic-Client-Version"] ?? `schematic-js@${version}`;
1292
+ const messagePayload = {
1293
+ apiKey: this.apiKey,
1294
+ clientVersion,
1295
+ data: context
1296
+ };
1297
+ this.debug(`WebSocket sending message:`, messagePayload);
1298
+ socket.send(JSON.stringify(messagePayload));
1095
1299
  };
1096
1300
  if (socket.readyState === WebSocket.OPEN) {
1301
+ this.debug(`WebSocket already open, sending message`);
1097
1302
  sendMessage();
1098
1303
  } else if (socket.readyState === WebSocket.CONNECTING) {
1304
+ this.debug(`WebSocket connecting, waiting for open to send message`);
1099
1305
  socket.addEventListener("open", sendMessage);
1100
1306
  } else {
1307
+ this.debug(`WebSocket is closed, cannot send message`);
1101
1308
  reject("WebSocket is not open or connecting");
1102
1309
  }
1103
1310
  });
@@ -1154,10 +1361,22 @@ var Schematic = class {
1154
1361
  };
1155
1362
  notifyFlagCheckListeners = (flagKey, check) => {
1156
1363
  const listeners = this.flagCheckListeners?.[flagKey] ?? [];
1364
+ if (listeners.size > 0) {
1365
+ this.debug(
1366
+ `Notifying ${listeners.size} flag check listeners for ${flagKey}`,
1367
+ check
1368
+ );
1369
+ }
1157
1370
  listeners.forEach((listener) => notifyFlagCheckListener(listener, check));
1158
1371
  };
1159
1372
  notifyFlagValueListeners = (flagKey, value) => {
1160
1373
  const listeners = this.flagValueListeners?.[flagKey] ?? [];
1374
+ if (listeners.size > 0) {
1375
+ this.debug(
1376
+ `Notifying ${listeners.size} flag value listeners for ${flagKey}`,
1377
+ { value }
1378
+ );
1379
+ }
1161
1380
  listeners.forEach((listener) => notifyFlagValueListener(listener, value));
1162
1381
  };
1163
1382
  };
@@ -1186,6 +1405,7 @@ export {
1186
1405
  CheckFlagResponseFromJSON,
1187
1406
  CheckFlagReturnFromJSON,
1188
1407
  CheckFlagsResponseFromJSON,
1408
+ EventBodyFlagCheckToJSON,
1189
1409
  RuleType,
1190
1410
  Schematic,
1191
1411
  UsagePeriod
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@schematichq/schematic-js",
3
- "version": "1.2.0",
3
+ "version": "1.2.2",
4
4
  "main": "dist/schematic.cjs.js",
5
5
  "module": "dist/schematic.esm.js",
6
6
  "types": "dist/schematic.d.ts",
@@ -17,7 +17,7 @@
17
17
  "url": "git+ssh://git@github.com/SchematicHQ/schematic-js.git"
18
18
  },
19
19
  "scripts": {
20
- "build": "npx tsc && yarn clean && yarn build:browser && yarn build:cjs && yarn build:esm && yarn build:types",
20
+ "build": "./version.sh && npx tsc && yarn clean && yarn build:browser && yarn build:cjs && yarn build:esm && yarn build:types",
21
21
  "build:browser": "npx esbuild src/browser.ts --bundle --minify --outfile=dist/schematic.browser.js --platform=browser",
22
22
  "build:cjs": "npx esbuild src/index.ts --bundle --format=cjs --outfile=dist/schematic.cjs.js",
23
23
  "build:esm": "npx esbuild src/index.ts --bundle --format=esm --outfile=dist/schematic.esm.js",
@@ -39,7 +39,7 @@
39
39
  "@types/uuid": "^10.0.0",
40
40
  "@typescript-eslint/eslint-plugin": "^8.23.0",
41
41
  "@typescript-eslint/parser": "^8.23.0",
42
- "esbuild": "^0.24.2",
42
+ "esbuild": "^0.25.0",
43
43
  "esbuild-jest": "^0.5.0",
44
44
  "eslint": "^8.57.1",
45
45
  "jest": "^29.7.0",