@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 +4 -4
- package/dist/schematic.browser.js +2 -2
- package/dist/schematic.cjs.js +103 -33
- package/dist/schematic.d.ts +35 -0
- package/dist/schematic.esm.js +103 -33
- package/package.json +2 -1
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
|
|
2
|
-
`)===0?
|
|
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 */
|
package/dist/schematic.cjs.js
CHANGED
|
@@ -653,37 +653,89 @@ var Schematic = class {
|
|
|
653
653
|
});
|
|
654
654
|
}
|
|
655
655
|
}
|
|
656
|
-
|
|
657
|
-
|
|
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
|
-
|
|
662
|
-
|
|
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
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
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
|
-
|
|
679
|
-
}).then((data) => {
|
|
728
|
+
const data = await response.json();
|
|
680
729
|
return data.data.value;
|
|
681
|
-
}
|
|
682
|
-
console.error("
|
|
730
|
+
} catch (error) {
|
|
731
|
+
console.error("REST API call failed, using fallback value:", error);
|
|
683
732
|
return fallback;
|
|
684
|
-
}
|
|
733
|
+
}
|
|
685
734
|
}
|
|
686
|
-
|
|
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
|
-
|
|
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
|
-
|
|
720
|
-
|
|
721
|
-
|
|
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
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
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("
|
|
806
|
+
console.error("Failed to establish WebSocket connection:", error);
|
|
807
|
+
throw error;
|
|
743
808
|
}
|
|
744
809
|
};
|
|
745
|
-
|
|
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 = () => {
|
package/dist/schematic.d.ts
CHANGED
|
@@ -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;
|
package/dist/schematic.esm.js
CHANGED
|
@@ -640,37 +640,89 @@ var Schematic = class {
|
|
|
640
640
|
});
|
|
641
641
|
}
|
|
642
642
|
}
|
|
643
|
-
|
|
644
|
-
|
|
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
|
-
|
|
649
|
-
|
|
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
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
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
|
-
|
|
666
|
-
}).then((data) => {
|
|
715
|
+
const data = await response.json();
|
|
667
716
|
return data.data.value;
|
|
668
|
-
}
|
|
669
|
-
console.error("
|
|
717
|
+
} catch (error) {
|
|
718
|
+
console.error("REST API call failed, using fallback value:", error);
|
|
670
719
|
return fallback;
|
|
671
|
-
}
|
|
720
|
+
}
|
|
672
721
|
}
|
|
673
|
-
|
|
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
|
-
|
|
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
|
-
|
|
707
|
-
|
|
708
|
-
|
|
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
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
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("
|
|
793
|
+
console.error("Failed to establish WebSocket connection:", error);
|
|
794
|
+
throw error;
|
|
730
795
|
}
|
|
731
796
|
};
|
|
732
|
-
|
|
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.
|
|
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"
|