@schematichq/schematic-js 1.2.2 → 1.2.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 +6 -2
- package/dist/schematic.browser.js +2 -2
- package/dist/schematic.cjs.js +91 -8
- package/dist/schematic.d.ts +20 -2
- package/dist/schematic.esm.js +91 -8
- package/package.json +10 -9
package/README.md
CHANGED
|
@@ -14,7 +14,9 @@ pnpm add @schematichq/schematic-js
|
|
|
14
14
|
|
|
15
15
|
## Usage
|
|
16
16
|
|
|
17
|
-
You can use Schematic to identify users; after this, your subsequent track events and flag checks will be associated with this user.
|
|
17
|
+
You can use Schematic to identify users; after this, your subsequent track events and flag checks will be associated with this user.
|
|
18
|
+
|
|
19
|
+
A number of these examples use `keys` to identify companies and users. Learn more about keys [here](https://docs.schematichq.com/developer_resources/key_management).
|
|
18
20
|
|
|
19
21
|
```typescript
|
|
20
22
|
import { Schematic } from "@schematichq/schematic-js";
|
|
@@ -42,9 +44,11 @@ schematic.identify({
|
|
|
42
44
|
|
|
43
45
|
// Send a track event to record usage
|
|
44
46
|
schematic.track({ event: "query" });
|
|
47
|
+
// OR, Send a track event with a quantity to record multiple units of usage
|
|
48
|
+
schematic.track({ event: "query", quantity: 10 });
|
|
45
49
|
|
|
46
50
|
// Check a flag
|
|
47
|
-
await schematic.checkFlag("some-flag-key");
|
|
51
|
+
await schematic.checkFlag({ key: "some-flag-key" });
|
|
48
52
|
```
|
|
49
53
|
|
|
50
54
|
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`:
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
"use strict";(()=>{var ne=Object.create;var
|
|
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;})();
|
|
1
|
+
"use strict";(()=>{var ne=Object.create;var $=Object.defineProperty;var se=Object.getOwnPropertyDescriptor;var ae=Object.getOwnPropertyNames;var ie=Object.getPrototypeOf,oe=Object.prototype.hasOwnProperty;var le=(n,e)=>()=>(e||n((e={exports:{}}).exports,e),e.exports);var ue=(n,e,r,s)=>{if(e&&typeof e=="object"||typeof e=="function")for(let a of ae(e))!oe.call(n,a)&&a!==r&&$(n,a,{get:()=>e[a],enumerable:!(s=se(e,a))||s.enumerable});return n};var ce=(n,e,r)=>(r=n!=null?ne(ie(n)):{},ue(e||!n||!n.__esModule?$(r,"default",{value:n,enumerable:!0}):r,n));var X=le(z=>{(function(n){var e=function(r){var s=typeof globalThis<"u"&&globalThis||typeof n<"u"&&n||typeof global<"u"&&global||{},a={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 f(t){return t&&DataView.prototype.isPrototypeOf(t)}if(a.arrayBuffer)var o=["[object Int8Array]","[object Uint8Array]","[object Uint8ClampedArray]","[object Int16Array]","[object Uint16Array]","[object Int32Array]","[object Uint32Array]","[object Float32Array]","[object Float64Array]"],u=ArrayBuffer.isView||function(t){return t&&o.indexOf(Object.prototype.toString.call(t))>-1};function d(t){if(typeof t!="string"&&(t=String(t)),/[^a-z0-9\-#$%&'*+.^_`|~!]/i.test(t)||t==="")throw new TypeError('Invalid character in header field name: "'+t+'"');return t.toLowerCase()}function y(t){return typeof t!="string"&&(t=String(t)),t}function F(t){var i={next:function(){var l=t.shift();return{done:l===void 0,value:l}}};return a.iterable&&(i[Symbol.iterator]=function(){return i}),i}function g(t){this.map={},t instanceof g?t.forEach(function(i,l){this.append(l,i)},this):Array.isArray(t)?t.forEach(function(i){if(i.length!=2)throw new TypeError("Headers constructor: expected name/value pair to be length 2, found"+i.length);this.append(i[0],i[1])},this):t&&Object.getOwnPropertyNames(t).forEach(function(i){this.append(i,t[i])},this)}g.prototype.append=function(t,i){t=d(t),i=y(i);var l=this.map[t];this.map[t]=l?l+", "+i:i},g.prototype.delete=function(t){delete this.map[d(t)]},g.prototype.get=function(t){return t=d(t),this.has(t)?this.map[t]:null},g.prototype.has=function(t){return this.map.hasOwnProperty(d(t))},g.prototype.set=function(t,i){this.map[d(t)]=y(i)},g.prototype.forEach=function(t,i){for(var l in this.map)this.map.hasOwnProperty(l)&&t.call(i,this.map[l],l,this)},g.prototype.keys=function(){var t=[];return this.forEach(function(i,l){t.push(l)}),F(t)},g.prototype.values=function(){var t=[];return this.forEach(function(i){t.push(i)}),F(t)},g.prototype.entries=function(){var t=[];return this.forEach(function(i,l){t.push([l,i])}),F(t)},a.iterable&&(g.prototype[Symbol.iterator]=g.prototype.entries);function v(t){if(!t._noBody){if(t.bodyUsed)return Promise.reject(new TypeError("Already read"));t.bodyUsed=!0}}function m(t){return new Promise(function(i,l){t.onload=function(){i(t.result)},t.onerror=function(){l(t.error)}})}function S(t){var i=new FileReader,l=m(i);return i.readAsArrayBuffer(t),l}function V(t){var i=new FileReader,l=m(i),h=/charset=([A-Za-z0-9_-]+)/.exec(t.type),p=h?h[1]:"utf-8";return i.readAsText(t,p),l}function Y(t){for(var i=new Uint8Array(t),l=new Array(i.length),h=0;h<i.length;h++)l[h]=String.fromCharCode(i[h]);return l.join("")}function q(t){if(t.slice)return t.slice(0);var i=new Uint8Array(t.byteLength);return i.set(new Uint8Array(t)),i.buffer}function H(){return this.bodyUsed=!1,this._initBody=function(t){this.bodyUsed=this.bodyUsed,this._bodyInit=t,t?typeof t=="string"?this._bodyText=t:a.blob&&Blob.prototype.isPrototypeOf(t)?this._bodyBlob=t:a.formData&&FormData.prototype.isPrototypeOf(t)?this._bodyFormData=t:a.searchParams&&URLSearchParams.prototype.isPrototypeOf(t)?this._bodyText=t.toString():a.arrayBuffer&&a.blob&&f(t)?(this._bodyArrayBuffer=q(t.buffer),this._bodyInit=new Blob([this._bodyArrayBuffer])):a.arrayBuffer&&(ArrayBuffer.prototype.isPrototypeOf(t)||u(t))?this._bodyArrayBuffer=q(t):this._bodyText=t=Object.prototype.toString.call(t):(this._noBody=!0,this._bodyText=""),this.headers.get("content-type")||(typeof t=="string"?this.headers.set("content-type","text/plain;charset=UTF-8"):this._bodyBlob&&this._bodyBlob.type?this.headers.set("content-type",this._bodyBlob.type):a.searchParams&&URLSearchParams.prototype.isPrototypeOf(t)&&this.headers.set("content-type","application/x-www-form-urlencoded;charset=UTF-8"))},a.blob&&(this.blob=function(){var t=v(this);if(t)return t;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 t=v(this);return t||(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(a.blob)return this.blob().then(S);throw new Error("could not read as ArrayBuffer")}},this.text=function(){var t=v(this);if(t)return t;if(this._bodyBlob)return V(this._bodyBlob);if(this._bodyArrayBuffer)return Promise.resolve(Y(this._bodyArrayBuffer));if(this._bodyFormData)throw new Error("could not read FormData body as text");return Promise.resolve(this._bodyText)},a.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(t){var i=t.toUpperCase();return Z.indexOf(i)>-1?i:t}function R(t,i){if(!(this instanceof R))throw new TypeError('Please use the "new" operator, this DOM object constructor cannot be called as a function.');i=i||{};var l=i.body;if(t instanceof R){if(t.bodyUsed)throw new TypeError("Already read");this.url=t.url,this.credentials=t.credentials,i.headers||(this.headers=new g(t.headers)),this.method=t.method,this.mode=t.mode,this.signal=t.signal,!l&&t._bodyInit!=null&&(l=t._bodyInit,t.bodyUsed=!0)}else this.url=String(t);if(this.credentials=i.credentials||this.credentials||"same-origin",(i.headers||!this.headers)&&(this.headers=new g(i.headers)),this.method=j(i.method||this.method||"GET"),this.mode=i.mode||this.mode||null,this.signal=i.signal||this.signal||function(){if("AbortController"in s){var c=new AbortController;return c.signal}}(),this.referrer=null,(this.method==="GET"||this.method==="HEAD")&&l)throw new TypeError("Body not allowed for GET or HEAD requests");if(this._initBody(l),(this.method==="GET"||this.method==="HEAD")&&(i.cache==="no-store"||i.cache==="no-cache")){var h=/([?&])_=[^&]*/;if(h.test(this.url))this.url=this.url.replace(h,"$1_="+new Date().getTime());else{var p=/\?/;this.url+=(p.test(this.url)?"&":"?")+"_="+new Date().getTime()}}}R.prototype.clone=function(){return new R(this,{body:this._bodyInit})};function ee(t){var i=new FormData;return t.trim().split("&").forEach(function(l){if(l){var h=l.split("="),p=h.shift().replace(/\+/g," "),c=h.join("=").replace(/\+/g," ");i.append(decodeURIComponent(p),decodeURIComponent(c))}}),i}function te(t){var i=new g,l=t.replace(/\r?\n[\t ]+/g," ");return l.split("\r").map(function(h){return h.indexOf(`
|
|
2
|
+
`)===0?h.substr(1,h.length):h}).forEach(function(h){var p=h.split(":"),c=p.shift().trim();if(c){var U=p.join(":").trim();try{i.append(c,U)}catch(A){console.warn("Response "+A.message)}}}),i}H.call(R.prototype);function E(t,i){if(!(this instanceof E))throw new TypeError('Please use the "new" operator, this DOM object constructor cannot be called as a function.');if(i||(i={}),this.type="default",this.status=i.status===void 0?200:i.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=i.statusText===void 0?"":""+i.statusText,this.headers=new g(i.headers),this.url=i.url||"",this._initBody(t)}H.call(E.prototype),E.prototype.clone=function(){return new E(this._bodyInit,{status:this.status,statusText:this.statusText,headers:new g(this.headers),url:this.url})},E.error=function(){var t=new E(null,{status:200,statusText:""});return t.ok=!1,t.status=0,t.type="error",t};var re=[301,302,303,307,308];E.redirect=function(t,i){if(re.indexOf(i)===-1)throw new RangeError("Invalid status code");return new E(null,{status:i,headers:{location:t}})},r.DOMException=s.DOMException;try{new r.DOMException}catch{r.DOMException=function(i,l){this.message=i,this.name=l;var h=Error(i);this.stack=h.stack},r.DOMException.prototype=Object.create(Error.prototype),r.DOMException.prototype.constructor=r.DOMException}function I(t,i){return new Promise(function(l,h){var p=new R(t,i);if(p.signal&&p.signal.aborted)return h(new r.DOMException("Aborted","AbortError"));var c=new XMLHttpRequest;function U(){c.abort()}c.onload=function(){var b={statusText:c.statusText,headers:te(c.getAllResponseHeaders()||"")};p.url.indexOf("file://")===0&&(c.status<200||c.status>599)?b.status=200:b.status=c.status,b.url="responseURL"in c?c.responseURL:b.headers.get("X-Request-URL");var w="response"in c?c.response:c.responseText;setTimeout(function(){l(new E(w,b))},0)},c.onerror=function(){setTimeout(function(){h(new TypeError("Network request failed"))},0)},c.ontimeout=function(){setTimeout(function(){h(new TypeError("Network request timed out"))},0)},c.onabort=function(){setTimeout(function(){h(new r.DOMException("Aborted","AbortError"))},0)};function A(b){try{return b===""&&s.location.href?s.location.href:b}catch{return b}}if(c.open(p.method,A(p.url),!0),p.credentials==="include"?c.withCredentials=!0:p.credentials==="omit"&&(c.withCredentials=!1),"responseType"in c&&(a.blob?c.responseType="blob":a.arrayBuffer&&(c.responseType="arraybuffer")),i&&typeof i.headers=="object"&&!(i.headers instanceof g||s.Headers&&i.headers instanceof s.Headers)){var W=[];Object.getOwnPropertyNames(i.headers).forEach(function(b){W.push(d(b)),c.setRequestHeader(b,y(i.headers[b]))}),p.headers.forEach(function(b,w){W.indexOf(w)===-1&&c.setRequestHeader(w,b)})}else p.headers.forEach(function(b,w){c.setRequestHeader(w,b)});p.signal&&(p.signal.addEventListener("abort",U),c.onreadystatechange=function(){c.readyState===4&&p.signal.removeEventListener("abort",U)}),c.send(typeof p._bodyInit>"u"?null:p._bodyInit)})}return I.polyfill=!0,s.fetch||(s.fetch=I,s.Headers=g,s.Request=R,s.Response=E),r.Headers=g,r.Request=R,r.Response=E,r.fetch=I,r}({})})(typeof self<"u"?self:z)});var k=[];for(let n=0;n<256;++n)k.push((n+256).toString(16).slice(1));function K(n,e=0){return(k[n[e+0]]+k[n[e+1]]+k[n[e+2]]+k[n[e+3]]+"-"+k[n[e+4]]+k[n[e+5]]+"-"+k[n[e+6]]+k[n[e+7]]+"-"+k[n[e+8]]+k[n[e+9]]+"-"+k[n[e+10]]+k[n[e+11]]+k[n[e+12]]+k[n[e+13]]+k[n[e+14]]+k[n[e+15]]).toLowerCase()}var P,fe=new Uint8Array(16);function L(){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(fe)}var de=typeof crypto<"u"&&crypto.randomUUID&&crypto.randomUUID.bind(crypto),B={randomUUID:de};function he(n,e,r){if(B.randomUUID&&!e&&!n)return B.randomUUID();n=n||{};let s=n.random??n.rng?.()??L();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,e){if(r=r||0,r<0||r+16>e.length)throw new RangeError(`UUID byte range ${r}:${r+15} is out of buffer bounds`);for(let a=0;a<16;++a)e[r+a]=s[a];return e}return K(s)}var _=he;var rt=ce(X());function x(n){return pe(n,!1)}function pe(n,e){return n==null?n:{companyId:n.company_id==null?void 0:n.company_id,error:n.error==null?void 0:n.error,featureAllocation:n.feature_allocation==null?void 0:n.feature_allocation,featureUsage:n.feature_usage==null?void 0:n.feature_usage,featureUsageEvent:n.feature_usage_event==null?void 0:n.feature_usage_event,featureUsagePeriod:n.feature_usage_period==null?void 0:n.feature_usage_period,featureUsageResetAt:n.feature_usage_reset_at==null?void 0:new Date(n.feature_usage_reset_at),flag:n.flag,flagId:n.flag_id==null?void 0:n.flag_id,reason:n.reason,ruleId:n.rule_id==null?void 0:n.rule_id,ruleType:n.rule_type==null?void 0:n.rule_type,userId:n.user_id==null?void 0:n.user_id,value:n.value}}function N(n){return ye(n,!1)}function ye(n,e=!1){return n==null?n:{company_id:n.companyId,error:n.error,flag_id:n.flagId,flag_key:n.flagKey,reason:n.reason,req_company:n.reqCompany,req_user:n.reqUser,rule_id:n.ruleId,user_id:n.userId,value:n.value}}function T(n){return be(n,!1)}function be(n,e){return n==null?n:{data:x(n.data),params:n.params}}function G(n){return ke(n,!1)}function ke(n,e){return n==null?n:{flags:n.flags.map(x)}}function J(n){return ve(n,!1)}function ve(n,e){return n==null?n:{data:G(n.data),params:n.params}}var O=n=>{let{companyId:e,error:r,featureAllocation:s,featureUsage:a,featureUsageEvent:f,featureUsagePeriod:o,featureUsageResetAt:u,flag:d,flagId:y,reason:F,ruleId:g,ruleType:v,userId:m,value:S}=x(n);return{featureUsageExceeded:!S&&(v=="company_override_usage_exceeded"||v=="plan_entitlement_usage_exceeded"),companyId:e??void 0,error:r??void 0,featureAllocation:s??void 0,featureUsage:a??void 0,featureUsageEvent:f===null?void 0:f,featureUsagePeriod:o??void 0,featureUsageResetAt:u??void 0,flag:d,flagId:y??void 0,reason:F,ruleId:g??void 0,ruleType:v??void 0,userId:m??void 0,value:S}};function C(n){let e=Object.keys(n).reduce((r,s)=>{let f=Object.keys(n[s]||{}).sort().reduce((o,u)=>(o[u]=n[s][u],o),{});return r[s]=f,r},{});return JSON.stringify(e)}var M="1.2.3";var Q="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={};featureUsageEventMap={};webSocketUrl="wss://api.schematichq.com";constructor(e,r){if(this.apiKey=e,this.eventQueue=[],this.useWebSocket=r?.useWebSocket??!1,this.debugEnabled=r?.debug??!1,this.offlineEnabled=r?.offline??!1,typeof window<"u"&&typeof window.location<"u"){let s=new URLSearchParams(window.location.search),a=s.get("schematic_debug");a!==null&&(a===""||a==="true"||a==="1")&&(this.debugEnabled=!0);let f=s.get("schematic_offline");f!==null&&(f===""||f==="true"||f==="1")&&(this.offlineEnabled=!0,this.debugEnabled=!0)}this.offlineEnabled&&r?.debug!==!1&&(this.debugEnabled=!0),this.offlineEnabled&&this.setIsPending(!1),this.additionalHeaders={"X-Schematic-Client-Version":`schematic-js@${M}`,...r?.additionalHeaders??{}},r?.storage?this.storage=r.storage:typeof localStorage<"u"&&(this.storage=localStorage),r?.apiUrl!==void 0&&(this.apiUrl=r.apiUrl),r?.eventUrl!==void 0&&(this.eventUrl=r.eventUrl),r?.webSocketUrl!==void 0&&(this.webSocketUrl=r.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(e){let{fallback:r=!1,key:s}=e,a=e.context||this.context,f=C(a);if(this.debug(`checkFlag: ${s}`,{context:a,fallback:r}),this.isOffline())return this.debug(`checkFlag offline result: ${s}`,{value:r,offlineMode:!0}),r;if(!this.useWebSocket){let o=`${this.apiUrl}/flags/${s}/check`;return fetch(o,{method:"POST",headers:{...this.additionalHeaders??{},"Content-Type":"application/json;charset=UTF-8","X-Schematic-Api-Key":this.apiKey},body:JSON.stringify(a)}).then(u=>{if(!u.ok)throw new Error("Network response was not ok");return u.json()}).then(u=>{let d=T(u);this.debug(`checkFlag result: ${s}`,d);let y=O(d.data);return typeof y.featureUsageEvent=="string"&&this.updateFeatureUsageEventMap(y),this.submitFlagCheckEvent(s,y,a),y.value}).catch(u=>{console.error("There was a problem with the fetch operation:",u);let d={flag:s,value:r,reason:"API request failed",error:u instanceof Error?u.message:String(u)};return this.submitFlagCheckEvent(s,d,a),r})}try{let o=this.checks[f];if(this.conn!==null&&typeof o<"u"&&typeof o[s]<"u")return this.debug(`checkFlag cached result: ${s}`,o[s]),o[s].value;if(this.isOffline())return r;try{await this.setContext(a)}catch(F){return console.error("WebSocket connection failed, falling back to REST:",F),this.fallbackToRest(s,a,r)}let d=(this.checks[f]??{})[s],y=d?.value??r;return this.debug(`checkFlag WebSocket result: ${s}`,typeof d<"u"?d:{value:r,fallbackUsed:!0}),typeof d<"u"&&this.submitFlagCheckEvent(s,d,a),y}catch(o){console.error("Unexpected error in checkFlag:",o);let u={flag:s,value:r,reason:"Unexpected error in flag check",error:o instanceof Error?o.message:String(o)};return this.submitFlagCheckEvent(s,u,a),r}}debug(e,...r){this.debugEnabled&&console.log(`[Schematic] ${e}`,...r)}isOffline(){return this.offlineEnabled}submitFlagCheckEvent(e,r,s){let a={flagKey:e,value:r.value,reason:r.reason,flagId:r.flagId,ruleId:r.ruleId,companyId:r.companyId,userId:r.userId,error:r.error,reqCompany:s.company,reqUser:s.user};return this.debug("submitting flag check event:",a),this.handleEvent("flag_check",N(a))}async fallbackToRest(e,r,s){if(this.isOffline())return this.debug(`fallbackToRest offline result: ${e}`,{value:s,offlineMode:!0}),s;try{let a=`${this.apiUrl}/flags/${e}/check`,f=await fetch(a,{method:"POST",headers:{...this.additionalHeaders??{},"Content-Type":"application/json;charset=UTF-8","X-Schematic-Api-Key":this.apiKey},body:JSON.stringify(r)});if(!f.ok)throw new Error("Network response was not ok");let o=await f.json(),u=T(o);this.debug(`fallbackToRest result: ${e}`,u);let d=O(u.data);return typeof d.featureUsageEvent=="string"&&this.updateFeatureUsageEventMap(d),this.submitFlagCheckEvent(e,d,r),d.value}catch(a){console.error("REST API call failed, using fallback value:",a);let f={flag:e,value:s,reason:"API request failed (fallback)",error:a instanceof Error?a.message:String(a)};return this.submitFlagCheckEvent(e,f,r),s}}checkFlags=async e=>{if(e=e||this.context,this.debug("checkFlags",{context:e}),this.isOffline())return this.debug("checkFlags offline result: returning empty object"),{};let r=`${this.apiUrl}/flags/check`,s=JSON.stringify(e);return fetch(r,{method:"POST",headers:{...this.additionalHeaders??{},"Content-Type":"application/json;charset=UTF-8","X-Schematic-Api-Key":this.apiKey},body:s}).then(a=>{if(!a.ok)throw new Error("Network response was not ok");return a.json()}).then(a=>{let f=J(a);return this.debug("checkFlags result:",f),(f?.data?.flags??[]).reduce((o,u)=>(o[u.flag]=u.value,o),{})}).catch(a=>(console.error("There was a problem with the fetch operation:",a),{}))};identify=e=>{this.debug("identify:",e);try{this.setContext({company:e.company?.keys,user:e.keys})}catch(r){console.error("Error setting context:",r)}return this.handleEvent("identify",e)};setContext=async e=>{if(this.isOffline()||!this.useWebSocket)return this.context=e,this.setIsPending(!1),Promise.resolve();try{this.setIsPending(!0),this.conn||(this.conn=this.wsConnect());let r=await this.conn;await this.wsSendMessage(r,e)}catch(r){throw console.error("Failed to establish WebSocket connection:",r),r}};track=e=>{let{company:r,user:s,event:a,traits:f,quantity:o=1}=e,u={company:r??this.context.company,event:a,traits:f??{},user:s??this.context.user,quantity:o};return this.debug("track:",u),a in this.featureUsageEventMap&&this.optimisticallyUpdateFeatureUsage(a,o),this.handleEvent("track",u)};optimisticallyUpdateFeatureUsage=(e,r=1)=>{let s=this.featureUsageEventMap[e];s!=null&&(this.debug(`Optimistically updating feature usage for event: ${e}`,{quantity:r}),Object.entries(s).forEach(([a,f])=>{if(f===void 0)return;let o={...f};if(typeof o.featureUsage=="number"){if(o.featureUsage+=r,typeof o.featureAllocation=="number"){let d=o.featureUsageExceeded===!0,y=o.featureUsage>=o.featureAllocation;y!==d&&(o.featureUsageExceeded=y,y&&(o.value=!1),this.debug(`Usage limit status changed for flag: ${a}`,{was:d?"exceeded":"within limits",now:y?"exceeded":"within limits",featureUsage:o.featureUsage,featureAllocation:o.featureAllocation,value:o.value}))}this.featureUsageEventMap[e]!==void 0&&(this.featureUsageEventMap[e][a]=o);let u=C(this.context);this.checks[u]!==void 0&&this.checks[u]!==null&&(this.checks[u][a]=o),this.notifyFlagCheckListeners(a,o),this.notifyFlagValueListeners(a,o.value)}}))};flushEventQueue=()=>{for(;this.eventQueue.length>0;){let e=this.eventQueue.shift();e&&this.sendEvent(e)}};getAnonymousId=()=>{if(!this.storage)return _();let e=this.storage.getItem(Q);if(typeof e<"u")return e;let r=_();return this.storage.setItem(Q,r),r};handleEvent=(e,r)=>{let s={api_key:this.apiKey,body:r,sent_at:new Date().toISOString(),tracker_event_id:_(),tracker_user_id:this.getAnonymousId(),type:e};return document?.hidden?this.storeEvent(s):this.sendEvent(s)};sendEvent=async e=>{let r=`${this.eventUrl}/e`,s=JSON.stringify(e);if(this.debug("sending event:",{url:r,event:e}),this.isOffline())return this.debug("event not sent (offline mode):",{event:e}),Promise.resolve();try{let a=await fetch(r,{method:"POST",headers:{...this.additionalHeaders??{},"Content-Type":"application/json;charset=UTF-8"},body:s});this.debug("event sent:",{status:a.status,statusText:a.statusText})}catch(a){console.error("Error sending Schematic event: ",a)}return Promise.resolve()};storeEvent=e=>(this.eventQueue.push(e),Promise.resolve());cleanup=async()=>{if(this.isOffline())return this.debug("cleanup: skipped (offline mode)"),Promise.resolve();if(this.conn)try{(await this.conn).close()}catch(e){console.error("Error during cleanup:",e)}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((e,r)=>{let s=`${this.webSocketUrl}/flags/bootstrap?apiKey=${this.apiKey}`;this.debug("connecting to WebSocket:",s);let a=new WebSocket(s);a.onopen=()=>{this.debug("WebSocket connection opened"),e(a)},a.onerror=f=>{this.debug("WebSocket connection error:",f),r(f)},a.onclose=()=>{this.debug("WebSocket connection closed"),this.conn=null}});wsSendMessage=(e,r)=>this.isOffline()?(this.debug("wsSendMessage: skipped (offline mode)"),this.setIsPending(!1),Promise.resolve()):new Promise((s,a)=>{if(C(r)==C(this.context))return this.debug("WebSocket context unchanged, skipping update"),s(this.setIsPending(!1));this.debug("WebSocket context updated:",r),this.context=r;let f=()=>{let o=!1,u=F=>{let g=JSON.parse(F.data);this.debug("WebSocket message received:",g),C(r)in this.checks||(this.checks[C(r)]={}),(g.flags??[]).forEach(v=>{let m=O(v),S=C(r);this.checks[S]===void 0&&(this.checks[S]={}),this.checks[S][m.flag]=m,this.debug("WebSocket flag update:",{flag:m.flag,value:m.value,flagCheck:m}),typeof m.featureUsageEvent=="string"&&this.updateFeatureUsageEventMap(m),(this.flagCheckListeners[v.flag]?.size>0||this.flagValueListeners[v.flag]?.size>0)&&this.submitFlagCheckEvent(m.flag,m,r),this.notifyFlagCheckListeners(v.flag,m),this.notifyFlagValueListeners(v.flag,m.value)}),this.setIsPending(!1),o||(o=!0,s())};e.addEventListener("message",u);let d=this.additionalHeaders["X-Schematic-Client-Version"]??`schematic-js@${M}`,y={apiKey:this.apiKey,clientVersion:d,data:r};this.debug("WebSocket sending message:",y),e.send(JSON.stringify(y))};e.readyState===WebSocket.OPEN?(this.debug("WebSocket already open, sending message"),f()):e.readyState===WebSocket.CONNECTING?(this.debug("WebSocket connecting, waiting for open to send message"),e.addEventListener("open",f)):(this.debug("WebSocket is closed, cannot send message"),a("WebSocket is not open or connecting"))});getIsPending=()=>this.isPending;addIsPendingListener=e=>(this.isPendingListeners.add(e),()=>{this.isPendingListeners.delete(e)});setIsPending=e=>{this.isPending=e,this.isPendingListeners.forEach(r=>Ee(r,e))};getFlagCheck=e=>{let r=C(this.context);return(this.checks[r]??{})[e]};getFlagValue=e=>this.getFlagCheck(e)?.value;addFlagValueListener=(e,r)=>(e in this.flagValueListeners||(this.flagValueListeners[e]=new Set),this.flagValueListeners[e].add(r),()=>{this.flagValueListeners[e].delete(r)});addFlagCheckListener=(e,r)=>(e in this.flagCheckListeners||(this.flagCheckListeners[e]=new Set),this.flagCheckListeners[e].add(r),()=>{this.flagCheckListeners[e].delete(r)});notifyFlagCheckListeners=(e,r)=>{let s=this.flagCheckListeners?.[e]??[];s.size>0&&this.debug(`Notifying ${s.size} flag check listeners for ${e}`,r),typeof r.featureUsageEvent=="string"&&this.updateFeatureUsageEventMap(r),s.forEach(a=>Fe(a,r))};updateFeatureUsageEventMap=e=>{if(typeof e.featureUsageEvent!="string")return;let r=e.featureUsageEvent;(this.featureUsageEventMap[r]===void 0||this.featureUsageEventMap[r]===null)&&(this.featureUsageEventMap[r]={}),this.featureUsageEventMap[r]!==void 0&&(this.featureUsageEventMap[r][e.flag]=e),this.debug(`Updated featureUsageEventMap for event: ${r}, flag: ${e.flag}`,e)};notifyFlagValueListeners=(e,r)=>{let s=this.flagValueListeners?.[e]??[];s.size>0&&this.debug(`Notifying ${s.size} flag value listeners for ${e}`,{value:r}),s.forEach(a=>Ce(a,r))}},Ee=(n,e)=>{n.length>0?n(e):n()},Fe=(n,e)=>{n.length>0?n(e):n()},Ce=(n,e)=>{n.length>0?n(e):n()};window.Schematic=D;})();
|
|
3
3
|
/* @preserve */
|
package/dist/schematic.cjs.js
CHANGED
|
@@ -647,6 +647,7 @@ function CheckFlagResponseDataFromJSONTyped(json, ignoreDiscriminator) {
|
|
|
647
647
|
error: json["error"] == null ? void 0 : json["error"],
|
|
648
648
|
featureAllocation: json["feature_allocation"] == null ? void 0 : json["feature_allocation"],
|
|
649
649
|
featureUsage: json["feature_usage"] == null ? void 0 : json["feature_usage"],
|
|
650
|
+
featureUsageEvent: json["feature_usage_event"] == null ? void 0 : json["feature_usage_event"],
|
|
650
651
|
featureUsagePeriod: json["feature_usage_period"] == null ? void 0 : json["feature_usage_period"],
|
|
651
652
|
featureUsageResetAt: json["feature_usage_reset_at"] == null ? void 0 : new Date(json["feature_usage_reset_at"]),
|
|
652
653
|
flag: json["flag"],
|
|
@@ -746,6 +747,7 @@ var CheckFlagReturnFromJSON = (json) => {
|
|
|
746
747
|
error,
|
|
747
748
|
featureAllocation,
|
|
748
749
|
featureUsage,
|
|
750
|
+
featureUsageEvent,
|
|
749
751
|
featureUsagePeriod,
|
|
750
752
|
featureUsageResetAt,
|
|
751
753
|
flag,
|
|
@@ -765,6 +767,7 @@ var CheckFlagReturnFromJSON = (json) => {
|
|
|
765
767
|
error: error == null ? void 0 : error,
|
|
766
768
|
featureAllocation: featureAllocation == null ? void 0 : featureAllocation,
|
|
767
769
|
featureUsage: featureUsage == null ? void 0 : featureUsage,
|
|
770
|
+
featureUsageEvent: featureUsageEvent === null ? void 0 : featureUsageEvent,
|
|
768
771
|
featureUsagePeriod: featureUsagePeriod == null ? void 0 : featureUsagePeriod,
|
|
769
772
|
featureUsageResetAt: featureUsageResetAt == null ? void 0 : featureUsageResetAt,
|
|
770
773
|
flag,
|
|
@@ -794,7 +797,7 @@ function contextString(context) {
|
|
|
794
797
|
}
|
|
795
798
|
|
|
796
799
|
// src/version.ts
|
|
797
|
-
var version = "1.2.
|
|
800
|
+
var version = "1.2.3";
|
|
798
801
|
|
|
799
802
|
// src/index.ts
|
|
800
803
|
var anonymousIdKey = "schematicId";
|
|
@@ -815,6 +818,7 @@ var Schematic = class {
|
|
|
815
818
|
storage;
|
|
816
819
|
useWebSocket = false;
|
|
817
820
|
checks = {};
|
|
821
|
+
featureUsageEventMap = {};
|
|
818
822
|
webSocketUrl = "wss://api.schematichq.com";
|
|
819
823
|
constructor(apiKey, options) {
|
|
820
824
|
this.apiKey = apiKey;
|
|
@@ -909,6 +913,9 @@ var Schematic = class {
|
|
|
909
913
|
const parsedResponse = CheckFlagResponseFromJSON(response);
|
|
910
914
|
this.debug(`checkFlag result: ${key}`, parsedResponse);
|
|
911
915
|
const result = CheckFlagReturnFromJSON(parsedResponse.data);
|
|
916
|
+
if (typeof result.featureUsageEvent === "string") {
|
|
917
|
+
this.updateFeatureUsageEventMap(result);
|
|
918
|
+
}
|
|
912
919
|
this.submitFlagCheckEvent(key, result, context);
|
|
913
920
|
return result.value;
|
|
914
921
|
}).catch((error) => {
|
|
@@ -1028,6 +1035,9 @@ var Schematic = class {
|
|
|
1028
1035
|
const data = CheckFlagResponseFromJSON(responseJson);
|
|
1029
1036
|
this.debug(`fallbackToRest result: ${key}`, data);
|
|
1030
1037
|
const result = CheckFlagReturnFromJSON(data.data);
|
|
1038
|
+
if (typeof result.featureUsageEvent === "string") {
|
|
1039
|
+
this.updateFeatureUsageEventMap(result);
|
|
1040
|
+
}
|
|
1031
1041
|
this.submitFlagCheckEvent(key, result, context);
|
|
1032
1042
|
return result.value;
|
|
1033
1043
|
} catch (error) {
|
|
@@ -1111,14 +1121,11 @@ var Schematic = class {
|
|
|
1111
1121
|
* In offline mode, this will just set the context locally without connecting.
|
|
1112
1122
|
*/
|
|
1113
1123
|
setContext = async (context) => {
|
|
1114
|
-
if (this.isOffline()) {
|
|
1124
|
+
if (this.isOffline() || !this.useWebSocket) {
|
|
1115
1125
|
this.context = context;
|
|
1116
1126
|
this.setIsPending(false);
|
|
1117
1127
|
return Promise.resolve();
|
|
1118
1128
|
}
|
|
1119
|
-
if (!this.useWebSocket) {
|
|
1120
|
-
return Promise.resolve();
|
|
1121
|
-
}
|
|
1122
1129
|
try {
|
|
1123
1130
|
this.setIsPending(true);
|
|
1124
1131
|
if (!this.conn) {
|
|
@@ -1134,18 +1141,69 @@ var Schematic = class {
|
|
|
1134
1141
|
/**
|
|
1135
1142
|
* Send a track event
|
|
1136
1143
|
* Track usage for a company and/or user.
|
|
1144
|
+
* Optimistically updates feature usage flags if tracking a featureUsageEvent.
|
|
1137
1145
|
*/
|
|
1138
1146
|
track = (body) => {
|
|
1139
|
-
const { company, user, event, traits } = body;
|
|
1147
|
+
const { company, user, event, traits, quantity = 1 } = body;
|
|
1140
1148
|
const trackData = {
|
|
1141
1149
|
company: company ?? this.context.company,
|
|
1142
1150
|
event,
|
|
1143
1151
|
traits: traits ?? {},
|
|
1144
|
-
user: user ?? this.context.user
|
|
1152
|
+
user: user ?? this.context.user,
|
|
1153
|
+
quantity
|
|
1145
1154
|
};
|
|
1146
1155
|
this.debug(`track:`, trackData);
|
|
1156
|
+
if (event in this.featureUsageEventMap) {
|
|
1157
|
+
this.optimisticallyUpdateFeatureUsage(event, quantity);
|
|
1158
|
+
}
|
|
1147
1159
|
return this.handleEvent("track", trackData);
|
|
1148
1160
|
};
|
|
1161
|
+
/**
|
|
1162
|
+
* Optimistically update feature usage flags associated with a tracked event
|
|
1163
|
+
* This updates flags in memory with updated usage counts and value/featureUsageExceeded flags
|
|
1164
|
+
* before the network request completes
|
|
1165
|
+
*/
|
|
1166
|
+
optimisticallyUpdateFeatureUsage = (eventName, quantity = 1) => {
|
|
1167
|
+
const flagsForEvent = this.featureUsageEventMap[eventName];
|
|
1168
|
+
if (flagsForEvent === void 0 || flagsForEvent === null) return;
|
|
1169
|
+
this.debug(
|
|
1170
|
+
`Optimistically updating feature usage for event: ${eventName}`,
|
|
1171
|
+
{ quantity }
|
|
1172
|
+
);
|
|
1173
|
+
Object.entries(flagsForEvent).forEach(([flagKey, check]) => {
|
|
1174
|
+
if (check === void 0) return;
|
|
1175
|
+
const updatedCheck = { ...check };
|
|
1176
|
+
if (typeof updatedCheck.featureUsage === "number") {
|
|
1177
|
+
updatedCheck.featureUsage += quantity;
|
|
1178
|
+
if (typeof updatedCheck.featureAllocation === "number") {
|
|
1179
|
+
const wasExceeded = updatedCheck.featureUsageExceeded === true;
|
|
1180
|
+
const nowExceeded = updatedCheck.featureUsage >= updatedCheck.featureAllocation;
|
|
1181
|
+
if (nowExceeded !== wasExceeded) {
|
|
1182
|
+
updatedCheck.featureUsageExceeded = nowExceeded;
|
|
1183
|
+
if (nowExceeded) {
|
|
1184
|
+
updatedCheck.value = false;
|
|
1185
|
+
}
|
|
1186
|
+
this.debug(`Usage limit status changed for flag: ${flagKey}`, {
|
|
1187
|
+
was: wasExceeded ? "exceeded" : "within limits",
|
|
1188
|
+
now: nowExceeded ? "exceeded" : "within limits",
|
|
1189
|
+
featureUsage: updatedCheck.featureUsage,
|
|
1190
|
+
featureAllocation: updatedCheck.featureAllocation,
|
|
1191
|
+
value: updatedCheck.value
|
|
1192
|
+
});
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
if (this.featureUsageEventMap[eventName] !== void 0) {
|
|
1196
|
+
this.featureUsageEventMap[eventName][flagKey] = updatedCheck;
|
|
1197
|
+
}
|
|
1198
|
+
const contextStr = contextString(this.context);
|
|
1199
|
+
if (this.checks[contextStr] !== void 0 && this.checks[contextStr] !== null) {
|
|
1200
|
+
this.checks[contextStr][flagKey] = updatedCheck;
|
|
1201
|
+
}
|
|
1202
|
+
this.notifyFlagCheckListeners(flagKey, updatedCheck);
|
|
1203
|
+
this.notifyFlagValueListeners(flagKey, updatedCheck.value);
|
|
1204
|
+
}
|
|
1205
|
+
});
|
|
1206
|
+
};
|
|
1149
1207
|
/**
|
|
1150
1208
|
* Event processing
|
|
1151
1209
|
*/
|
|
@@ -1288,12 +1346,19 @@ var Schematic = class {
|
|
|
1288
1346
|
}
|
|
1289
1347
|
(message.flags ?? []).forEach((flag) => {
|
|
1290
1348
|
const flagCheck = CheckFlagReturnFromJSON(flag);
|
|
1291
|
-
|
|
1349
|
+
const contextStr = contextString(context);
|
|
1350
|
+
if (this.checks[contextStr] === void 0) {
|
|
1351
|
+
this.checks[contextStr] = {};
|
|
1352
|
+
}
|
|
1353
|
+
this.checks[contextStr][flagCheck.flag] = flagCheck;
|
|
1292
1354
|
this.debug(`WebSocket flag update:`, {
|
|
1293
1355
|
flag: flagCheck.flag,
|
|
1294
1356
|
value: flagCheck.value,
|
|
1295
1357
|
flagCheck
|
|
1296
1358
|
});
|
|
1359
|
+
if (typeof flagCheck.featureUsageEvent === "string") {
|
|
1360
|
+
this.updateFeatureUsageEventMap(flagCheck);
|
|
1361
|
+
}
|
|
1297
1362
|
if (this.flagCheckListeners[flag.flag]?.size > 0 || this.flagValueListeners[flag.flag]?.size > 0) {
|
|
1298
1363
|
this.submitFlagCheckEvent(flagCheck.flag, flagCheck, context);
|
|
1299
1364
|
}
|
|
@@ -1386,8 +1451,26 @@ var Schematic = class {
|
|
|
1386
1451
|
check
|
|
1387
1452
|
);
|
|
1388
1453
|
}
|
|
1454
|
+
if (typeof check.featureUsageEvent === "string") {
|
|
1455
|
+
this.updateFeatureUsageEventMap(check);
|
|
1456
|
+
}
|
|
1389
1457
|
listeners.forEach((listener) => notifyFlagCheckListener(listener, check));
|
|
1390
1458
|
};
|
|
1459
|
+
/** Add or update a CheckFlagReturn in the featureUsageEventMap */
|
|
1460
|
+
updateFeatureUsageEventMap = (check) => {
|
|
1461
|
+
if (typeof check.featureUsageEvent !== "string") return;
|
|
1462
|
+
const eventName = check.featureUsageEvent;
|
|
1463
|
+
if (this.featureUsageEventMap[eventName] === void 0 || this.featureUsageEventMap[eventName] === null) {
|
|
1464
|
+
this.featureUsageEventMap[eventName] = {};
|
|
1465
|
+
}
|
|
1466
|
+
if (this.featureUsageEventMap[eventName] !== void 0) {
|
|
1467
|
+
this.featureUsageEventMap[eventName][check.flag] = check;
|
|
1468
|
+
}
|
|
1469
|
+
this.debug(
|
|
1470
|
+
`Updated featureUsageEventMap for event: ${eventName}, flag: ${check.flag}`,
|
|
1471
|
+
check
|
|
1472
|
+
);
|
|
1473
|
+
};
|
|
1391
1474
|
notifyFlagValueListeners = (flagKey, value) => {
|
|
1392
1475
|
const listeners = this.flagValueListeners?.[flagKey] ?? [];
|
|
1393
1476
|
if (listeners.size > 0) {
|
package/dist/schematic.d.ts
CHANGED
|
@@ -61,6 +61,12 @@ export declare interface CheckFlagResponseData {
|
|
|
61
61
|
* @memberof CheckFlagResponseData
|
|
62
62
|
*/
|
|
63
63
|
featureUsage?: number | null;
|
|
64
|
+
/**
|
|
65
|
+
* If an event-based numeric feature entitlement rule was matched, the event used to track its usage
|
|
66
|
+
* @type {string}
|
|
67
|
+
* @memberof CheckFlagResponseData
|
|
68
|
+
*/
|
|
69
|
+
featureUsageEvent?: string | null;
|
|
64
70
|
/**
|
|
65
71
|
* For event-based feature entitlement rules, the period over which usage is tracked (current_month, current_day, current_week, all_time)
|
|
66
72
|
* @type {string}
|
|
@@ -130,6 +136,8 @@ export declare type CheckFlagReturn = {
|
|
|
130
136
|
featureAllocation?: number;
|
|
131
137
|
/** If a numeric feature entitlement rule was matched, the company's usage */
|
|
132
138
|
featureUsage?: number;
|
|
139
|
+
/** Event representing the feature usage */
|
|
140
|
+
featureUsageEvent?: string;
|
|
133
141
|
/** For event-based feature entitlement rules, the period over which usage is tracked (current_month, current_day, current_week, all_time) */
|
|
134
142
|
featureUsagePeriod?: UsagePeriod;
|
|
135
143
|
/** For event-based feature entitlement rules, when the usage period will reset */
|
|
@@ -357,6 +365,7 @@ export declare class Schematic {
|
|
|
357
365
|
private storage;
|
|
358
366
|
private useWebSocket;
|
|
359
367
|
private checks;
|
|
368
|
+
private featureUsageEventMap;
|
|
360
369
|
private webSocketUrl;
|
|
361
370
|
constructor(apiKey: string, options?: SchematicOptions);
|
|
362
371
|
/**
|
|
@@ -410,8 +419,15 @@ export declare class Schematic {
|
|
|
410
419
|
/**
|
|
411
420
|
* Send a track event
|
|
412
421
|
* Track usage for a company and/or user.
|
|
422
|
+
* Optimistically updates feature usage flags if tracking a featureUsageEvent.
|
|
413
423
|
*/
|
|
414
424
|
track: (body: EventBodyTrack) => Promise<void>;
|
|
425
|
+
/**
|
|
426
|
+
* Optimistically update feature usage flags associated with a tracked event
|
|
427
|
+
* This updates flags in memory with updated usage counts and value/featureUsageExceeded flags
|
|
428
|
+
* before the network request completes
|
|
429
|
+
*/
|
|
430
|
+
private optimisticallyUpdateFeatureUsage;
|
|
415
431
|
/**
|
|
416
432
|
* Event processing
|
|
417
433
|
*/
|
|
@@ -436,13 +452,15 @@ export declare class Schematic {
|
|
|
436
452
|
getIsPending: () => boolean;
|
|
437
453
|
addIsPendingListener: (listener: PendingListenerFn) => () => void;
|
|
438
454
|
private setIsPending;
|
|
439
|
-
getFlagCheck: (flagKey: string) => CheckFlagReturn;
|
|
440
|
-
getFlagValue: (flagKey: string) => boolean;
|
|
455
|
+
getFlagCheck: (flagKey: string) => CheckFlagReturn | undefined;
|
|
456
|
+
getFlagValue: (flagKey: string) => boolean | undefined;
|
|
441
457
|
/** Register an event listener that will be notified with the boolean value for a given flag when this value changes */
|
|
442
458
|
addFlagValueListener: (flagKey: string, listener: FlagValueListenerFn) => () => void;
|
|
443
459
|
/** Register an event listener that will be notified with the full flag check response for a given flag whenever this value changes */
|
|
444
460
|
addFlagCheckListener: (flagKey: string, listener: FlagCheckListenerFn) => () => void;
|
|
445
461
|
private notifyFlagCheckListeners;
|
|
462
|
+
/** Add or update a CheckFlagReturn in the featureUsageEventMap */
|
|
463
|
+
private updateFeatureUsageEventMap;
|
|
446
464
|
private notifyFlagValueListeners;
|
|
447
465
|
}
|
|
448
466
|
|
package/dist/schematic.esm.js
CHANGED
|
@@ -628,6 +628,7 @@ function CheckFlagResponseDataFromJSONTyped(json, ignoreDiscriminator) {
|
|
|
628
628
|
error: json["error"] == null ? void 0 : json["error"],
|
|
629
629
|
featureAllocation: json["feature_allocation"] == null ? void 0 : json["feature_allocation"],
|
|
630
630
|
featureUsage: json["feature_usage"] == null ? void 0 : json["feature_usage"],
|
|
631
|
+
featureUsageEvent: json["feature_usage_event"] == null ? void 0 : json["feature_usage_event"],
|
|
631
632
|
featureUsagePeriod: json["feature_usage_period"] == null ? void 0 : json["feature_usage_period"],
|
|
632
633
|
featureUsageResetAt: json["feature_usage_reset_at"] == null ? void 0 : new Date(json["feature_usage_reset_at"]),
|
|
633
634
|
flag: json["flag"],
|
|
@@ -727,6 +728,7 @@ var CheckFlagReturnFromJSON = (json) => {
|
|
|
727
728
|
error,
|
|
728
729
|
featureAllocation,
|
|
729
730
|
featureUsage,
|
|
731
|
+
featureUsageEvent,
|
|
730
732
|
featureUsagePeriod,
|
|
731
733
|
featureUsageResetAt,
|
|
732
734
|
flag,
|
|
@@ -746,6 +748,7 @@ var CheckFlagReturnFromJSON = (json) => {
|
|
|
746
748
|
error: error == null ? void 0 : error,
|
|
747
749
|
featureAllocation: featureAllocation == null ? void 0 : featureAllocation,
|
|
748
750
|
featureUsage: featureUsage == null ? void 0 : featureUsage,
|
|
751
|
+
featureUsageEvent: featureUsageEvent === null ? void 0 : featureUsageEvent,
|
|
749
752
|
featureUsagePeriod: featureUsagePeriod == null ? void 0 : featureUsagePeriod,
|
|
750
753
|
featureUsageResetAt: featureUsageResetAt == null ? void 0 : featureUsageResetAt,
|
|
751
754
|
flag,
|
|
@@ -775,7 +778,7 @@ function contextString(context) {
|
|
|
775
778
|
}
|
|
776
779
|
|
|
777
780
|
// src/version.ts
|
|
778
|
-
var version = "1.2.
|
|
781
|
+
var version = "1.2.3";
|
|
779
782
|
|
|
780
783
|
// src/index.ts
|
|
781
784
|
var anonymousIdKey = "schematicId";
|
|
@@ -796,6 +799,7 @@ var Schematic = class {
|
|
|
796
799
|
storage;
|
|
797
800
|
useWebSocket = false;
|
|
798
801
|
checks = {};
|
|
802
|
+
featureUsageEventMap = {};
|
|
799
803
|
webSocketUrl = "wss://api.schematichq.com";
|
|
800
804
|
constructor(apiKey, options) {
|
|
801
805
|
this.apiKey = apiKey;
|
|
@@ -890,6 +894,9 @@ var Schematic = class {
|
|
|
890
894
|
const parsedResponse = CheckFlagResponseFromJSON(response);
|
|
891
895
|
this.debug(`checkFlag result: ${key}`, parsedResponse);
|
|
892
896
|
const result = CheckFlagReturnFromJSON(parsedResponse.data);
|
|
897
|
+
if (typeof result.featureUsageEvent === "string") {
|
|
898
|
+
this.updateFeatureUsageEventMap(result);
|
|
899
|
+
}
|
|
893
900
|
this.submitFlagCheckEvent(key, result, context);
|
|
894
901
|
return result.value;
|
|
895
902
|
}).catch((error) => {
|
|
@@ -1009,6 +1016,9 @@ var Schematic = class {
|
|
|
1009
1016
|
const data = CheckFlagResponseFromJSON(responseJson);
|
|
1010
1017
|
this.debug(`fallbackToRest result: ${key}`, data);
|
|
1011
1018
|
const result = CheckFlagReturnFromJSON(data.data);
|
|
1019
|
+
if (typeof result.featureUsageEvent === "string") {
|
|
1020
|
+
this.updateFeatureUsageEventMap(result);
|
|
1021
|
+
}
|
|
1012
1022
|
this.submitFlagCheckEvent(key, result, context);
|
|
1013
1023
|
return result.value;
|
|
1014
1024
|
} catch (error) {
|
|
@@ -1092,14 +1102,11 @@ var Schematic = class {
|
|
|
1092
1102
|
* In offline mode, this will just set the context locally without connecting.
|
|
1093
1103
|
*/
|
|
1094
1104
|
setContext = async (context) => {
|
|
1095
|
-
if (this.isOffline()) {
|
|
1105
|
+
if (this.isOffline() || !this.useWebSocket) {
|
|
1096
1106
|
this.context = context;
|
|
1097
1107
|
this.setIsPending(false);
|
|
1098
1108
|
return Promise.resolve();
|
|
1099
1109
|
}
|
|
1100
|
-
if (!this.useWebSocket) {
|
|
1101
|
-
return Promise.resolve();
|
|
1102
|
-
}
|
|
1103
1110
|
try {
|
|
1104
1111
|
this.setIsPending(true);
|
|
1105
1112
|
if (!this.conn) {
|
|
@@ -1115,18 +1122,69 @@ var Schematic = class {
|
|
|
1115
1122
|
/**
|
|
1116
1123
|
* Send a track event
|
|
1117
1124
|
* Track usage for a company and/or user.
|
|
1125
|
+
* Optimistically updates feature usage flags if tracking a featureUsageEvent.
|
|
1118
1126
|
*/
|
|
1119
1127
|
track = (body) => {
|
|
1120
|
-
const { company, user, event, traits } = body;
|
|
1128
|
+
const { company, user, event, traits, quantity = 1 } = body;
|
|
1121
1129
|
const trackData = {
|
|
1122
1130
|
company: company ?? this.context.company,
|
|
1123
1131
|
event,
|
|
1124
1132
|
traits: traits ?? {},
|
|
1125
|
-
user: user ?? this.context.user
|
|
1133
|
+
user: user ?? this.context.user,
|
|
1134
|
+
quantity
|
|
1126
1135
|
};
|
|
1127
1136
|
this.debug(`track:`, trackData);
|
|
1137
|
+
if (event in this.featureUsageEventMap) {
|
|
1138
|
+
this.optimisticallyUpdateFeatureUsage(event, quantity);
|
|
1139
|
+
}
|
|
1128
1140
|
return this.handleEvent("track", trackData);
|
|
1129
1141
|
};
|
|
1142
|
+
/**
|
|
1143
|
+
* Optimistically update feature usage flags associated with a tracked event
|
|
1144
|
+
* This updates flags in memory with updated usage counts and value/featureUsageExceeded flags
|
|
1145
|
+
* before the network request completes
|
|
1146
|
+
*/
|
|
1147
|
+
optimisticallyUpdateFeatureUsage = (eventName, quantity = 1) => {
|
|
1148
|
+
const flagsForEvent = this.featureUsageEventMap[eventName];
|
|
1149
|
+
if (flagsForEvent === void 0 || flagsForEvent === null) return;
|
|
1150
|
+
this.debug(
|
|
1151
|
+
`Optimistically updating feature usage for event: ${eventName}`,
|
|
1152
|
+
{ quantity }
|
|
1153
|
+
);
|
|
1154
|
+
Object.entries(flagsForEvent).forEach(([flagKey, check]) => {
|
|
1155
|
+
if (check === void 0) return;
|
|
1156
|
+
const updatedCheck = { ...check };
|
|
1157
|
+
if (typeof updatedCheck.featureUsage === "number") {
|
|
1158
|
+
updatedCheck.featureUsage += quantity;
|
|
1159
|
+
if (typeof updatedCheck.featureAllocation === "number") {
|
|
1160
|
+
const wasExceeded = updatedCheck.featureUsageExceeded === true;
|
|
1161
|
+
const nowExceeded = updatedCheck.featureUsage >= updatedCheck.featureAllocation;
|
|
1162
|
+
if (nowExceeded !== wasExceeded) {
|
|
1163
|
+
updatedCheck.featureUsageExceeded = nowExceeded;
|
|
1164
|
+
if (nowExceeded) {
|
|
1165
|
+
updatedCheck.value = false;
|
|
1166
|
+
}
|
|
1167
|
+
this.debug(`Usage limit status changed for flag: ${flagKey}`, {
|
|
1168
|
+
was: wasExceeded ? "exceeded" : "within limits",
|
|
1169
|
+
now: nowExceeded ? "exceeded" : "within limits",
|
|
1170
|
+
featureUsage: updatedCheck.featureUsage,
|
|
1171
|
+
featureAllocation: updatedCheck.featureAllocation,
|
|
1172
|
+
value: updatedCheck.value
|
|
1173
|
+
});
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
if (this.featureUsageEventMap[eventName] !== void 0) {
|
|
1177
|
+
this.featureUsageEventMap[eventName][flagKey] = updatedCheck;
|
|
1178
|
+
}
|
|
1179
|
+
const contextStr = contextString(this.context);
|
|
1180
|
+
if (this.checks[contextStr] !== void 0 && this.checks[contextStr] !== null) {
|
|
1181
|
+
this.checks[contextStr][flagKey] = updatedCheck;
|
|
1182
|
+
}
|
|
1183
|
+
this.notifyFlagCheckListeners(flagKey, updatedCheck);
|
|
1184
|
+
this.notifyFlagValueListeners(flagKey, updatedCheck.value);
|
|
1185
|
+
}
|
|
1186
|
+
});
|
|
1187
|
+
};
|
|
1130
1188
|
/**
|
|
1131
1189
|
* Event processing
|
|
1132
1190
|
*/
|
|
@@ -1269,12 +1327,19 @@ var Schematic = class {
|
|
|
1269
1327
|
}
|
|
1270
1328
|
(message.flags ?? []).forEach((flag) => {
|
|
1271
1329
|
const flagCheck = CheckFlagReturnFromJSON(flag);
|
|
1272
|
-
|
|
1330
|
+
const contextStr = contextString(context);
|
|
1331
|
+
if (this.checks[contextStr] === void 0) {
|
|
1332
|
+
this.checks[contextStr] = {};
|
|
1333
|
+
}
|
|
1334
|
+
this.checks[contextStr][flagCheck.flag] = flagCheck;
|
|
1273
1335
|
this.debug(`WebSocket flag update:`, {
|
|
1274
1336
|
flag: flagCheck.flag,
|
|
1275
1337
|
value: flagCheck.value,
|
|
1276
1338
|
flagCheck
|
|
1277
1339
|
});
|
|
1340
|
+
if (typeof flagCheck.featureUsageEvent === "string") {
|
|
1341
|
+
this.updateFeatureUsageEventMap(flagCheck);
|
|
1342
|
+
}
|
|
1278
1343
|
if (this.flagCheckListeners[flag.flag]?.size > 0 || this.flagValueListeners[flag.flag]?.size > 0) {
|
|
1279
1344
|
this.submitFlagCheckEvent(flagCheck.flag, flagCheck, context);
|
|
1280
1345
|
}
|
|
@@ -1367,8 +1432,26 @@ var Schematic = class {
|
|
|
1367
1432
|
check
|
|
1368
1433
|
);
|
|
1369
1434
|
}
|
|
1435
|
+
if (typeof check.featureUsageEvent === "string") {
|
|
1436
|
+
this.updateFeatureUsageEventMap(check);
|
|
1437
|
+
}
|
|
1370
1438
|
listeners.forEach((listener) => notifyFlagCheckListener(listener, check));
|
|
1371
1439
|
};
|
|
1440
|
+
/** Add or update a CheckFlagReturn in the featureUsageEventMap */
|
|
1441
|
+
updateFeatureUsageEventMap = (check) => {
|
|
1442
|
+
if (typeof check.featureUsageEvent !== "string") return;
|
|
1443
|
+
const eventName = check.featureUsageEvent;
|
|
1444
|
+
if (this.featureUsageEventMap[eventName] === void 0 || this.featureUsageEventMap[eventName] === null) {
|
|
1445
|
+
this.featureUsageEventMap[eventName] = {};
|
|
1446
|
+
}
|
|
1447
|
+
if (this.featureUsageEventMap[eventName] !== void 0) {
|
|
1448
|
+
this.featureUsageEventMap[eventName][check.flag] = check;
|
|
1449
|
+
}
|
|
1450
|
+
this.debug(
|
|
1451
|
+
`Updated featureUsageEventMap for event: ${eventName}, flag: ${check.flag}`,
|
|
1452
|
+
check
|
|
1453
|
+
);
|
|
1454
|
+
};
|
|
1372
1455
|
notifyFlagValueListeners = (flagKey, value) => {
|
|
1373
1456
|
const listeners = this.flagValueListeners?.[flagKey] ?? [];
|
|
1374
1457
|
if (listeners.size > 0) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@schematichq/schematic-js",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.3",
|
|
4
4
|
"main": "dist/schematic.cjs.js",
|
|
5
5
|
"module": "dist/schematic.esm.js",
|
|
6
6
|
"types": "dist/schematic.d.ts",
|
|
@@ -33,23 +33,24 @@
|
|
|
33
33
|
"uuid": "^11.0.5"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
|
-
"@
|
|
37
|
-
"@
|
|
36
|
+
"@eslint/js": "^9.24.0",
|
|
37
|
+
"@microsoft/api-extractor": "^7.52.2",
|
|
38
|
+
"@openapitools/openapi-generator-cli": "^2.18.4",
|
|
38
39
|
"@types/jest": "^29.5.14",
|
|
39
40
|
"@types/uuid": "^10.0.0",
|
|
40
|
-
"
|
|
41
|
-
"@typescript-eslint/parser": "^8.23.0",
|
|
42
|
-
"esbuild": "^0.25.0",
|
|
41
|
+
"esbuild": "^0.25.2",
|
|
43
42
|
"esbuild-jest": "^0.5.0",
|
|
44
|
-
"eslint": "^
|
|
43
|
+
"eslint": "^9.24.0",
|
|
44
|
+
"globals": "^16.0.0",
|
|
45
45
|
"jest": "^29.7.0",
|
|
46
46
|
"jest-environment-jsdom": "^29.7.0",
|
|
47
47
|
"jest-esbuild": "^0.3.0",
|
|
48
48
|
"jest-fetch-mock": "^3.0.3",
|
|
49
49
|
"mock-socket": "^9.3.1",
|
|
50
50
|
"prettier": "^3.4.2",
|
|
51
|
-
"ts-jest": "^29.
|
|
52
|
-
"typescript": "^5.7.3"
|
|
51
|
+
"ts-jest": "^29.3.0",
|
|
52
|
+
"typescript": "^5.7.3",
|
|
53
|
+
"typescript-eslint": "^8.29.1"
|
|
53
54
|
},
|
|
54
55
|
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
|
|
55
56
|
}
|