@schematichq/schematic-js 1.0.1 → 1.0.3

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
@@ -44,7 +44,7 @@ schematic.identify({
44
44
  schematic.track({ event: "query" });
45
45
 
46
46
  // Check a flag
47
- schematic.checkFlag("some-flag-key");
47
+ await schematic.checkFlag("some-flag-key");
48
48
  ```
49
49
 
50
50
  By default, `checkFlag` will perform a network request to get the flag value for this user. If you'd like to check all flags at once in order to minimize network requests, you can use `checkFlags`:
@@ -61,7 +61,7 @@ schematic.identify({
61
61
  },
62
62
  });
63
63
 
64
- schematic.checkFlags();
64
+ await schematic.checkFlags();
65
65
  ```
66
66
 
67
67
  Alternatively, you can run in websocket mode, which will keep a persistent connection open to the Schematic service and receive flag updates in real time:
@@ -69,14 +69,14 @@ Alternatively, you can run in websocket mode, which will keep a persistent conne
69
69
  ```typescript
70
70
  import { Schematic } from "@schematichq/schematic-js";
71
71
 
72
- const schematic = new Schematic("your-api-key", { useWebSocket: true});
72
+ const schematic = new Schematic("your-api-key", { useWebSocket: true });
73
73
 
74
74
  schematic.identify({
75
75
  keys: { id: "my-user-id" },
76
76
  company: { keys: { id: "my-company-id" } },
77
77
  });
78
78
 
79
- schematic.checkFlag("some-flag-key");
79
+ await schematic.checkFlag("some-flag-key");
80
80
  ```
81
81
 
82
82
  ## License
@@ -1,3 +1,3 @@
1
- "use strict";(()=>{var z=Object.create;var R=Object.defineProperty;var Y=Object.getOwnPropertyDescriptor;var Z=Object.getOwnPropertyNames;var ee=Object.getPrototypeOf,te=Object.prototype.hasOwnProperty;var re=(o,t)=>()=>(t||o((t={exports:{}}).exports,t),t.exports);var ne=(o,t,n,i)=>{if(t&&typeof t=="object"||typeof t=="function")for(let s of Z(t))!te.call(o,s)&&s!==n&&R(o,s,{get:()=>t[s],enumerable:!(i=Y(t,s))||i.enumerable});return o};var ie=(o,t,n)=>(n=o!=null?z(ee(o)):{},ne(t||!o||!o.__esModule?R(n,"default",{value:o,enumerable:!0}):n,o));var V=re(j=>{(function(o){var t=function(n){var i=typeof globalThis<"u"&&globalThis||typeof o<"u"&&o||typeof i<"u"&&i,s={searchParams:"URLSearchParams"in i,iterable:"Symbol"in i&&"iterator"in Symbol,blob:"FileReader"in i&&"Blob"in i&&function(){try{return new Blob,!0}catch{return!1}}(),formData:"FormData"in i,arrayBuffer:"ArrayBuffer"in i};function y(e){return e&&DataView.prototype.isPrototypeOf(e)}if(s.arrayBuffer)var h=["[object Int8Array]","[object Uint8Array]","[object Uint8ClampedArray]","[object Int16Array]","[object Uint16Array]","[object Int32Array]","[object Uint32Array]","[object Float32Array]","[object Float64Array]"],E=ArrayBuffer.isView||function(e){return e&&h.indexOf(Object.prototype.toString.call(e))>-1};function w(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 S(e){return typeof e!="string"&&(e=String(e)),e}function v(e){var r={next:function(){var a=e.shift();return{done:a===void 0,value:a}}};return s.iterable&&(r[Symbol.iterator]=function(){return r}),r}function d(e){this.map={},e instanceof d?e.forEach(function(r,a){this.append(a,r)},this):Array.isArray(e)?e.forEach(function(r){this.append(r[0],r[1])},this):e&&Object.getOwnPropertyNames(e).forEach(function(r){this.append(r,e[r])},this)}d.prototype.append=function(e,r){e=w(e),r=S(r);var a=this.map[e];this.map[e]=a?a+", "+r:r},d.prototype.delete=function(e){delete this.map[w(e)]},d.prototype.get=function(e){return e=w(e),this.has(e)?this.map[e]:null},d.prototype.has=function(e){return this.map.hasOwnProperty(w(e))},d.prototype.set=function(e,r){this.map[w(e)]=S(r)},d.prototype.forEach=function(e,r){for(var a in this.map)this.map.hasOwnProperty(a)&&e.call(r,this.map[a],a,this)},d.prototype.keys=function(){var e=[];return this.forEach(function(r,a){e.push(a)}),v(e)},d.prototype.values=function(){var e=[];return this.forEach(function(r){e.push(r)}),v(e)},d.prototype.entries=function(){var e=[];return this.forEach(function(r,a){e.push([a,r])}),v(e)},s.iterable&&(d.prototype[Symbol.iterator]=d.prototype.entries);function O(e){if(e.bodyUsed)return Promise.reject(new TypeError("Already read"));e.bodyUsed=!0}function F(e){return new Promise(function(r,a){e.onload=function(){r(e.result)},e.onerror=function(){a(e.error)}})}function M(e){var r=new FileReader,a=F(r);return r.readAsArrayBuffer(e),a}function q(e){var r=new FileReader,a=F(r);return r.readAsText(e),a}function W(e){for(var r=new Uint8Array(e),a=new Array(r.length),u=0;u<r.length;u++)a[u]=String.fromCharCode(r[u]);return a.join("")}function I(e){if(e.slice)return e.slice(0);var r=new Uint8Array(e.byteLength);return r.set(new Uint8Array(e)),r.buffer}function D(){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&&y(e)?(this._bodyArrayBuffer=I(e.buffer),this._bodyInit=new Blob([this._bodyArrayBuffer])):s.arrayBuffer&&(ArrayBuffer.prototype.isPrototypeOf(e)||E(e))?this._bodyArrayBuffer=I(e):this._bodyText=e=Object.prototype.toString.call(e):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=O(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=O(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 return this.blob().then(M)}),this.text=function(){var e=O(this);if(e)return e;if(this._bodyBlob)return q(this._bodyBlob);if(this._bodyArrayBuffer)return Promise.resolve(W(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(Q)}),this.json=function(){return this.text().then(JSON.parse)},this}var K=["DELETE","GET","HEAD","OPTIONS","POST","PUT"];function J(e){var r=e.toUpperCase();return K.indexOf(r)>-1?r:e}function g(e,r){if(!(this instanceof g))throw new TypeError('Please use the "new" operator, this DOM object constructor cannot be called as a function.');r=r||{};var a=r.body;if(e instanceof g){if(e.bodyUsed)throw new TypeError("Already read");this.url=e.url,this.credentials=e.credentials,r.headers||(this.headers=new d(e.headers)),this.method=e.method,this.mode=e.mode,this.signal=e.signal,!a&&e._bodyInit!=null&&(a=e._bodyInit,e.bodyUsed=!0)}else this.url=String(e);if(this.credentials=r.credentials||this.credentials||"same-origin",(r.headers||!this.headers)&&(this.headers=new d(r.headers)),this.method=J(r.method||this.method||"GET"),this.mode=r.mode||this.mode||null,this.signal=r.signal||this.signal,this.referrer=null,(this.method==="GET"||this.method==="HEAD")&&a)throw new TypeError("Body not allowed for GET or HEAD requests");if(this._initBody(a),(this.method==="GET"||this.method==="HEAD")&&(r.cache==="no-store"||r.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()}}}g.prototype.clone=function(){return new g(this,{body:this._bodyInit})};function Q(e){var r=new FormData;return e.trim().split("&").forEach(function(a){if(a){var u=a.split("="),f=u.shift().replace(/\+/g," "),c=u.join("=").replace(/\+/g," ");r.append(decodeURIComponent(f),decodeURIComponent(c))}}),r}function $(e){var r=new d,a=e.replace(/\r?\n[\t ]+/g," ");return a.split("\r").map(function(u){return u.indexOf(`
2
- `)===0?u.substr(1,u.length):u}).forEach(function(u){var f=u.split(":"),c=f.shift().trim();if(c){var T=f.join(":").trim();r.append(c,T)}}),r}D.call(g.prototype);function m(e,r){if(!(this instanceof m))throw new TypeError('Please use the "new" operator, this DOM object constructor cannot be called as a function.');r||(r={}),this.type="default",this.status=r.status===void 0?200:r.status,this.ok=this.status>=200&&this.status<300,this.statusText=r.statusText===void 0?"":""+r.statusText,this.headers=new d(r.headers),this.url=r.url||"",this._initBody(e)}D.call(m.prototype),m.prototype.clone=function(){return new m(this._bodyInit,{status:this.status,statusText:this.statusText,headers:new d(this.headers),url:this.url})},m.error=function(){var e=new m(null,{status:0,statusText:""});return e.type="error",e};var G=[301,302,303,307,308];m.redirect=function(e,r){if(G.indexOf(r)===-1)throw new RangeError("Invalid status code");return new m(null,{status:r,headers:{location:e}})},n.DOMException=i.DOMException;try{new n.DOMException}catch{n.DOMException=function(r,a){this.message=r,this.name=a;var u=Error(r);this.stack=u.stack},n.DOMException.prototype=Object.create(Error.prototype),n.DOMException.prototype.constructor=n.DOMException}function k(e,r){return new Promise(function(a,u){var f=new g(e,r);if(f.signal&&f.signal.aborted)return u(new n.DOMException("Aborted","AbortError"));var c=new XMLHttpRequest;function T(){c.abort()}c.onload=function(){var p={status:c.status,statusText:c.statusText,headers:$(c.getAllResponseHeaders()||"")};p.url="responseURL"in c?c.responseURL:p.headers.get("X-Request-URL");var P="response"in c?c.response:c.responseText;setTimeout(function(){a(new m(P,p))},0)},c.onerror=function(){setTimeout(function(){u(new TypeError("Network request failed"))},0)},c.ontimeout=function(){setTimeout(function(){u(new TypeError("Network request failed"))},0)},c.onabort=function(){setTimeout(function(){u(new n.DOMException("Aborted","AbortError"))},0)};function X(p){try{return p===""&&i.location.href?i.location.href:p}catch{return p}}c.open(f.method,X(f.url),!0),f.credentials==="include"?c.withCredentials=!0:f.credentials==="omit"&&(c.withCredentials=!1),"responseType"in c&&(s.blob?c.responseType="blob":s.arrayBuffer&&f.headers.get("Content-Type")&&f.headers.get("Content-Type").indexOf("application/octet-stream")!==-1&&(c.responseType="arraybuffer")),r&&typeof r.headers=="object"&&!(r.headers instanceof d)?Object.getOwnPropertyNames(r.headers).forEach(function(p){c.setRequestHeader(p,S(r.headers[p]))}):f.headers.forEach(function(p,P){c.setRequestHeader(P,p)}),f.signal&&(f.signal.addEventListener("abort",T),c.onreadystatechange=function(){c.readyState===4&&f.signal.removeEventListener("abort",T)}),c.send(typeof f._bodyInit>"u"?null:f._bodyInit)})}return k.polyfill=!0,i.fetch||(i.fetch=k,i.Headers=d,i.Request=g,i.Response=m),n.Headers=d,n.Request=g,n.Response=m,n.fetch=k,n}({})})(typeof self<"u"?self:j)});var l=[];for(U=0;U<256;++U)l.push((U+256).toString(16).slice(1));var U;function C(o,t=0){return(l[o[t+0]]+l[o[t+1]]+l[o[t+2]]+l[o[t+3]]+"-"+l[o[t+4]]+l[o[t+5]]+"-"+l[o[t+6]]+l[o[t+7]]+"-"+l[o[t+8]]+l[o[t+9]]+"-"+l[o[t+10]]+l[o[t+11]]+l[o[t+12]]+l[o[t+13]]+l[o[t+14]]+l[o[t+15]]).toLowerCase()}var A,se=new Uint8Array(16);function _(){if(!A&&(A=typeof crypto<"u"&&crypto.getRandomValues&&crypto.getRandomValues.bind(crypto),!A))throw new Error("crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported");return A(se)}var oe=typeof crypto<"u"&&crypto.randomUUID&&crypto.randomUUID.bind(crypto),L={randomUUID:oe};function ae(o,t,n){if(L.randomUUID&&!t&&!o)return L.randomUUID();o=o||{};var i=o.random||(o.rng||_)();if(i[6]=i[6]&15|64,i[8]=i[8]&63|128,t){n=n||0;for(var s=0;s<16;++s)t[n+s]=i[s];return t}return C(i)}var x=ae;var we=ie(V());function b(o){let t=Object.keys(o).reduce((n,i)=>{let y=Object.keys(o[i]||{}).sort().reduce((h,E)=>(h[E]=o[i][E],h),{});return n[i]=y,n},{});return JSON.stringify(t)}var H="schematicId";var B=class{additionalHeaders={};apiKey;apiUrl="https://api.schematichq.com";conn=null;context={};eventQueue;eventUrl="https://c.schematichq.com";flagListener;flagValueListeners={};isPending=!0;isPendingListeners=new Set;storage;useWebSocket=!1;values={};webSocketUrl="wss://api.schematichq.com";constructor(t,n){this.apiKey=t,this.eventQueue=[],this.useWebSocket=n?.useWebSocket??!1,this.flagListener=n?.flagListener,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:i}=t,s=t.context||this.context;if(this.useWebSocket){let h=this.values[b(s)]??{};return typeof h[i]>"u"?n:h[i]}let y=`${this.apiUrl}/flags/${i}/check`;return fetch(y,{method:"POST",headers:{...this.additionalHeaders??{},"Content-Type":"application/json;charset=UTF-8","X-Schematic-Api-Key":this.apiKey},body:JSON.stringify(s)}).then(h=>{if(!h.ok)throw new Error("Network response was not ok");return h.json()}).then(h=>h.data.value).catch(h=>(console.error("There was a problem with the fetch operation:",h),n))}checkFlags=async t=>{t=t||this.context;let n=`${this.apiUrl}/flags/check`,i=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:i}).then(s=>{if(!s.ok)throw new Error("Network response was not ok");return s.json()}).then(s=>(s?.data?.flags??[]).reduce((y,h)=>(y[h.flag]=h.value,y),{})).catch(s=>(console.error("There was a problem with the fetch operation:",s),!1))};identify=t=>(this.setContext({company:t.company?.keys,user:t.keys}),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){console.error("Error setting Schematic context:",n)}};track=t=>{let{company:n,user:i,event:s,traits:y}=t;return this.handleEvent("track",{company:n??this.context.company,event:s,traits:y??{},user:i??this.context.user})};flushEventQueue=()=>{for(;this.eventQueue.length>0;){let t=this.eventQueue.shift();t&&this.sendEvent(t)}};getAnonymousId=()=>{if(!this.storage)return x();let t=this.storage.getItem(H);if(typeof t<"u")return t;let n=x();return this.storage.setItem(H,n),n};handleEvent=(t,n)=>{let i={api_key:this.apiKey,body:n,sent_at:new Date().toISOString(),tracker_event_id:x(),tracker_user_id:this.getAnonymousId(),type:t};return document?.hidden?this.storeEvent(i):this.sendEvent(i)};sendEvent=async t=>{let n=`${this.eventUrl}/e`,i=JSON.stringify(t);try{await fetch(n,{method:"POST",headers:{...this.additionalHeaders??{},"Content-Type":"application/json;charset=UTF-8"},body:i})}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 i=`${this.webSocketUrl}/flags/bootstrap`,s=new WebSocket(i);s.onopen=()=>{t(s)},s.onerror=y=>{n(y)},s.onclose=()=>{this.conn=null}});wsSendMessage=(t,n)=>new Promise((i,s)=>{if(b(n)==b(this.context)){i();return}this.context=n;let y=()=>{let h=!1,E=w=>{let S=JSON.parse(w.data);b(n)in this.values||(this.values[b(n)]={}),(S.flags??[]).forEach(v=>{this.values[b(n)][v.flag]=v.value,this.notifyFlagValueListeners(v.flag,v.value)}),this.flagListener&&this.flagListener(this.getFlagValues()),this.setIsPending(!1),h||(h=!0,i())};t.addEventListener("message",E),t.send(JSON.stringify({apiKey:this.apiKey,data:n}))};t.readyState===WebSocket.OPEN?y():t.readyState===WebSocket.CONNECTING?t.addEventListener("open",y):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=>N(n,t))};getFlagValue=t=>this.getFlagValues()[t];getFlagValues=()=>{let t=b(this.context);return this.values[t]??{}};addFlagValueListener=(t,n)=>(t in this.flagValueListeners||(this.flagValueListeners[t]=new Set),this.flagValueListeners[t].add(n),()=>{this.flagValueListeners[t].delete(n)});notifyFlagValueListeners=(t,n)=>{(this.flagValueListeners?.[t]??[]).forEach(s=>N(s,n))}},N=(o,t)=>{o.length>0?o(t):o()};window.Schematic=B;})();
1
+ "use strict";(()=>{var z=Object.create;var C=Object.defineProperty;var Y=Object.getOwnPropertyDescriptor;var Z=Object.getOwnPropertyNames;var ee=Object.getPrototypeOf,te=Object.prototype.hasOwnProperty;var re=(s,t)=>()=>(t||s((t={exports:{}}).exports,t),t.exports);var ne=(s,t,n,o)=>{if(t&&typeof t=="object"||typeof t=="function")for(let i of Z(t))!te.call(s,i)&&i!==n&&C(s,i,{get:()=>t[i],enumerable:!(o=Y(t,i))||o.enumerable});return s};var ie=(s,t,n)=>(n=s!=null?z(ee(s)):{},ne(t||!s||!s.__esModule?C(n,"default",{value:s,enumerable:!0}):n,s));var V=re(j=>{(function(s){var t=function(n){var o=typeof globalThis<"u"&&globalThis||typeof s<"u"&&s||typeof o<"u"&&o,i={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 l(e){return e&&DataView.prototype.isPrototypeOf(e)}if(i.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 g(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 S(e){return typeof e!="string"&&(e=String(e)),e}function b(e){var r={next:function(){var a=e.shift();return{done:a===void 0,value:a}}};return i.iterable&&(r[Symbol.iterator]=function(){return r}),r}function f(e){this.map={},e instanceof f?e.forEach(function(r,a){this.append(a,r)},this):Array.isArray(e)?e.forEach(function(r){this.append(r[0],r[1])},this):e&&Object.getOwnPropertyNames(e).forEach(function(r){this.append(r,e[r])},this)}f.prototype.append=function(e,r){e=g(e),r=S(r);var a=this.map[e];this.map[e]=a?a+", "+r:r},f.prototype.delete=function(e){delete this.map[g(e)]},f.prototype.get=function(e){return e=g(e),this.has(e)?this.map[e]:null},f.prototype.has=function(e){return this.map.hasOwnProperty(g(e))},f.prototype.set=function(e,r){this.map[g(e)]=S(r)},f.prototype.forEach=function(e,r){for(var a in this.map)this.map.hasOwnProperty(a)&&e.call(r,this.map[a],a,this)},f.prototype.keys=function(){var e=[];return this.forEach(function(r,a){e.push(a)}),b(e)},f.prototype.values=function(){var e=[];return this.forEach(function(r){e.push(r)}),b(e)},f.prototype.entries=function(){var e=[];return this.forEach(function(r,a){e.push([a,r])}),b(e)},i.iterable&&(f.prototype[Symbol.iterator]=f.prototype.entries);function B(e){if(e.bodyUsed)return Promise.reject(new TypeError("Already read"));e.bodyUsed=!0}function F(e){return new Promise(function(r,a){e.onload=function(){r(e.result)},e.onerror=function(){a(e.error)}})}function q(e){var r=new FileReader,a=F(r);return r.readAsArrayBuffer(e),a}function M(e){var r=new FileReader,a=F(r);return r.readAsText(e),a}function W(e){for(var r=new Uint8Array(e),a=new Array(r.length),h=0;h<r.length;h++)a[h]=String.fromCharCode(r[h]);return a.join("")}function R(e){if(e.slice)return e.slice(0);var r=new Uint8Array(e.byteLength);return r.set(new Uint8Array(e)),r.buffer}function I(){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&&l(e)?(this._bodyArrayBuffer=R(e.buffer),this._bodyInit=new Blob([this._bodyArrayBuffer])):i.arrayBuffer&&(ArrayBuffer.prototype.isPrototypeOf(e)||p(e))?this._bodyArrayBuffer=R(e):this._bodyText=e=Object.prototype.toString.call(e):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=B(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=B(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 return this.blob().then(q)}),this.text=function(){var e=B(this);if(e)return e;if(this._bodyBlob)return M(this._bodyBlob);if(this._bodyArrayBuffer)return Promise.resolve(W(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(J)}),this.json=function(){return this.text().then(JSON.parse)},this}var K=["DELETE","GET","HEAD","OPTIONS","POST","PUT"];function $(e){var r=e.toUpperCase();return K.indexOf(r)>-1?r:e}function w(e,r){if(!(this instanceof w))throw new TypeError('Please use the "new" operator, this DOM object constructor cannot be called as a function.');r=r||{};var a=r.body;if(e instanceof w){if(e.bodyUsed)throw new TypeError("Already read");this.url=e.url,this.credentials=e.credentials,r.headers||(this.headers=new f(e.headers)),this.method=e.method,this.mode=e.mode,this.signal=e.signal,!a&&e._bodyInit!=null&&(a=e._bodyInit,e.bodyUsed=!0)}else this.url=String(e);if(this.credentials=r.credentials||this.credentials||"same-origin",(r.headers||!this.headers)&&(this.headers=new f(r.headers)),this.method=$(r.method||this.method||"GET"),this.mode=r.mode||this.mode||null,this.signal=r.signal||this.signal,this.referrer=null,(this.method==="GET"||this.method==="HEAD")&&a)throw new TypeError("Body not allowed for GET or HEAD requests");if(this._initBody(a),(this.method==="GET"||this.method==="HEAD")&&(r.cache==="no-store"||r.cache==="no-cache")){var h=/([?&])_=[^&]*/;if(h.test(this.url))this.url=this.url.replace(h,"$1_="+new Date().getTime());else{var u=/\?/;this.url+=(u.test(this.url)?"&":"?")+"_="+new Date().getTime()}}}w.prototype.clone=function(){return new w(this,{body:this._bodyInit})};function J(e){var r=new FormData;return e.trim().split("&").forEach(function(a){if(a){var h=a.split("="),u=h.shift().replace(/\+/g," "),c=h.join("=").replace(/\+/g," ");r.append(decodeURIComponent(u),decodeURIComponent(c))}}),r}function Q(e){var r=new f,a=e.replace(/\r?\n[\t ]+/g," ");return a.split("\r").map(function(h){return h.indexOf(`
2
+ `)===0?h.substr(1,h.length):h}).forEach(function(h){var u=h.split(":"),c=u.shift().trim();if(c){var T=u.join(":").trim();r.append(c,T)}}),r}I.call(w.prototype);function v(e,r){if(!(this instanceof v))throw new TypeError('Please use the "new" operator, this DOM object constructor cannot be called as a function.');r||(r={}),this.type="default",this.status=r.status===void 0?200:r.status,this.ok=this.status>=200&&this.status<300,this.statusText=r.statusText===void 0?"":""+r.statusText,this.headers=new f(r.headers),this.url=r.url||"",this._initBody(e)}I.call(v.prototype),v.prototype.clone=function(){return new v(this._bodyInit,{status:this.status,statusText:this.statusText,headers:new f(this.headers),url:this.url})},v.error=function(){var e=new v(null,{status:0,statusText:""});return e.type="error",e};var X=[301,302,303,307,308];v.redirect=function(e,r){if(X.indexOf(r)===-1)throw new RangeError("Invalid status code");return new v(null,{status:r,headers:{location:e}})},n.DOMException=o.DOMException;try{new n.DOMException}catch{n.DOMException=function(r,a){this.message=r,this.name=a;var h=Error(r);this.stack=h.stack},n.DOMException.prototype=Object.create(Error.prototype),n.DOMException.prototype.constructor=n.DOMException}function O(e,r){return new Promise(function(a,h){var u=new w(e,r);if(u.signal&&u.signal.aborted)return h(new n.DOMException("Aborted","AbortError"));var c=new XMLHttpRequest;function T(){c.abort()}c.onload=function(){var m={status:c.status,statusText:c.statusText,headers:Q(c.getAllResponseHeaders()||"")};m.url="responseURL"in c?c.responseURL:m.headers.get("X-Request-URL");var P="response"in c?c.response:c.responseText;setTimeout(function(){a(new v(P,m))},0)},c.onerror=function(){setTimeout(function(){h(new TypeError("Network request failed"))},0)},c.ontimeout=function(){setTimeout(function(){h(new TypeError("Network request failed"))},0)},c.onabort=function(){setTimeout(function(){h(new n.DOMException("Aborted","AbortError"))},0)};function G(m){try{return m===""&&o.location.href?o.location.href:m}catch{return m}}c.open(u.method,G(u.url),!0),u.credentials==="include"?c.withCredentials=!0:u.credentials==="omit"&&(c.withCredentials=!1),"responseType"in c&&(i.blob?c.responseType="blob":i.arrayBuffer&&u.headers.get("Content-Type")&&u.headers.get("Content-Type").indexOf("application/octet-stream")!==-1&&(c.responseType="arraybuffer")),r&&typeof r.headers=="object"&&!(r.headers instanceof f)?Object.getOwnPropertyNames(r.headers).forEach(function(m){c.setRequestHeader(m,S(r.headers[m]))}):u.headers.forEach(function(m,P){c.setRequestHeader(P,m)}),u.signal&&(u.signal.addEventListener("abort",T),c.onreadystatechange=function(){c.readyState===4&&u.signal.removeEventListener("abort",T)}),c.send(typeof u._bodyInit>"u"?null:u._bodyInit)})}return O.polyfill=!0,o.fetch||(o.fetch=O,o.Headers=f,o.Request=w,o.Response=v),n.Headers=f,n.Request=w,n.Response=v,n.fetch=O,n}({})})(typeof self<"u"?self:j)});var y=[];for(U=0;U<256;++U)y.push((U+256).toString(16).slice(1));var U;function D(s,t=0){return(y[s[t+0]]+y[s[t+1]]+y[s[t+2]]+y[s[t+3]]+"-"+y[s[t+4]]+y[s[t+5]]+"-"+y[s[t+6]]+y[s[t+7]]+"-"+y[s[t+8]]+y[s[t+9]]+"-"+y[s[t+10]]+y[s[t+11]]+y[s[t+12]]+y[s[t+13]]+y[s[t+14]]+y[s[t+15]]).toLowerCase()}var k,oe=new Uint8Array(16);function _(){if(!k&&(k=typeof crypto<"u"&&crypto.getRandomValues&&crypto.getRandomValues.bind(crypto),!k))throw new Error("crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported");return k(oe)}var se=typeof crypto<"u"&&crypto.randomUUID&&crypto.randomUUID.bind(crypto),L={randomUUID:se};function ae(s,t,n){if(L.randomUUID&&!t&&!s)return L.randomUUID();s=s||{};var o=s.random||(s.rng||_)();if(o[6]=o[6]&15|64,o[8]=o[8]&63|128,t){n=n||0;for(var i=0;i<16;++i)t[n+i]=o[i];return t}return D(o)}var x=ae;var we=ie(V());function E(s){let t=Object.keys(s).reduce((n,o)=>{let l=Object.keys(s[o]||{}).sort().reduce((d,p)=>(d[p]=s[o][p],d),{});return n[o]=l,n},{});return JSON.stringify(t)}var H="schematicId";var A=class{additionalHeaders={};apiKey;apiUrl="https://api.schematichq.com";conn=null;context={};eventQueue;eventUrl="https://c.schematichq.com";flagListener;flagValueListeners={};isPending=!0;isPendingListeners=new Set;storage;useWebSocket=!1;values={};webSocketUrl="wss://api.schematichq.com";constructor(t,n){this.apiKey=t,this.eventQueue=[],this.useWebSocket=n?.useWebSocket??!1,this.flagListener=n?.flagListener,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,i=t.context||this.context,l=E(i);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(i)}).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.values[l];if(this.conn&&typeof d<"u"&&typeof d[o]<"u")return d[o];try{await this.setContext(i)}catch(g){return console.error("WebSocket connection failed, falling back to REST:",g),this.fallbackToRest(o,i,n)}let p=this.values[l]??{};return typeof p[o]>"u"?n:p[o]}catch(d){return console.error("Unexpected error in checkFlag:",d),n}}async fallbackToRest(t,n,o){try{let i=`${this.apiUrl}/flags/${t}/check`,l=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(!l.ok)throw new Error("Network response was not ok");return(await l.json()).data.value}catch(i){return console.error("REST API call failed, using fallback value:",i),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(i=>{if(!i.ok)throw new Error("Network response was not ok");return i.json()}).then(i=>(i?.data?.flags??[]).reduce((l,d)=>(l[d.flag]=d.value,l),{})).catch(i=>(console.error("There was a problem with the fetch operation:",i),!1))};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:i,traits:l}=t;return this.handleEvent("track",{company:n??this.context.company,event:i,traits:l??{},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 x();let t=this.storage.getItem(H);if(typeof t<"u")return t;let n=x();return this.storage.setItem(H,n),n};handleEvent=(t,n)=>{let o={api_key:this.apiKey,body:n,sent_at:new Date().toISOString(),tracker_event_id:x(),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(i){console.error("Error sending Schematic event: ",i)}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`,i=new WebSocket(o);i.onopen=()=>{t(i)},i.onerror=l=>{n(l)},i.onclose=()=>{this.conn=null}});wsSendMessage=(t,n)=>new Promise((o,i)=>{if(E(n)==E(this.context))return o(this.setIsPending(!1));this.context=n;let l=()=>{let d=!1,p=g=>{let S=JSON.parse(g.data);E(n)in this.values||(this.values[E(n)]={}),(S.flags??[]).forEach(b=>{this.values[E(n)][b.flag]=b.value,this.notifyFlagValueListeners(b.flag,b.value)}),this.flagListener&&this.flagListener(this.getFlagValues()),this.setIsPending(!1),d||(d=!0,o())};t.addEventListener("message",p),t.send(JSON.stringify({apiKey:this.apiKey,data:n}))};t.readyState===WebSocket.OPEN?l():t.readyState===WebSocket.CONNECTING?t.addEventListener("open",l):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=>N(n,t))};getFlagValue=t=>this.getFlagValues()[t];getFlagValues=()=>{let t=E(this.context);return this.values[t]??{}};addFlagValueListener=(t,n)=>(t in this.flagValueListeners||(this.flagValueListeners[t]=new Set),this.flagValueListeners[t].add(n),()=>{this.flagValueListeners[t].delete(n)});notifyFlagValueListeners=(t,n)=>{(this.flagValueListeners?.[t]??[]).forEach(i=>N(i,n))}},N=(s,t)=>{s.length>0?s(t):s()};window.Schematic=A;})();
3
3
  /* @preserve */
@@ -653,37 +653,89 @@ var Schematic = class {
653
653
  });
654
654
  }
655
655
  }
656
- // Get value for a single flag
657
- // If in websocket mode, return the local value, otherwise make an API call
656
+ /**
657
+ * Get value for a single flag.
658
+ * In WebSocket mode, returns cached values if connection is active, otherwise establishes
659
+ * new connection and then returns the requestedvalue. Falls back to REST API if WebSocket
660
+ * connection fails.
661
+ * In REST mode, makes an API call for each check.
662
+ */
658
663
  async checkFlag(options) {
659
664
  const { fallback = false, key } = options;
660
665
  const context = options.context || this.context;
661
- if (this.useWebSocket) {
662
- const contextVals = this.values[contextString(context)] ?? {};
666
+ const contextStr = contextString(context);
667
+ if (!this.useWebSocket) {
668
+ const requestUrl = `${this.apiUrl}/flags/${key}/check`;
669
+ return fetch(requestUrl, {
670
+ method: "POST",
671
+ headers: {
672
+ ...this.additionalHeaders ?? {},
673
+ "Content-Type": "application/json;charset=UTF-8",
674
+ "X-Schematic-Api-Key": this.apiKey
675
+ },
676
+ body: JSON.stringify(context)
677
+ }).then((response) => {
678
+ if (!response.ok) {
679
+ throw new Error("Network response was not ok");
680
+ }
681
+ return response.json();
682
+ }).then((data) => {
683
+ return data.data.value;
684
+ }).catch((error) => {
685
+ console.error("There was a problem with the fetch operation:", error);
686
+ return fallback;
687
+ });
688
+ }
689
+ try {
690
+ const existingVals = this.values[contextStr];
691
+ if (this.conn && typeof existingVals !== "undefined" && typeof existingVals[key] !== "undefined") {
692
+ return existingVals[key];
693
+ }
694
+ try {
695
+ await this.setContext(context);
696
+ } catch (error) {
697
+ console.error(
698
+ "WebSocket connection failed, falling back to REST:",
699
+ error
700
+ );
701
+ return this.fallbackToRest(key, context, fallback);
702
+ }
703
+ const contextVals = this.values[contextStr] ?? {};
663
704
  return typeof contextVals[key] === "undefined" ? fallback : contextVals[key];
705
+ } catch (error) {
706
+ console.error("Unexpected error in checkFlag:", error);
707
+ return fallback;
664
708
  }
665
- const requestUrl = `${this.apiUrl}/flags/${key}/check`;
666
- return fetch(requestUrl, {
667
- method: "POST",
668
- headers: {
669
- ...this.additionalHeaders ?? {},
670
- "Content-Type": "application/json;charset=UTF-8",
671
- "X-Schematic-Api-Key": this.apiKey
672
- },
673
- body: JSON.stringify(context)
674
- }).then((response) => {
709
+ }
710
+ /**
711
+ * Helper method for falling back to REST API when WebSocket connection fails
712
+ */
713
+ async fallbackToRest(key, context, fallback) {
714
+ try {
715
+ const requestUrl = `${this.apiUrl}/flags/${key}/check`;
716
+ const response = await fetch(requestUrl, {
717
+ method: "POST",
718
+ headers: {
719
+ ...this.additionalHeaders ?? {},
720
+ "Content-Type": "application/json;charset=UTF-8",
721
+ "X-Schematic-Api-Key": this.apiKey
722
+ },
723
+ body: JSON.stringify(context)
724
+ });
675
725
  if (!response.ok) {
676
726
  throw new Error("Network response was not ok");
677
727
  }
678
- return response.json();
679
- }).then((data) => {
728
+ const data = await response.json();
680
729
  return data.data.value;
681
- }).catch((error) => {
682
- console.error("There was a problem with the fetch operation:", error);
730
+ } catch (error) {
731
+ console.error("REST API call failed, using fallback value:", error);
683
732
  return fallback;
684
- });
733
+ }
685
734
  }
686
- // Make an API call to fetch all flag values for a given context (use if not in websocket mode)
735
+ /**
736
+ * Make an API call to fetch all flag values for a given context.
737
+ * Recommended for use in REST mode only.
738
+ */
687
739
  checkFlags = async (context) => {
688
740
  context = context || this.context;
689
741
  const requestUrl = `${this.apiUrl}/flags/check`;
@@ -714,18 +766,30 @@ var Schematic = class {
714
766
  return false;
715
767
  });
716
768
  };
717
- // Send an identify event
769
+ /**
770
+ * Send an identify event.
771
+ * This will set the context for subsequent flag evaluation and events, and will also
772
+ * send an identify event to the Schematic API which will upsert a user and company.
773
+ */
718
774
  identify = (body) => {
719
- this.setContext({
720
- company: body.company?.keys,
721
- user: body.keys
722
- });
775
+ try {
776
+ this.setContext({
777
+ company: body.company?.keys,
778
+ user: body.keys
779
+ });
780
+ } catch (error) {
781
+ console.error("Error setting context:", error);
782
+ }
723
783
  return this.handleEvent("identify", body);
724
784
  };
725
- // Set the flag evaluation context; if the context has changed,
726
- // this will open a websocket connection (if not already open)
727
- // and submit this context. The promise will resolve when the
728
- // websocket sends back an initial set of flag values.
785
+ /**
786
+ * Set the flag evaluation context.
787
+ * In WebSocket mode, this will:
788
+ * 1. Open a websocket connection if not already open
789
+ * 2. Send the context to the server
790
+ * 3. Wait for initial flag values to be returned
791
+ * The promise resolves when initial flag values are received.
792
+ */
729
793
  setContext = async (context) => {
730
794
  if (!this.useWebSocket) {
731
795
  this.context = context;
@@ -739,10 +803,14 @@ var Schematic = class {
739
803
  const socket = await this.conn;
740
804
  await this.wsSendMessage(socket, context);
741
805
  } catch (error) {
742
- console.error("Error setting Schematic context:", error);
806
+ console.error("Failed to establish WebSocket connection:", error);
807
+ throw error;
743
808
  }
744
809
  };
745
- // Send track event
810
+ /**
811
+ * Send a track event
812
+ * Track usage for a company and/or user.
813
+ */
746
814
  track = (body) => {
747
815
  const { company, user, event, traits } = body;
748
816
  return this.handleEvent("track", {
@@ -814,6 +882,9 @@ var Schematic = class {
814
882
  /**
815
883
  * Websocket management
816
884
  */
885
+ /**
886
+ * If using websocket mode, close the connection when done.
887
+ */
817
888
  cleanup = async () => {
818
889
  if (this.conn) {
819
890
  try {
@@ -847,8 +918,7 @@ var Schematic = class {
847
918
  wsSendMessage = (socket, context) => {
848
919
  return new Promise((resolve, reject) => {
849
920
  if (contextString(context) == contextString(this.context)) {
850
- resolve();
851
- return;
921
+ return resolve(this.setIsPending(false));
852
922
  }
853
923
  this.context = context;
854
924
  const sendMessage = () => {
@@ -72,10 +72,42 @@ export declare class Schematic {
72
72
  private values;
73
73
  private webSocketUrl;
74
74
  constructor(apiKey: string, options?: SchematicOptions);
75
+ /**
76
+ * Get value for a single flag.
77
+ * In WebSocket mode, returns cached values if connection is active, otherwise establishes
78
+ * new connection and then returns the requestedvalue. Falls back to REST API if WebSocket
79
+ * connection fails.
80
+ * In REST mode, makes an API call for each check.
81
+ */
75
82
  checkFlag(options: CheckOptions): Promise<boolean>;
83
+ /**
84
+ * Helper method for falling back to REST API when WebSocket connection fails
85
+ */
86
+ private fallbackToRest;
87
+ /**
88
+ * Make an API call to fetch all flag values for a given context.
89
+ * Recommended for use in REST mode only.
90
+ */
76
91
  checkFlags: (context?: SchematicContext) => Promise<Record<string, boolean>>;
92
+ /**
93
+ * Send an identify event.
94
+ * This will set the context for subsequent flag evaluation and events, and will also
95
+ * send an identify event to the Schematic API which will upsert a user and company.
96
+ */
77
97
  identify: (body: EventBodyIdentify) => Promise<void>;
98
+ /**
99
+ * Set the flag evaluation context.
100
+ * In WebSocket mode, this will:
101
+ * 1. Open a websocket connection if not already open
102
+ * 2. Send the context to the server
103
+ * 3. Wait for initial flag values to be returned
104
+ * The promise resolves when initial flag values are received.
105
+ */
78
106
  setContext: (context: SchematicContext) => Promise<void>;
107
+ /**
108
+ * Send a track event
109
+ * Track usage for a company and/or user.
110
+ */
79
111
  track: (body: EventBodyTrack) => Promise<void>;
80
112
  /**
81
113
  * Event processing
@@ -88,6 +120,9 @@ export declare class Schematic {
88
120
  /**
89
121
  * Websocket management
90
122
  */
123
+ /**
124
+ * If using websocket mode, close the connection when done.
125
+ */
91
126
  cleanup: () => Promise<void>;
92
127
  private wsConnect;
93
128
  private wsSendMessage;
@@ -640,37 +640,89 @@ var Schematic = class {
640
640
  });
641
641
  }
642
642
  }
643
- // Get value for a single flag
644
- // If in websocket mode, return the local value, otherwise make an API call
643
+ /**
644
+ * Get value for a single flag.
645
+ * In WebSocket mode, returns cached values if connection is active, otherwise establishes
646
+ * new connection and then returns the requestedvalue. Falls back to REST API if WebSocket
647
+ * connection fails.
648
+ * In REST mode, makes an API call for each check.
649
+ */
645
650
  async checkFlag(options) {
646
651
  const { fallback = false, key } = options;
647
652
  const context = options.context || this.context;
648
- if (this.useWebSocket) {
649
- const contextVals = this.values[contextString(context)] ?? {};
653
+ const contextStr = contextString(context);
654
+ if (!this.useWebSocket) {
655
+ const requestUrl = `${this.apiUrl}/flags/${key}/check`;
656
+ return fetch(requestUrl, {
657
+ method: "POST",
658
+ headers: {
659
+ ...this.additionalHeaders ?? {},
660
+ "Content-Type": "application/json;charset=UTF-8",
661
+ "X-Schematic-Api-Key": this.apiKey
662
+ },
663
+ body: JSON.stringify(context)
664
+ }).then((response) => {
665
+ if (!response.ok) {
666
+ throw new Error("Network response was not ok");
667
+ }
668
+ return response.json();
669
+ }).then((data) => {
670
+ return data.data.value;
671
+ }).catch((error) => {
672
+ console.error("There was a problem with the fetch operation:", error);
673
+ return fallback;
674
+ });
675
+ }
676
+ try {
677
+ const existingVals = this.values[contextStr];
678
+ if (this.conn && typeof existingVals !== "undefined" && typeof existingVals[key] !== "undefined") {
679
+ return existingVals[key];
680
+ }
681
+ try {
682
+ await this.setContext(context);
683
+ } catch (error) {
684
+ console.error(
685
+ "WebSocket connection failed, falling back to REST:",
686
+ error
687
+ );
688
+ return this.fallbackToRest(key, context, fallback);
689
+ }
690
+ const contextVals = this.values[contextStr] ?? {};
650
691
  return typeof contextVals[key] === "undefined" ? fallback : contextVals[key];
692
+ } catch (error) {
693
+ console.error("Unexpected error in checkFlag:", error);
694
+ return fallback;
651
695
  }
652
- const requestUrl = `${this.apiUrl}/flags/${key}/check`;
653
- return fetch(requestUrl, {
654
- method: "POST",
655
- headers: {
656
- ...this.additionalHeaders ?? {},
657
- "Content-Type": "application/json;charset=UTF-8",
658
- "X-Schematic-Api-Key": this.apiKey
659
- },
660
- body: JSON.stringify(context)
661
- }).then((response) => {
696
+ }
697
+ /**
698
+ * Helper method for falling back to REST API when WebSocket connection fails
699
+ */
700
+ async fallbackToRest(key, context, fallback) {
701
+ try {
702
+ const requestUrl = `${this.apiUrl}/flags/${key}/check`;
703
+ const response = await fetch(requestUrl, {
704
+ method: "POST",
705
+ headers: {
706
+ ...this.additionalHeaders ?? {},
707
+ "Content-Type": "application/json;charset=UTF-8",
708
+ "X-Schematic-Api-Key": this.apiKey
709
+ },
710
+ body: JSON.stringify(context)
711
+ });
662
712
  if (!response.ok) {
663
713
  throw new Error("Network response was not ok");
664
714
  }
665
- return response.json();
666
- }).then((data) => {
715
+ const data = await response.json();
667
716
  return data.data.value;
668
- }).catch((error) => {
669
- console.error("There was a problem with the fetch operation:", error);
717
+ } catch (error) {
718
+ console.error("REST API call failed, using fallback value:", error);
670
719
  return fallback;
671
- });
720
+ }
672
721
  }
673
- // Make an API call to fetch all flag values for a given context (use if not in websocket mode)
722
+ /**
723
+ * Make an API call to fetch all flag values for a given context.
724
+ * Recommended for use in REST mode only.
725
+ */
674
726
  checkFlags = async (context) => {
675
727
  context = context || this.context;
676
728
  const requestUrl = `${this.apiUrl}/flags/check`;
@@ -701,18 +753,30 @@ var Schematic = class {
701
753
  return false;
702
754
  });
703
755
  };
704
- // Send an identify event
756
+ /**
757
+ * Send an identify event.
758
+ * This will set the context for subsequent flag evaluation and events, and will also
759
+ * send an identify event to the Schematic API which will upsert a user and company.
760
+ */
705
761
  identify = (body) => {
706
- this.setContext({
707
- company: body.company?.keys,
708
- user: body.keys
709
- });
762
+ try {
763
+ this.setContext({
764
+ company: body.company?.keys,
765
+ user: body.keys
766
+ });
767
+ } catch (error) {
768
+ console.error("Error setting context:", error);
769
+ }
710
770
  return this.handleEvent("identify", body);
711
771
  };
712
- // Set the flag evaluation context; if the context has changed,
713
- // this will open a websocket connection (if not already open)
714
- // and submit this context. The promise will resolve when the
715
- // websocket sends back an initial set of flag values.
772
+ /**
773
+ * Set the flag evaluation context.
774
+ * In WebSocket mode, this will:
775
+ * 1. Open a websocket connection if not already open
776
+ * 2. Send the context to the server
777
+ * 3. Wait for initial flag values to be returned
778
+ * The promise resolves when initial flag values are received.
779
+ */
716
780
  setContext = async (context) => {
717
781
  if (!this.useWebSocket) {
718
782
  this.context = context;
@@ -726,10 +790,14 @@ var Schematic = class {
726
790
  const socket = await this.conn;
727
791
  await this.wsSendMessage(socket, context);
728
792
  } catch (error) {
729
- console.error("Error setting Schematic context:", error);
793
+ console.error("Failed to establish WebSocket connection:", error);
794
+ throw error;
730
795
  }
731
796
  };
732
- // Send track event
797
+ /**
798
+ * Send a track event
799
+ * Track usage for a company and/or user.
800
+ */
733
801
  track = (body) => {
734
802
  const { company, user, event, traits } = body;
735
803
  return this.handleEvent("track", {
@@ -801,6 +869,9 @@ var Schematic = class {
801
869
  /**
802
870
  * Websocket management
803
871
  */
872
+ /**
873
+ * If using websocket mode, close the connection when done.
874
+ */
804
875
  cleanup = async () => {
805
876
  if (this.conn) {
806
877
  try {
@@ -834,8 +905,7 @@ var Schematic = class {
834
905
  wsSendMessage = (socket, context) => {
835
906
  return new Promise((resolve, reject) => {
836
907
  if (contextString(context) == contextString(this.context)) {
837
- resolve();
838
- return;
908
+ return resolve(this.setIsPending(false));
839
909
  }
840
910
  this.context = context;
841
911
  const sendMessage = () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@schematichq/schematic-js",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "main": "dist/schematic.cjs.js",
5
5
  "module": "dist/schematic.esm.js",
6
6
  "types": "dist/schematic.d.ts",
@@ -44,6 +44,7 @@
44
44
  "jest-environment-jsdom": "^29.7.0",
45
45
  "jest-esbuild": "^0.3.0",
46
46
  "jest-fetch-mock": "^3.0.3",
47
+ "mock-socket": "^9.3.1",
47
48
  "prettier": "^3.3.3",
48
49
  "ts-jest": "^29.2.5",
49
50
  "typescript": "^5.6.2"