@schematichq/schematic-js 1.2.7 → 1.2.9
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/dist/schematic.browser.js +2 -2
- package/dist/schematic.cjs.js +266 -43
- package/dist/schematic.d.ts +39 -0
- package/dist/schematic.esm.js +266 -43
- package/package.json +5 -5
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
"use strict";(()=>{var re=Object.create;var
|
|
2
|
-
`)===0?h.substr(1,h.length):h}).forEach(function(h){var g=h.split(":"),d=g.shift().trim();if(d){var _=g.join(":").trim();try{a.append(d,_)}catch(I){console.warn("Response "+I.message)}}}),a}q.call(C.prototype);function E(n,a){if(!(this instanceof E))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 p(a.headers),this.url=a.url||"",this._initBody(n)}q.call(E.prototype),E.prototype.clone=function(){return new E(this._bodyInit,{status:this.status,statusText:this.statusText,headers:new p(this.headers),url:this.url})},E.error=function(){var n=new E(null,{status:200,statusText:""});return n.ok=!1,n.status=0,n.type="error",n};var ne=[301,302,303,307,308];E.redirect=function(n,a){if(ne.indexOf(a)===-1)throw new RangeError("Invalid status code");return new E(null,{status:a,headers:{location:n}})},t.DOMException=s.DOMException;try{new t.DOMException}catch{t.DOMException=function(a,u){this.message=a,this.name=u;var h=Error(a);this.stack=h.stack},t.DOMException.prototype=Object.create(Error.prototype),t.DOMException.prototype.constructor=t.DOMException}function A(n,a){return new Promise(function(u,h){var g=new C(n,a);if(g.signal&&g.signal.aborted)return h(new t.DOMException("Aborted","AbortError"));var d=new XMLHttpRequest;function _(){d.abort()}d.onload=function(){var b={statusText:d.statusText,headers:te(d.getAllResponseHeaders()||"")};g.url.indexOf("file://")===0&&(d.status<200||d.status>599)?b.status=200:b.status=d.status,b.url="responseURL"in d?d.responseURL:b.headers.get("X-Request-URL");var S="response"in d?d.response:d.responseText;setTimeout(function(){u(new E(S,b))},0)},d.onerror=function(){setTimeout(function(){h(new TypeError("Network request failed"))},0)},d.ontimeout=function(){setTimeout(function(){h(new TypeError("Network request timed out"))},0)},d.onabort=function(){setTimeout(function(){h(new t.DOMException("Aborted","AbortError"))},0)};function I(b){try{return b===""&&s.location.href?s.location.href:b}catch{return b}}if(d.open(g.method,I(g.url),!0),g.credentials==="include"?d.withCredentials=!0:g.credentials==="omit"&&(d.withCredentials=!1),"responseType"in d&&(i.blob?d.responseType="blob":i.arrayBuffer&&(d.responseType="arraybuffer")),a&&typeof a.headers=="object"&&!(a.headers instanceof p||s.Headers&&a.headers instanceof s.Headers)){var W=[];Object.getOwnPropertyNames(a.headers).forEach(function(b){W.push(f(b)),d.setRequestHeader(b,y(a.headers[b]))}),g.headers.forEach(function(b,S){W.indexOf(S)===-1&&d.setRequestHeader(S,b)})}else g.headers.forEach(function(b,S){d.setRequestHeader(S,b)});g.signal&&(g.signal.addEventListener("abort",_),d.onreadystatechange=function(){d.readyState===4&&g.signal.removeEventListener("abort",_)}),d.send(typeof g._bodyInit>"u"?null:g._bodyInit)})}return A.polyfill=!0,s.fetch||(s.fetch=A,s.Headers=p,s.Request=C,s.Response=E),t.Headers=p,t.Request=C,t.Response=E,t.fetch=A,t})({})})(typeof self<"u"?self:Q)});var k=[];for(let r=0;r<256;++r)k.push((r+256).toString(16).slice(1));function K(r,e=0){return(k[r[e+0]]+k[r[e+1]]+k[r[e+2]]+k[r[e+3]]+"-"+k[r[e+4]]+k[r[e+5]]+"-"+k[r[e+6]]+k[r[e+7]]+"-"+k[r[e+8]]+k[r[e+9]]+"-"+k[r[e+10]]+k[r[e+11]]+k[r[e+12]]+k[r[e+13]]+k[r[e+14]]+k[r[e+15]]).toLowerCase()}var P,de=new Uint8Array(16);function B(){if(!P){if(typeof crypto>"u"||!crypto.getRandomValues)throw new Error("crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported");P=crypto.getRandomValues.bind(crypto)}return P(de)}var fe=typeof crypto<"u"&&crypto.randomUUID&&crypto.randomUUID.bind(crypto),L={randomUUID:fe};function he(r,e,t){r=r||{};let s=r.random??r.rng?.()??B();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(t=t||0,t<0||t+16>e.length)throw new RangeError(`UUID byte range ${t}:${t+15} is out of buffer bounds`);for(let i=0;i<16;++i)e[t+i]=s[i];return e}return K(s)}function pe(r,e,t){return L.randomUUID&&!e&&!r?L.randomUUID():he(r,e,t)}var x=pe;var st=ue(z());function T(r){return ge(r,!1)}function ge(r,e){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,featureUsageEvent:r.feature_usage_event==null?void 0:r.feature_usage_event,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 N(r){return ye(r,!1)}function ye(r,e=!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 D(r){return be(r,!1)}function be(r,e){return r==null?r:{data:T(r.data),params:r.params}}function X(r){return ke(r,!1)}function ke(r,e){return r==null?r:{flags:r.flags.map(T)}}function M(r){return ve(r,!1)}function ve(r,e){return r==null?r:{data:X(r.data),params:r.params}}var U=r=>{let{companyId:e,error:t,featureAllocation:s,featureUsage:i,featureUsageEvent:c,featureUsagePeriod:o,featureUsageResetAt:l,flag:f,flagId:y,reason:w,ruleId:p,ruleType:v,userId:m,value:R}=T(r);return{featureUsageExceeded:!R&&(v=="company_override_usage_exceeded"||v=="plan_entitlement_usage_exceeded"),companyId:e??void 0,error:t??void 0,featureAllocation:s??void 0,featureUsage:i??void 0,featureUsageEvent:c===null?void 0:c,featureUsagePeriod:o??void 0,featureUsageResetAt:l??void 0,flag:f,flagId:y??void 0,reason:w,ruleId:p??void 0,ruleType:v??void 0,userId:m??void 0,value:R}};function F(r){let e=Object.keys(r).reduce((t,s)=>{let c=Object.keys(r[s]||{}).sort().reduce((o,l)=>(o[l]=r[s][l],o),{});return t[s]=c,t},{});return JSON.stringify(e)}var J="1.2.7";var G="schematicId";var O=class{additionalHeaders={};apiKey;apiUrl="https://api.schematichq.com";conn=null;context={};debugEnabled=!1;offlineEnabled=!1;eventQueue;contextDependentEventQueue;eventUrl="https://c.schematichq.com";flagCheckListeners={};flagValueListeners={};isPending=!0;isPendingListeners=new Set;storage;useWebSocket=!1;checks={};featureUsageEventMap={};webSocketUrl="wss://api.schematichq.com";webSocketConnectionTimeout=1e4;webSocketReconnect=!0;webSocketMaxReconnectAttempts=7;webSocketInitialRetryDelay=1e3;webSocketMaxRetryDelay=3e4;wsReconnectAttempts=0;wsReconnectTimer=null;wsIntentionalDisconnect=!1;constructor(e,t){if(this.apiKey=e,this.eventQueue=[],this.contextDependentEventQueue=[],this.useWebSocket=t?.useWebSocket??!1,this.debugEnabled=t?.debug??!1,this.offlineEnabled=t?.offline??!1,typeof window<"u"&&typeof window.location<"u"){let 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&&t?.debug!==!1&&(this.debugEnabled=!0),this.offlineEnabled&&this.setIsPending(!1),this.additionalHeaders={"X-Schematic-Client-Version":`schematic-js@${J}`,...t?.additionalHeaders??{}},t?.storage?this.storage=t.storage:typeof localStorage<"u"&&(this.storage=localStorage),t?.apiUrl!==void 0&&(this.apiUrl=t.apiUrl),t?.eventUrl!==void 0&&(this.eventUrl=t.eventUrl),t?.webSocketUrl!==void 0&&(this.webSocketUrl=t.webSocketUrl),t?.webSocketConnectionTimeout!==void 0&&(this.webSocketConnectionTimeout=t.webSocketConnectionTimeout),t?.webSocketReconnect!==void 0&&(this.webSocketReconnect=t.webSocketReconnect),t?.webSocketMaxReconnectAttempts!==void 0&&(this.webSocketMaxReconnectAttempts=t.webSocketMaxReconnectAttempts),t?.webSocketInitialRetryDelay!==void 0&&(this.webSocketInitialRetryDelay=t.webSocketInitialRetryDelay),t?.webSocketMaxRetryDelay!==void 0&&(this.webSocketMaxRetryDelay=t.webSocketMaxRetryDelay),typeof window<"u"&&window?.addEventListener&&(window.addEventListener("beforeunload",()=>{this.flushEventQueue(),this.flushContextDependentEventQueue()}),this.useWebSocket&&(window.addEventListener("offline",()=>{this.debug("Browser went offline, closing WebSocket connection"),this.handleNetworkOffline()}),window.addEventListener("online",()=>{this.debug("Browser came online, attempting to reconnect WebSocket"),this.handleNetworkOnline()}))),this.offlineEnabled?this.debug("Initialized with offline mode enabled - no network requests will be made"):this.debugEnabled&&this.debug("Initialized with debug mode enabled")}async checkFlag(e){let{fallback:t=!1,key:s}=e,i=e.context||this.context,c=F(i);if(this.debug(`checkFlag: ${s}`,{context:i,fallback:t}),this.isOffline())return this.debug(`checkFlag offline result: ${s}`,{value:t,offlineMode:!0}),t;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(i)}).then(l=>{if(!l.ok)throw new Error("Network response was not ok");return l.json()}).then(l=>{let f=D(l);this.debug(`checkFlag result: ${s}`,f);let y=U(f.data);return typeof y.featureUsageEvent=="string"&&this.updateFeatureUsageEventMap(y),this.submitFlagCheckEvent(s,y,i),y.value}).catch(l=>{console.error("There was a problem with the fetch operation:",l);let f={flag:s,value:t,reason:"API request failed",error:l instanceof Error?l.message:String(l)};return this.submitFlagCheckEvent(s,f,i),t})}try{let o=this.checks[c];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 t;try{await this.setContext(i)}catch(w){return console.error("WebSocket connection failed, falling back to REST:",w),this.fallbackToRest(s,i,t)}let f=(this.checks[c]??{})[s],y=f?.value??t;return this.debug(`checkFlag WebSocket result: ${s}`,typeof f<"u"?f:{value:t,fallbackUsed:!0}),typeof f<"u"&&this.submitFlagCheckEvent(s,f,i),y}catch(o){console.error("Unexpected error in checkFlag:",o);let l={flag:s,value:t,reason:"Unexpected error in flag check",error:o instanceof Error?o.message:String(o)};return this.submitFlagCheckEvent(s,l,i),t}}debug(e,...t){this.debugEnabled&&console.log(`[Schematic] ${e}`,...t)}isOffline(){return this.offlineEnabled}submitFlagCheckEvent(e,t,s){let i={flagKey:e,value:t.value,reason:t.reason,flagId:t.flagId,ruleId:t.ruleId,companyId:t.companyId,userId:t.userId,error:t.error,reqCompany:s.company,reqUser:s.user};return this.debug("submitting flag check event:",i),this.handleEvent("flag_check",N(i))}async fallbackToRest(e,t,s){if(this.isOffline())return this.debug(`fallbackToRest offline result: ${e}`,{value:s,offlineMode:!0}),s;try{let i=`${this.apiUrl}/flags/${e}/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(t)});if(!c.ok)throw new Error("Network response was not ok");let o=await c.json(),l=D(o);this.debug(`fallbackToRest result: ${e}`,l);let f=U(l.data);return typeof f.featureUsageEvent=="string"&&this.updateFeatureUsageEventMap(f),this.submitFlagCheckEvent(e,f,t),f.value}catch(i){console.error("REST API call failed, using fallback value:",i);let c={flag:e,value:s,reason:"API request failed (fallback)",error:i instanceof Error?i.message:String(i)};return this.submitFlagCheckEvent(e,c,t),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 t=`${this.apiUrl}/flags/check`,s=JSON.stringify(e);return fetch(t,{method:"POST",headers:{...this.additionalHeaders??{},"Content-Type":"application/json;charset=UTF-8","X-Schematic-Api-Key":this.apiKey},body:s}).then(i=>{if(!i.ok)throw new Error("Network response was not ok");return i.json()}).then(i=>{let c=M(i);return this.debug("checkFlags result:",c),(c?.data?.flags??[]).reduce((o,l)=>(o[l.flag]=l.value,o),{})}).catch(i=>(console.error("There was a problem with the fetch operation:",i),{}))};identify=e=>{this.debug("identify:",e);try{this.setContext({company:e.company?.keys,user:e.keys})}catch(t){console.error("Error setting context:",t)}return this.handleEvent("identify",e)};setContext=async e=>{if(this.isOffline()||!this.useWebSocket)return this.context=e,this.flushContextDependentEventQueue(),this.setIsPending(!1),Promise.resolve();try{this.setIsPending(!0),this.conn||(this.wsReconnectTimer!==null&&(this.debug("Cancelling scheduled reconnection, connecting immediately"),clearTimeout(this.wsReconnectTimer),this.wsReconnectTimer=null),this.conn=this.wsConnect());let t=await this.conn;await this.wsSendMessage(t,e)}catch(t){throw console.error("Failed to establish WebSocket connection:",t),t}};track=e=>{let{company:t,user:s,event:i,traits:c,quantity:o=1}=e;if(!this.hasContext(t,s)){this.debug(`track: queuing event "${i}" until context is available`);let f={api_key:this.apiKey,body:{company:t,event:i,traits:c??{},user:s,quantity:o},sent_at:new Date().toISOString(),tracker_event_id:x(),tracker_user_id:this.getAnonymousId(),type:"track"};return this.contextDependentEventQueue.push(f),Promise.resolve()}let l={company:t??this.context.company,event:i,traits:c??{},user:s??this.context.user,quantity:o};return this.debug("track:",l),i in this.featureUsageEventMap&&this.optimisticallyUpdateFeatureUsage(i,o),this.handleEvent("track",l)};optimisticallyUpdateFeatureUsage=(e,t=1)=>{let s=this.featureUsageEventMap[e];s!=null&&(this.debug(`Optimistically updating feature usage for event: ${e}`,{quantity:t}),Object.entries(s).forEach(([i,c])=>{if(c===void 0)return;let o={...c};if(typeof o.featureUsage=="number"){if(o.featureUsage+=t,typeof o.featureAllocation=="number"){let f=o.featureUsageExceeded===!0,y=o.featureUsage>=o.featureAllocation;y!==f&&(o.featureUsageExceeded=y,y&&(o.value=!1),this.debug(`Usage limit status changed for flag: ${i}`,{was:f?"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][i]=o);let l=F(this.context);this.checks[l]!==void 0&&this.checks[l]!==null&&(this.checks[l][i]=o),this.notifyFlagCheckListeners(i,o),this.notifyFlagValueListeners(i,o.value)}}))};hasContext=(e,t)=>{let s=e!=null&&Object.keys(e).length>0||t!=null&&Object.keys(t).length>0,i=this.context.company!==void 0&&this.context.company!==null&&Object.keys(this.context.company).length>0||this.context.user!==void 0&&this.context.user!==null&&Object.keys(this.context.user).length>0;return s||i};flushContextDependentEventQueue=()=>{for(this.debug(`flushing ${this.contextDependentEventQueue.length} context-dependent events`);this.contextDependentEventQueue.length>0;){let e=this.contextDependentEventQueue.shift();if(e)if(e.type==="track"&&typeof e.body=="object"&&e.body!==null){let t=e.body,s={...t,company:t.company??this.context.company,user:t.user??this.context.user},i={...e,body:s,sent_at:new Date().toISOString()};this.sendEvent(i)}else this.sendEvent(e)}};flushEventQueue=()=>{for(;this.eventQueue.length>0;){let e=this.eventQueue.shift();e&&this.sendEvent(e)}};getAnonymousId=()=>{if(!this.storage)return x();let e=this.storage.getItem(G);if(typeof e<"u")return e;let t=x();return this.storage.setItem(G,t),t};handleEvent=(e,t)=>{let s={api_key:this.apiKey,body:t,sent_at:new Date().toISOString(),tracker_event_id:x(),tracker_user_id:this.getAnonymousId(),type:e};return typeof document<"u"&&document?.hidden?this.storeEvent(s):this.sendEvent(s)};sendEvent=async e=>{let t=`${this.eventUrl}/e`,s=JSON.stringify(e);if(this.debug("sending event:",{url:t,event:e}),this.isOffline())return this.debug("event not sent (offline mode):",{event:e}),Promise.resolve();try{let i=await fetch(t,{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=e=>(this.eventQueue.push(e),Promise.resolve());cleanup=async()=>{if(this.isOffline())return this.debug("cleanup: skipped (offline mode)"),Promise.resolve();if(this.wsIntentionalDisconnect=!0,this.wsReconnectTimer!==null&&(clearTimeout(this.wsReconnectTimer),this.wsReconnectTimer=null),this.conn)try{(await this.conn).close()}catch(e){console.error("Error during cleanup:",e)}finally{this.conn=null}};calculateReconnectDelay=()=>{let e=this.webSocketInitialRetryDelay*Math.pow(2,this.wsReconnectAttempts),t=Math.min(e,this.webSocketMaxRetryDelay),s=Math.random()*t*.5,i=t+s;return this.debug(`Reconnect delay calculated: ${i.toFixed(0)}ms (attempt ${this.wsReconnectAttempts+1}/${this.webSocketMaxReconnectAttempts})`),i};handleNetworkOffline=async()=>{if(this.conn!==null){try{(await this.conn).close()}catch(e){this.debug("Error closing connection on offline:",e)}this.conn=null}this.wsReconnectTimer!==null&&(clearTimeout(this.wsReconnectTimer),this.wsReconnectTimer=null)};handleNetworkOnline=()=>{if(this.context.company===void 0&&this.context.user===void 0){this.debug("No context set, skipping reconnection");return}this.wsReconnectAttempts=0,this.wsReconnectTimer!==null&&(clearTimeout(this.wsReconnectTimer),this.wsReconnectTimer=null),this.debug("Network online, reconnecting immediately"),this.attemptReconnect()};attemptReconnect=()=>{if(this.wsReconnectAttempts>=this.webSocketMaxReconnectAttempts){this.debug(`Maximum reconnection attempts (${this.webSocketMaxReconnectAttempts}) reached, giving up`);return}this.wsReconnectTimer!==null&&clearTimeout(this.wsReconnectTimer);let e=this.calculateReconnectDelay();this.debug(`Scheduling reconnection attempt ${this.wsReconnectAttempts+1}/${this.webSocketMaxReconnectAttempts} in ${e.toFixed(0)}ms`),this.wsReconnectTimer=setTimeout(async()=>{this.wsReconnectTimer=null,this.wsReconnectAttempts++,this.debug(`Attempting to reconnect (attempt ${this.wsReconnectAttempts}/${this.webSocketMaxReconnectAttempts})`);try{this.conn=this.wsConnect();let t=await this.conn;(this.context.company!==void 0||this.context.user!==void 0)&&(this.debug("Reconnected, re-sending context"),await this.wsSendMessage(t,this.context)),this.debug("Reconnection successful")}catch(t){this.debug("Reconnection attempt failed:",t)}},e)};wsConnect=()=>this.isOffline()?(this.debug("wsConnect: skipped (offline mode)"),Promise.reject(new Error("WebSocket connection skipped in offline mode"))):new Promise((e,t)=>{let s=`${this.webSocketUrl}/flags/bootstrap?apiKey=${this.apiKey}`;this.debug("connecting to WebSocket:",s);let i=new WebSocket(s),c=null,o=!1;c=setTimeout(()=>{o||(this.debug(`WebSocket connection timeout after ${this.webSocketConnectionTimeout}ms`),i.close(),t(new Error("WebSocket connection timeout")))},this.webSocketConnectionTimeout),i.onopen=()=>{o=!0,c!==null&&clearTimeout(c),this.wsReconnectAttempts=0,this.wsIntentionalDisconnect=!1,this.debug("WebSocket connection opened"),e(i)},i.onerror=l=>{o=!0,c!==null&&clearTimeout(c),this.debug("WebSocket connection error:",l),t(l)},i.onclose=()=>{o=!0,c!==null&&clearTimeout(c),this.debug("WebSocket connection closed"),this.conn=null,!this.wsIntentionalDisconnect&&this.webSocketReconnect&&this.attemptReconnect()}});wsSendMessage=(e,t)=>this.isOffline()?(this.debug("wsSendMessage: skipped (offline mode)"),this.setIsPending(!1),Promise.resolve()):new Promise((s,i)=>{if(F(t)==F(this.context))return this.debug("WebSocket context unchanged, skipping update"),s(this.setIsPending(!1));this.debug("WebSocket context updated:",t),this.context=t;let c=()=>{let o=!1,l=w=>{let p=JSON.parse(w.data);this.debug("WebSocket message received:",p),F(t)in this.checks||(this.checks[F(t)]={}),(p.flags??[]).forEach(v=>{let m=U(v),R=F(t);this.checks[R]===void 0&&(this.checks[R]={}),this.checks[R][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,t),this.notifyFlagCheckListeners(v.flag,m),this.notifyFlagValueListeners(v.flag,m.value)}),this.flushContextDependentEventQueue(),this.setIsPending(!1),o||(o=!0,s())};e.addEventListener("message",l);let f=this.additionalHeaders["X-Schematic-Client-Version"]??`schematic-js@${J}`,y={apiKey:this.apiKey,clientVersion:f,data:t};this.debug("WebSocket sending message:",y),e.send(JSON.stringify(y))};e.readyState===WebSocket.OPEN?(this.debug("WebSocket already open, sending message"),c()):e.readyState===WebSocket.CONNECTING?(this.debug("WebSocket connecting, waiting for open to send message"),e.addEventListener("open",c)):(this.debug("WebSocket is closed, cannot send message"),i("WebSocket is not open or connecting"))});getIsPending=()=>this.isPending;addIsPendingListener=e=>(this.isPendingListeners.add(e),()=>{this.isPendingListeners.delete(e)});setIsPending=e=>{this.isPending=e,this.isPendingListeners.forEach(t=>Ee(t,e))};getFlagCheck=e=>{let t=F(this.context);return(this.checks[t]??{})[e]};getFlagValue=e=>this.getFlagCheck(e)?.value;addFlagValueListener=(e,t)=>(e in this.flagValueListeners||(this.flagValueListeners[e]=new Set),this.flagValueListeners[e].add(t),()=>{this.flagValueListeners[e].delete(t)});addFlagCheckListener=(e,t)=>(e in this.flagCheckListeners||(this.flagCheckListeners[e]=new Set),this.flagCheckListeners[e].add(t),()=>{this.flagCheckListeners[e].delete(t)});notifyFlagCheckListeners=(e,t)=>{let s=this.flagCheckListeners?.[e]??[];s.size>0&&this.debug(`Notifying ${s.size} flag check listeners for ${e}`,t),typeof t.featureUsageEvent=="string"&&this.updateFeatureUsageEventMap(t),s.forEach(i=>we(i,t))};updateFeatureUsageEventMap=e=>{if(typeof e.featureUsageEvent!="string")return;let t=e.featureUsageEvent;(this.featureUsageEventMap[t]===void 0||this.featureUsageEventMap[t]===null)&&(this.featureUsageEventMap[t]={}),this.featureUsageEventMap[t]!==void 0&&(this.featureUsageEventMap[t][e.flag]=e),this.debug(`Updated featureUsageEventMap for event: ${t}, flag: ${e.flag}`,e)};notifyFlagValueListeners=(e,t)=>{let s=this.flagValueListeners?.[e]??[];s.size>0&&this.debug(`Notifying ${s.size} flag value listeners for ${e}`,{value:t}),s.forEach(i=>Fe(i,t))}},Ee=(r,e)=>{r.length>0?r(e):r()},we=(r,e)=>{r.length>0?r(e):r()},Fe=(r,e)=>{r.length>0?r(e):r()};window.Schematic=O;})();
|
|
1
|
+
"use strict";(()=>{var re=Object.create;var Q=Object.defineProperty;var se=Object.getOwnPropertyDescriptor;var ie=Object.getOwnPropertyNames;var ae=Object.getPrototypeOf,oe=Object.prototype.hasOwnProperty;var le=(r,e)=>()=>(e||r((e={exports:{}}).exports,e),e.exports);var ce=(r,e,t,i)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of ie(e))!oe.call(r,s)&&s!==t&&Q(r,s,{get:()=>e[s],enumerable:!(i=se(e,s))||i.enumerable});return r};var ue=(r,e,t)=>(t=r!=null?re(ae(r)):{},ce(e||!r||!r.__esModule?Q(t,"default",{value:r,enumerable:!0}):t,r));var K=le(z=>{(function(r){var e=(function(t){var i=typeof globalThis<"u"&&globalThis||typeof r<"u"&&r||typeof global<"u"&&global||{},s={searchParams:"URLSearchParams"in i,iterable:"Symbol"in i&&"iterator"in Symbol,blob:"FileReader"in i&&"Blob"in i&&(function(){try{return new Blob,!0}catch{return!1}})(),formData:"FormData"in i,arrayBuffer:"ArrayBuffer"in i};function l(n){return n&&DataView.prototype.isPrototypeOf(n)}if(s.arrayBuffer)var o=["[object Int8Array]","[object Uint8Array]","[object Uint8ClampedArray]","[object Int16Array]","[object Uint16Array]","[object Int32Array]","[object Uint32Array]","[object Float32Array]","[object Float64Array]"],c=ArrayBuffer.isView||function(n){return n&&o.indexOf(Object.prototype.toString.call(n))>-1};function d(n){if(typeof n!="string"&&(n=String(n)),/[^a-z0-9\-#$%&'*+.^_`|~!]/i.test(n)||n==="")throw new TypeError('Invalid character in header field name: "'+n+'"');return n.toLowerCase()}function g(n){return typeof n!="string"&&(n=String(n)),n}function E(n){var a={next:function(){var u=n.shift();return{done:u===void 0,value:u}}};return s.iterable&&(a[Symbol.iterator]=function(){return a}),a}function h(n){this.map={},n instanceof h?n.forEach(function(a,u){this.append(u,a)},this):Array.isArray(n)?n.forEach(function(a){if(a.length!=2)throw new TypeError("Headers constructor: expected name/value pair to be length 2, found"+a.length);this.append(a[0],a[1])},this):n&&Object.getOwnPropertyNames(n).forEach(function(a){this.append(a,n[a])},this)}h.prototype.append=function(n,a){n=d(n),a=g(a);var u=this.map[n];this.map[n]=u?u+", "+a:a},h.prototype.delete=function(n){delete this.map[d(n)]},h.prototype.get=function(n){return n=d(n),this.has(n)?this.map[n]:null},h.prototype.has=function(n){return this.map.hasOwnProperty(d(n))},h.prototype.set=function(n,a){this.map[d(n)]=g(a)},h.prototype.forEach=function(n,a){for(var u in this.map)this.map.hasOwnProperty(u)&&n.call(a,this.map[u],u,this)},h.prototype.keys=function(){var n=[];return this.forEach(function(a,u){n.push(u)}),E(n)},h.prototype.values=function(){var n=[];return this.forEach(function(a){n.push(a)}),E(n)},h.prototype.entries=function(){var n=[];return this.forEach(function(a,u){n.push([u,a])}),E(n)},s.iterable&&(h.prototype[Symbol.iterator]=h.prototype.entries);function b(n){if(!n._noBody){if(n.bodyUsed)return Promise.reject(new TypeError("Already read"));n.bodyUsed=!0}}function m(n){return new Promise(function(a,u){n.onload=function(){a(n.result)},n.onerror=function(){u(n.error)}})}function F(n){var a=new FileReader,u=m(a);return a.readAsArrayBuffer(n),u}function $(n){var a=new FileReader,u=m(a),p=/charset=([A-Za-z0-9_-]+)/.exec(n.type),y=p?p[1]:"utf-8";return a.readAsText(n,y),u}function Y(n){for(var a=new Uint8Array(n),u=new Array(a.length),p=0;p<a.length;p++)u[p]=String.fromCharCode(a[p]);return u.join("")}function J(n){if(n.slice)return n.slice(0);var a=new Uint8Array(n.byteLength);return a.set(new Uint8Array(n)),a.buffer}function q(){return this.bodyUsed=!1,this._initBody=function(n){this.bodyUsed=this.bodyUsed,this._bodyInit=n,n?typeof n=="string"?this._bodyText=n:s.blob&&Blob.prototype.isPrototypeOf(n)?this._bodyBlob=n:s.formData&&FormData.prototype.isPrototypeOf(n)?this._bodyFormData=n:s.searchParams&&URLSearchParams.prototype.isPrototypeOf(n)?this._bodyText=n.toString():s.arrayBuffer&&s.blob&&l(n)?(this._bodyArrayBuffer=J(n.buffer),this._bodyInit=new Blob([this._bodyArrayBuffer])):s.arrayBuffer&&(ArrayBuffer.prototype.isPrototypeOf(n)||c(n))?this._bodyArrayBuffer=J(n):this._bodyText=n=Object.prototype.toString.call(n):(this._noBody=!0,this._bodyText=""),this.headers.get("content-type")||(typeof n=="string"?this.headers.set("content-type","text/plain;charset=UTF-8"):this._bodyBlob&&this._bodyBlob.type?this.headers.set("content-type",this._bodyBlob.type):s.searchParams&&URLSearchParams.prototype.isPrototypeOf(n)&&this.headers.set("content-type","application/x-www-form-urlencoded;charset=UTF-8"))},s.blob&&(this.blob=function(){var n=b(this);if(n)return n;if(this._bodyBlob)return Promise.resolve(this._bodyBlob);if(this._bodyArrayBuffer)return Promise.resolve(new Blob([this._bodyArrayBuffer]));if(this._bodyFormData)throw new Error("could not read FormData body as blob");return Promise.resolve(new Blob([this._bodyText]))}),this.arrayBuffer=function(){if(this._bodyArrayBuffer){var n=b(this);return n||(ArrayBuffer.isView(this._bodyArrayBuffer)?Promise.resolve(this._bodyArrayBuffer.buffer.slice(this._bodyArrayBuffer.byteOffset,this._bodyArrayBuffer.byteOffset+this._bodyArrayBuffer.byteLength)):Promise.resolve(this._bodyArrayBuffer))}else{if(s.blob)return this.blob().then(F);throw new Error("could not read as ArrayBuffer")}},this.text=function(){var n=b(this);if(n)return n;if(this._bodyBlob)return $(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)},s.formData&&(this.formData=function(){return this.text().then(ee)}),this.json=function(){return this.text().then(JSON.parse)},this}var Z=["CONNECT","DELETE","GET","HEAD","OPTIONS","PATCH","POST","PUT","TRACE"];function j(n){var a=n.toUpperCase();return Z.indexOf(a)>-1?a:n}function S(n,a){if(!(this instanceof S))throw new TypeError('Please use the "new" operator, this DOM object constructor cannot be called as a function.');a=a||{};var u=a.body;if(n instanceof S){if(n.bodyUsed)throw new TypeError("Already read");this.url=n.url,this.credentials=n.credentials,a.headers||(this.headers=new h(n.headers)),this.method=n.method,this.mode=n.mode,this.signal=n.signal,!u&&n._bodyInit!=null&&(u=n._bodyInit,n.bodyUsed=!0)}else this.url=String(n);if(this.credentials=a.credentials||this.credentials||"same-origin",(a.headers||!this.headers)&&(this.headers=new h(a.headers)),this.method=j(a.method||this.method||"GET"),this.mode=a.mode||this.mode||null,this.signal=a.signal||this.signal||(function(){if("AbortController"in i){var f=new AbortController;return f.signal}})(),this.referrer=null,(this.method==="GET"||this.method==="HEAD")&&u)throw new TypeError("Body not allowed for GET or HEAD requests");if(this._initBody(u),(this.method==="GET"||this.method==="HEAD")&&(a.cache==="no-store"||a.cache==="no-cache")){var p=/([?&])_=[^&]*/;if(p.test(this.url))this.url=this.url.replace(p,"$1_="+new Date().getTime());else{var y=/\?/;this.url+=(y.test(this.url)?"&":"?")+"_="+new Date().getTime()}}}S.prototype.clone=function(){return new S(this,{body:this._bodyInit})};function ee(n){var a=new FormData;return n.trim().split("&").forEach(function(u){if(u){var p=u.split("="),y=p.shift().replace(/\+/g," "),f=p.join("=").replace(/\+/g," ");a.append(decodeURIComponent(y),decodeURIComponent(f))}}),a}function te(n){var a=new h,u=n.replace(/\r?\n[\t ]+/g," ");return u.split("\r").map(function(p){return p.indexOf(`
|
|
2
|
+
`)===0?p.substr(1,p.length):p}).forEach(function(p){var y=p.split(":"),f=y.shift().trim();if(f){var _=y.join(":").trim();try{a.append(f,_)}catch(P){console.warn("Response "+P.message)}}}),a}q.call(S.prototype);function w(n,a){if(!(this instanceof w))throw new TypeError('Please use the "new" operator, this DOM object constructor cannot be called as a function.');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 h(a.headers),this.url=a.url||"",this._initBody(n)}q.call(w.prototype),w.prototype.clone=function(){return new w(this._bodyInit,{status:this.status,statusText:this.statusText,headers:new h(this.headers),url:this.url})},w.error=function(){var n=new w(null,{status:200,statusText:""});return n.ok=!1,n.status=0,n.type="error",n};var ne=[301,302,303,307,308];w.redirect=function(n,a){if(ne.indexOf(a)===-1)throw new RangeError("Invalid status code");return new w(null,{status:a,headers:{location:n}})},t.DOMException=i.DOMException;try{new t.DOMException}catch{t.DOMException=function(a,u){this.message=a,this.name=u;var p=Error(a);this.stack=p.stack},t.DOMException.prototype=Object.create(Error.prototype),t.DOMException.prototype.constructor=t.DOMException}function A(n,a){return new Promise(function(u,p){var y=new S(n,a);if(y.signal&&y.signal.aborted)return p(new t.DOMException("Aborted","AbortError"));var f=new XMLHttpRequest;function _(){f.abort()}f.onload=function(){var v={statusText:f.statusText,headers:te(f.getAllResponseHeaders()||"")};y.url.indexOf("file://")===0&&(f.status<200||f.status>599)?v.status=200:v.status=f.status,v.url="responseURL"in f?f.responseURL:v.headers.get("X-Request-URL");var x="response"in f?f.response:f.responseText;setTimeout(function(){u(new w(x,v))},0)},f.onerror=function(){setTimeout(function(){p(new TypeError("Network request failed"))},0)},f.ontimeout=function(){setTimeout(function(){p(new TypeError("Network request timed out"))},0)},f.onabort=function(){setTimeout(function(){p(new t.DOMException("Aborted","AbortError"))},0)};function P(v){try{return v===""&&i.location.href?i.location.href:v}catch{return v}}if(f.open(y.method,P(y.url),!0),y.credentials==="include"?f.withCredentials=!0:y.credentials==="omit"&&(f.withCredentials=!1),"responseType"in f&&(s.blob?f.responseType="blob":s.arrayBuffer&&(f.responseType="arraybuffer")),a&&typeof a.headers=="object"&&!(a.headers instanceof h||i.Headers&&a.headers instanceof i.Headers)){var W=[];Object.getOwnPropertyNames(a.headers).forEach(function(v){W.push(d(v)),f.setRequestHeader(v,g(a.headers[v]))}),y.headers.forEach(function(v,x){W.indexOf(x)===-1&&f.setRequestHeader(x,v)})}else y.headers.forEach(function(v,x){f.setRequestHeader(x,v)});y.signal&&(y.signal.addEventListener("abort",_),f.onreadystatechange=function(){f.readyState===4&&y.signal.removeEventListener("abort",_)}),f.send(typeof y._bodyInit>"u"?null:y._bodyInit)})}return A.polyfill=!0,i.fetch||(i.fetch=A,i.Headers=h,i.Request=S,i.Response=w),t.Headers=h,t.Request=S,t.Response=w,t.fetch=A,t})({})})(typeof self<"u"?self:z)});var k=[];for(let r=0;r<256;++r)k.push((r+256).toString(16).slice(1));function H(r,e=0){return(k[r[e+0]]+k[r[e+1]]+k[r[e+2]]+k[r[e+3]]+"-"+k[r[e+4]]+k[r[e+5]]+"-"+k[r[e+6]]+k[r[e+7]]+"-"+k[r[e+8]]+k[r[e+9]]+"-"+k[r[e+10]]+k[r[e+11]]+k[r[e+12]]+k[r[e+13]]+k[r[e+14]]+k[r[e+15]]).toLowerCase()}var L,de=new Uint8Array(16);function B(){if(!L){if(typeof crypto>"u"||!crypto.getRandomValues)throw new Error("crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported");L=crypto.getRandomValues.bind(crypto)}return L(de)}var fe=typeof crypto<"u"&&crypto.randomUUID&&crypto.randomUUID.bind(crypto),N={randomUUID:fe};function he(r,e,t){r=r||{};let i=r.random??r.rng?.()??B();if(i.length<16)throw new Error("Random bytes length must be >= 16");if(i[6]=i[6]&15|64,i[8]=i[8]&63|128,e){if(t=t||0,t<0||t+16>e.length)throw new RangeError(`UUID byte range ${t}:${t+15} is out of buffer bounds`);for(let s=0;s<16;++s)e[t+s]=i[s];return e}return H(i)}function ge(r,e,t){return N.randomUUID&&!e&&!r?N.randomUUID():he(r,e,t)}var C=ge;var st=ue(K());function T(r){return pe(r,!1)}function pe(r,e){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,featureUsageEvent:r.feature_usage_event==null?void 0:r.feature_usage_event,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 M(r){return ye(r,!1)}function ye(r,e=!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 U(r){return be(r,!1)}function be(r,e){return r==null?r:{data:T(r.data),params:r.params}}function X(r){return ve(r,!1)}function ve(r,e){return r==null?r:{flags:r.flags.map(T)}}function V(r){return ke(r,!1)}function ke(r,e){return r==null?r:{data:X(r.data),params:r.params}}var D=r=>{let{companyId:e,error:t,featureAllocation:i,featureUsage:s,featureUsageEvent:l,featureUsagePeriod:o,featureUsageResetAt:c,flag:d,flagId:g,reason:E,ruleId:h,ruleType:b,userId:m,value:F}=T(r);return{featureUsageExceeded:!F&&(b=="company_override_usage_exceeded"||b=="plan_entitlement_usage_exceeded"),companyId:e??void 0,error:t??void 0,featureAllocation:i??void 0,featureUsage:s??void 0,featureUsageEvent:l===null?void 0:l,featureUsagePeriod:o??void 0,featureUsageResetAt:c??void 0,flag:d,flagId:g??void 0,reason:E,ruleId:h??void 0,ruleType:b??void 0,userId:m??void 0,value:F}};function R(r){let e=Object.keys(r).reduce((t,i)=>{let l=Object.keys(r[i]||{}).sort().reduce((o,c)=>(o[c]=r[i][c],o),{});return t[i]=l,t},{});return JSON.stringify(e)}var O="1.2.9";var G="schematicId";var I=class{additionalHeaders={};apiKey;apiUrl="https://api.schematichq.com";conn=null;context={};debugEnabled=!1;offlineEnabled=!1;eventQueue;contextDependentEventQueue;eventUrl="https://c.schematichq.com";flagCheckListeners={};flagValueListeners={};isPending=!0;isPendingListeners=new Set;storage;useWebSocket=!1;checks={};featureUsageEventMap={};webSocketUrl="wss://api.schematichq.com";webSocketConnectionTimeout=1e4;webSocketReconnect=!0;webSocketMaxReconnectAttempts=7;webSocketInitialRetryDelay=1e3;webSocketMaxRetryDelay=3e4;wsReconnectAttempts=0;wsReconnectTimer=null;wsIntentionalDisconnect=!1;maxEventQueueSize=100;maxEventRetries=5;eventRetryInitialDelay=1e3;eventRetryMaxDelay=3e4;retryTimer=null;flagValueDefaults={};flagCheckDefaults={};constructor(e,t){if(this.apiKey=e,this.eventQueue=[],this.contextDependentEventQueue=[],this.useWebSocket=t?.useWebSocket??!1,this.debugEnabled=t?.debug??!1,this.offlineEnabled=t?.offline??!1,typeof window<"u"&&typeof window.location<"u"){let i=new URLSearchParams(window.location.search),s=i.get("schematic_debug");s!==null&&(s===""||s==="true"||s==="1")&&(this.debugEnabled=!0);let l=i.get("schematic_offline");l!==null&&(l===""||l==="true"||l==="1")&&(this.offlineEnabled=!0,this.debugEnabled=!0)}this.offlineEnabled&&t?.debug!==!1&&(this.debugEnabled=!0),this.offlineEnabled&&this.setIsPending(!1),this.additionalHeaders={"X-Schematic-Client-Version":`schematic-js@${O}`,...t?.additionalHeaders??{}},t?.storage?this.storage=t.storage:typeof localStorage<"u"&&(this.storage=localStorage),t?.apiUrl!==void 0&&(this.apiUrl=t.apiUrl),t?.eventUrl!==void 0&&(this.eventUrl=t.eventUrl),t?.webSocketUrl!==void 0&&(this.webSocketUrl=t.webSocketUrl),t?.webSocketConnectionTimeout!==void 0&&(this.webSocketConnectionTimeout=t.webSocketConnectionTimeout),t?.webSocketReconnect!==void 0&&(this.webSocketReconnect=t.webSocketReconnect),t?.webSocketMaxReconnectAttempts!==void 0&&(this.webSocketMaxReconnectAttempts=t.webSocketMaxReconnectAttempts),t?.webSocketInitialRetryDelay!==void 0&&(this.webSocketInitialRetryDelay=t.webSocketInitialRetryDelay),t?.webSocketMaxRetryDelay!==void 0&&(this.webSocketMaxRetryDelay=t.webSocketMaxRetryDelay),t?.maxEventQueueSize!==void 0&&(this.maxEventQueueSize=t.maxEventQueueSize),t?.maxEventRetries!==void 0&&(this.maxEventRetries=t.maxEventRetries),t?.eventRetryInitialDelay!==void 0&&(this.eventRetryInitialDelay=t.eventRetryInitialDelay),t?.eventRetryMaxDelay!==void 0&&(this.eventRetryMaxDelay=t.eventRetryMaxDelay),t?.flagValueDefaults!==void 0&&(this.flagValueDefaults=t.flagValueDefaults),t?.flagCheckDefaults!==void 0&&(this.flagCheckDefaults=t.flagCheckDefaults),typeof window<"u"&&window?.addEventListener&&(window.addEventListener("beforeunload",()=>{this.flushEventQueue(),this.flushContextDependentEventQueue()}),this.useWebSocket&&(window.addEventListener("offline",()=>{this.debug("Browser went offline, closing WebSocket connection"),this.handleNetworkOffline()}),window.addEventListener("online",()=>{this.debug("Browser came online, attempting to reconnect WebSocket"),this.handleNetworkOnline()}))),this.offlineEnabled?this.debug("Initialized with offline mode enabled - no network requests will be made"):this.debugEnabled&&this.debug("Initialized with debug mode enabled")}resolveFallbackValue(e,t){return t!==void 0?t:e in this.flagValueDefaults?this.flagValueDefaults[e]:!1}resolveFallbackCheckFlagReturn(e,t,i="Fallback value used",s){if(t!==void 0)return{flag:e,value:t,reason:i,error:s};if(e in this.flagCheckDefaults){let l=this.flagCheckDefaults[e];return{...l,flag:e,reason:s!==void 0?i:l.reason,error:s}}return e in this.flagValueDefaults?{flag:e,value:this.flagValueDefaults[e],reason:i,error:s}:{flag:e,value:!1,reason:i,error:s}}async checkFlag(e){let{fallback:t,key:i}=e,s=e.context||this.context,l=R(s);if(this.debug(`checkFlag: ${i}`,{context:s,fallback:t}),this.isOffline()){let o=this.resolveFallbackCheckFlagReturn(i,t,"Offline mode - using initialization defaults");return this.debug(`checkFlag offline result: ${i}`,{value:o.value,offlineMode:!0}),o.value}if(!this.useWebSocket){let o=`${this.apiUrl}/flags/${i}/check`;return fetch(o,{method:"POST",headers:{...this.additionalHeaders??{},"Content-Type":"application/json;charset=UTF-8","X-Schematic-Api-Key":this.apiKey},body:JSON.stringify(s)}).then(c=>{if(!c.ok)throw new Error("Network response was not ok");return c.json()}).then(c=>{let d=U(c);this.debug(`checkFlag result: ${i}`,d);let g=D(d.data);return typeof g.featureUsageEvent=="string"&&this.updateFeatureUsageEventMap(g),this.submitFlagCheckEvent(i,g,s),g.value}).catch(c=>{console.error("There was a problem with the fetch operation:",c);let d=this.resolveFallbackCheckFlagReturn(i,t,"API request failed",c instanceof Error?c.message:String(c));return this.submitFlagCheckEvent(i,d,s),d.value})}try{let o=this.checks[l];if(this.conn!==null&&typeof o<"u"&&typeof o[i]<"u")return this.debug(`checkFlag cached result: ${i}`,o[i]),o[i].value;if(this.isOffline())return this.resolveFallbackValue(i,t);try{await this.setContext(s)}catch(E){return console.error("WebSocket connection failed, falling back to REST:",E),this.fallbackToRest(i,s,t)}let d=(this.checks[l]??{})[i],g=d?.value??this.resolveFallbackValue(i,t);return this.debug(`checkFlag WebSocket result: ${i}`,typeof d<"u"?d:{value:g,fallbackUsed:!0}),typeof d<"u"&&this.submitFlagCheckEvent(i,d,s),g}catch(o){console.error("Unexpected error in checkFlag:",o);let c=this.resolveFallbackCheckFlagReturn(i,t,"Unexpected error in flag check",o instanceof Error?o.message:String(o));return this.submitFlagCheckEvent(i,c,s),c.value}}debug(e,...t){this.debugEnabled&&console.log(`[Schematic] ${e}`,...t)}isOffline(){return this.offlineEnabled}submitFlagCheckEvent(e,t,i){let s={flagKey:e,value:t.value,reason:t.reason,flagId:t.flagId,ruleId:t.ruleId,companyId:t.companyId,userId:t.userId,error:t.error,reqCompany:i.company,reqUser:i.user};return this.debug("submitting flag check event:",s),this.handleEvent("flag_check",M(s))}async fallbackToRest(e,t,i){if(this.isOffline()){let s=this.resolveFallbackValue(e,i);return this.debug(`fallbackToRest offline result: ${e}`,{value:s,offlineMode:!0}),s}try{let s=`${this.apiUrl}/flags/${e}/check`,l=await fetch(s,{method:"POST",headers:{...this.additionalHeaders??{},"Content-Type":"application/json;charset=UTF-8","X-Schematic-Api-Key":this.apiKey},body:JSON.stringify(t)});if(!l.ok)throw new Error("Network response was not ok");let o=await l.json(),c=U(o);this.debug(`fallbackToRest result: ${e}`,c);let d=D(c.data);return typeof d.featureUsageEvent=="string"&&this.updateFeatureUsageEventMap(d),this.submitFlagCheckEvent(e,d,t),d.value}catch(s){console.error("REST API call failed, using fallback value:",s);let l=this.resolveFallbackCheckFlagReturn(e,i,"API request failed (fallback)",s instanceof Error?s.message:String(s));return this.submitFlagCheckEvent(e,l,t),l.value}}checkFlags=async e=>{if(e=e||this.context,this.debug("checkFlags",{context:e}),this.isOffline())return this.debug("checkFlags offline result: returning empty object"),{};let t=`${this.apiUrl}/flags/check`,i=JSON.stringify(e);return fetch(t,{method:"POST",headers:{...this.additionalHeaders??{},"Content-Type":"application/json;charset=UTF-8","X-Schematic-Api-Key":this.apiKey},body:i}).then(s=>{if(!s.ok)throw new Error("Network response was not ok");return s.json()}).then(s=>{let l=V(s);return this.debug("checkFlags result:",l),(l?.data?.flags??[]).reduce((o,c)=>(o[c.flag]=c.value,o),{})}).catch(s=>(console.error("There was a problem with the fetch operation:",s),{}))};identify=e=>{this.debug("identify:",e);try{this.setContext({company:e.company?.keys,user:e.keys})}catch(t){console.error("Error setting context:",t)}return this.handleEvent("identify",e)};setContext=async e=>{if(this.isOffline()||!this.useWebSocket)return this.context=e,this.flushContextDependentEventQueue(),this.setIsPending(!1),Promise.resolve();try{this.setIsPending(!0),this.conn||(this.wsReconnectTimer!==null&&(this.debug("Cancelling scheduled reconnection, connecting immediately"),clearTimeout(this.wsReconnectTimer),this.wsReconnectTimer=null),this.conn=this.wsConnect());let t=await this.conn;await this.wsSendMessage(t,e)}catch(t){throw console.error("Failed to establish WebSocket connection:",t),t}};track=e=>{let{company:t,user:i,event:s,traits:l,quantity:o=1}=e;if(!this.hasContext(t,i)){this.debug(`track: queuing event "${s}" until context is available`);let d={api_key:this.apiKey,body:{company:t,event:s,traits:l??{},user:i,quantity:o},sent_at:new Date().toISOString(),tracker_event_id:C(),tracker_user_id:this.getAnonymousId(),type:"track"};return this.contextDependentEventQueue.push(d),Promise.resolve()}let c={company:t??this.context.company,event:s,traits:l??{},user:i??this.context.user,quantity:o};return this.debug("track:",c),s in this.featureUsageEventMap&&this.optimisticallyUpdateFeatureUsage(s,o),this.handleEvent("track",c)};optimisticallyUpdateFeatureUsage=(e,t=1)=>{let i=this.featureUsageEventMap[e];i!=null&&(this.debug(`Optimistically updating feature usage for event: ${e}`,{quantity:t}),Object.entries(i).forEach(([s,l])=>{if(l===void 0)return;let o={...l};if(typeof o.featureUsage=="number"){if(o.featureUsage+=t,typeof o.featureAllocation=="number"){let d=o.featureUsageExceeded===!0,g=o.featureUsage>=o.featureAllocation;g!==d&&(o.featureUsageExceeded=g,g&&(o.value=!1),this.debug(`Usage limit status changed for flag: ${s}`,{was:d?"exceeded":"within limits",now:g?"exceeded":"within limits",featureUsage:o.featureUsage,featureAllocation:o.featureAllocation,value:o.value}))}this.featureUsageEventMap[e]!==void 0&&(this.featureUsageEventMap[e][s]=o);let c=R(this.context);this.checks[c]!==void 0&&this.checks[c]!==null&&(this.checks[c][s]=o),this.notifyFlagCheckListeners(s,o),this.notifyFlagValueListeners(s,o.value)}}))};hasContext=(e,t)=>{let i=e!=null&&Object.keys(e).length>0||t!=null&&Object.keys(t).length>0,s=this.context.company!==void 0&&this.context.company!==null&&Object.keys(this.context.company).length>0||this.context.user!==void 0&&this.context.user!==null&&Object.keys(this.context.user).length>0;return i||s};flushContextDependentEventQueue=()=>{for(this.debug(`flushing ${this.contextDependentEventQueue.length} context-dependent events`);this.contextDependentEventQueue.length>0;){let e=this.contextDependentEventQueue.shift();if(e)if(e.type==="track"&&typeof e.body=="object"&&e.body!==null){let t=e.body,i={...t,company:t.company??this.context.company,user:t.user??this.context.user},s={...e,body:i,sent_at:new Date().toISOString()};this.sendEvent(s)}else this.sendEvent(e)}};startRetryTimer=()=>{this.retryTimer===null&&(this.retryTimer=setInterval(()=>{this.flushEventQueue().catch(e=>{this.debug("Error in retry timer flush:",e)}),this.eventQueue.length===0&&this.stopRetryTimer()},5e3),this.debug("Started retry timer"))};stopRetryTimer=()=>{this.retryTimer!==null&&(clearInterval(this.retryTimer),this.retryTimer=null,this.debug("Stopped retry timer"))};flushEventQueue=async()=>{if(this.eventQueue.length===0)return;let e=Date.now(),t=[],i=[];for(let s of this.eventQueue)s.next_retry_at===void 0||s.next_retry_at<=e?t.push(s):i.push(s);if(t.length===0){this.debug(`No events ready for retry yet (${i.length} still in backoff)`);return}this.debug(`Flushing event queue: ${t.length} ready, ${i.length} waiting`),this.eventQueue=i;for(let s of t)try{await this.sendEvent(s),this.debug("Queued event sent successfully:",s.type)}catch(l){this.debug("Failed to send queued event:",l)}};getAnonymousId=()=>{if(!this.storage)return C();let e=this.storage.getItem(G);if(typeof e<"u")return e;let t=C();return this.storage.setItem(G,t),t};handleEvent=(e,t)=>{let i={api_key:this.apiKey,body:t,sent_at:new Date().toISOString(),tracker_event_id:C(),tracker_user_id:this.getAnonymousId(),type:e};return typeof document<"u"&&document?.hidden?this.storeEvent(i):this.sendEvent(i)};sendEvent=async e=>{let t=`${this.eventUrl}/e`,i=JSON.stringify(e);if(this.debug("sending event:",{url:t,event:e}),this.isOffline())return this.debug("event not sent (offline mode):",{event:e}),Promise.resolve();try{let s=await fetch(t,{method:"POST",headers:{...this.additionalHeaders??{},"Content-Type":"application/json;charset=UTF-8"},body:i});if(!s.ok)throw new Error(`HTTP ${s.status}: ${s.statusText}`);this.debug("event sent:",{status:s.status,statusText:s.statusText})}catch(s){let l=(e.retry_count??0)+1;if(l<=this.maxEventRetries){this.debug(`Event failed to send (attempt ${l}/${this.maxEventRetries}), queueing for retry:`,s);let o=this.eventRetryInitialDelay*Math.pow(2,l-1),c=Math.min(o,this.eventRetryMaxDelay),d=Date.now()+c,g={...e,retry_count:l,next_retry_at:d};this.eventQueue.length<this.maxEventQueueSize?(this.eventQueue.push(g),this.debug(`Event queued for retry in ${c}ms (${this.eventQueue.length}/${this.maxEventQueueSize})`)):(this.debug(`Event queue full (${this.maxEventQueueSize}), dropping oldest event`),this.eventQueue.shift(),this.eventQueue.push(g)),this.startRetryTimer()}else this.debug(`Event failed permanently after ${this.maxEventRetries} attempts, dropping:`,s)}return Promise.resolve()};storeEvent=e=>(this.eventQueue.push(e),Promise.resolve());cleanup=async()=>{if(this.isOffline())return this.debug("cleanup: skipped (offline mode)"),Promise.resolve();if(this.wsIntentionalDisconnect=!0,this.wsReconnectTimer!==null&&(clearTimeout(this.wsReconnectTimer),this.wsReconnectTimer=null),this.stopRetryTimer(),this.conn)try{(await this.conn).close()}catch(e){console.error("Error during cleanup:",e)}finally{this.conn=null}};calculateReconnectDelay=()=>{let e=this.webSocketInitialRetryDelay*Math.pow(2,this.wsReconnectAttempts),t=Math.min(e,this.webSocketMaxRetryDelay),i=Math.random()*t*.5,s=t+i;return this.debug(`Reconnect delay calculated: ${s.toFixed(0)}ms (attempt ${this.wsReconnectAttempts+1}/${this.webSocketMaxReconnectAttempts})`),s};handleNetworkOffline=async()=>{if(this.conn!==null){try{(await this.conn).close()}catch(e){this.debug("Error closing connection on offline:",e)}this.conn=null}this.wsReconnectTimer!==null&&(clearTimeout(this.wsReconnectTimer),this.wsReconnectTimer=null)};handleNetworkOnline=()=>{this.debug("Network online, attempting reconnection and flushing queued events"),this.wsReconnectAttempts=0,this.wsReconnectTimer!==null&&(clearTimeout(this.wsReconnectTimer),this.wsReconnectTimer=null),this.flushEventQueue().catch(e=>{this.debug("Error flushing event queue on network online:",e)}),this.attemptReconnect()};attemptReconnect=()=>{if(this.wsReconnectAttempts>=this.webSocketMaxReconnectAttempts){this.debug(`Maximum reconnection attempts (${this.webSocketMaxReconnectAttempts}) reached, giving up`);return}this.wsReconnectTimer!==null&&clearTimeout(this.wsReconnectTimer);let e=this.calculateReconnectDelay();this.debug(`Scheduling reconnection attempt ${this.wsReconnectAttempts+1}/${this.webSocketMaxReconnectAttempts} in ${e.toFixed(0)}ms`),this.wsReconnectTimer=setTimeout(async()=>{this.wsReconnectTimer=null,this.wsReconnectAttempts++,this.debug(`Attempting to reconnect (attempt ${this.wsReconnectAttempts}/${this.webSocketMaxReconnectAttempts})`);try{this.conn=this.wsConnect();let t=await this.conn;this.debug("Reconnection context check:",{hasCompany:this.context.company!==void 0,hasUser:this.context.user!==void 0,context:this.context}),this.context.company!==void 0||this.context.user!==void 0?(this.debug("Reconnected, force re-sending context"),await this.wsSendContextAfterReconnection(t,this.context)):this.debug("No context to re-send after reconnection - websocket ready for new context"),this.flushEventQueue().catch(i=>{this.debug("Error flushing event queue after websocket reconnection:",i)}),this.debug("Reconnection successful")}catch(t){this.debug("Reconnection attempt failed:",t)}},e)};wsConnect=()=>this.isOffline()?(this.debug("wsConnect: skipped (offline mode)"),Promise.reject(new Error("WebSocket connection skipped in offline mode"))):new Promise((e,t)=>{let i=`${this.webSocketUrl}/flags/bootstrap?apiKey=${this.apiKey}`;this.debug("connecting to WebSocket:",i);let s=new WebSocket(i),l=null,o=!1;l=setTimeout(()=>{o||(this.debug(`WebSocket connection timeout after ${this.webSocketConnectionTimeout}ms`),s.close(),t(new Error("WebSocket connection timeout")))},this.webSocketConnectionTimeout),s.onopen=()=>{o=!0,l!==null&&clearTimeout(l),this.wsReconnectAttempts=0,this.wsIntentionalDisconnect=!1,this.debug("WebSocket connection opened"),e(s)},s.onerror=c=>{o=!0,l!==null&&clearTimeout(l),this.debug("WebSocket connection error:",c),t(c)},s.onclose=()=>{o=!0,l!==null&&clearTimeout(l),this.debug("WebSocket connection closed"),this.conn=null,!this.wsIntentionalDisconnect&&this.webSocketReconnect&&this.attemptReconnect()}});wsSendContextAfterReconnection=(e,t)=>this.isOffline()?(this.debug("wsSendContextAfterReconnection: skipped (offline mode)"),this.setIsPending(!1),Promise.resolve()):new Promise(i=>{this.debug("WebSocket force sending context after reconnection:",t),this.context=t;let s=()=>{let l=!1,o=g=>{let E=JSON.parse(g.data);this.debug("WebSocket message received after reconnection:",E),R(t)in this.checks||(this.checks[R(t)]={}),(E.flags??[]).forEach(h=>{let b=D(h),m=R(t);this.checks[m]===void 0&&(this.checks[m]={}),this.checks[m][b.flag]=b}),this.useWebSocket=!0,e.removeEventListener("message",o),l||(l=!0,i(this.setIsPending(!1)))};e.addEventListener("message",o);let c=this.additionalHeaders["X-Schematic-Client-Version"]??`schematic-js@${O}`,d={apiKey:this.apiKey,clientVersion:c,data:t};this.debug("WebSocket sending forced message after reconnection:",d),e.send(JSON.stringify(d))};e.readyState===WebSocket.OPEN?(this.debug("WebSocket already open, sending forced message after reconnection"),s()):e.addEventListener("open",()=>{this.debug("WebSocket opened, sending forced message after reconnection"),s()})});wsSendMessage=(e,t)=>this.isOffline()?(this.debug("wsSendMessage: skipped (offline mode)"),this.setIsPending(!1),Promise.resolve()):new Promise((i,s)=>{if(R(t)==R(this.context))return this.debug("WebSocket context unchanged, skipping update"),i(this.setIsPending(!1));this.debug("WebSocket context updated:",t),this.context=t;let l=()=>{let o=!1,c=E=>{let h=JSON.parse(E.data);this.debug("WebSocket message received:",h),R(t)in this.checks||(this.checks[R(t)]={}),(h.flags??[]).forEach(b=>{let m=D(b),F=R(t);this.checks[F]===void 0&&(this.checks[F]={}),this.checks[F][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[b.flag]?.size>0||this.flagValueListeners[b.flag]?.size>0)&&this.submitFlagCheckEvent(m.flag,m,t),this.notifyFlagCheckListeners(b.flag,m),this.notifyFlagValueListeners(b.flag,m.value)}),this.flushContextDependentEventQueue(),this.setIsPending(!1),o||(o=!0,i())};e.addEventListener("message",c);let d=this.additionalHeaders["X-Schematic-Client-Version"]??`schematic-js@${O}`,g={apiKey:this.apiKey,clientVersion:d,data:t};this.debug("WebSocket sending message:",g),e.send(JSON.stringify(g))};e.readyState===WebSocket.OPEN?(this.debug("WebSocket already open, sending message"),l()):e.readyState===WebSocket.CONNECTING?(this.debug("WebSocket connecting, waiting for open to send message"),e.addEventListener("open",l)):(this.debug("WebSocket is closed, cannot send message"),s("WebSocket is not open or connecting"))});getIsPending=()=>this.isPending;addIsPendingListener=e=>(this.isPendingListeners.add(e),()=>{this.isPendingListeners.delete(e)});setIsPending=e=>{this.isPending=e,this.isPendingListeners.forEach(t=>Ee(t,e))};getFlagCheck=e=>{let t=R(this.context);return(this.checks[t]??{})[e]};getFlagValue=e=>this.getFlagCheck(e)?.value;addFlagValueListener=(e,t)=>(e in this.flagValueListeners||(this.flagValueListeners[e]=new Set),this.flagValueListeners[e].add(t),()=>{this.flagValueListeners[e].delete(t)});addFlagCheckListener=(e,t)=>(e in this.flagCheckListeners||(this.flagCheckListeners[e]=new Set),this.flagCheckListeners[e].add(t),()=>{this.flagCheckListeners[e].delete(t)});notifyFlagCheckListeners=(e,t)=>{let i=this.flagCheckListeners?.[e]??[];i.size>0&&this.debug(`Notifying ${i.size} flag check listeners for ${e}`,t),typeof t.featureUsageEvent=="string"&&this.updateFeatureUsageEventMap(t),i.forEach(s=>Re(s,t))};updateFeatureUsageEventMap=e=>{if(typeof e.featureUsageEvent!="string")return;let t=e.featureUsageEvent;(this.featureUsageEventMap[t]===void 0||this.featureUsageEventMap[t]===null)&&(this.featureUsageEventMap[t]={}),this.featureUsageEventMap[t]!==void 0&&(this.featureUsageEventMap[t][e.flag]=e),this.debug(`Updated featureUsageEventMap for event: ${t}, flag: ${e.flag}`,e)};notifyFlagValueListeners=(e,t)=>{let i=this.flagValueListeners?.[e]??[];i.size>0&&this.debug(`Notifying ${i.size} flag value listeners for ${e}`,{value:t}),i.forEach(s=>we(s,t))}},Ee=(r,e)=>{r.length>0?r(e):r()},Re=(r,e)=>{r.length>0?r(e):r()},we=(r,e)=>{r.length>0?r(e):r()};window.Schematic=I;})();
|
|
3
3
|
/* @preserve */
|
package/dist/schematic.cjs.js
CHANGED
|
@@ -800,7 +800,7 @@ function contextString(context) {
|
|
|
800
800
|
}
|
|
801
801
|
|
|
802
802
|
// src/version.ts
|
|
803
|
-
var version = "1.2.
|
|
803
|
+
var version = "1.2.9";
|
|
804
804
|
|
|
805
805
|
// src/index.ts
|
|
806
806
|
var anonymousIdKey = "schematicId";
|
|
@@ -832,6 +832,17 @@ var Schematic = class {
|
|
|
832
832
|
wsReconnectAttempts = 0;
|
|
833
833
|
wsReconnectTimer = null;
|
|
834
834
|
wsIntentionalDisconnect = false;
|
|
835
|
+
maxEventQueueSize = 100;
|
|
836
|
+
// Prevent memory issues with very long network outages
|
|
837
|
+
maxEventRetries = 5;
|
|
838
|
+
// Maximum retry attempts for failed events
|
|
839
|
+
eventRetryInitialDelay = 1e3;
|
|
840
|
+
// Initial retry delay in ms
|
|
841
|
+
eventRetryMaxDelay = 3e4;
|
|
842
|
+
// Maximum retry delay in ms
|
|
843
|
+
retryTimer = null;
|
|
844
|
+
flagValueDefaults = {};
|
|
845
|
+
flagCheckDefaults = {};
|
|
835
846
|
constructor(apiKey, options) {
|
|
836
847
|
this.apiKey = apiKey;
|
|
837
848
|
this.eventQueue = [];
|
|
@@ -890,6 +901,24 @@ var Schematic = class {
|
|
|
890
901
|
if (options?.webSocketMaxRetryDelay !== void 0) {
|
|
891
902
|
this.webSocketMaxRetryDelay = options.webSocketMaxRetryDelay;
|
|
892
903
|
}
|
|
904
|
+
if (options?.maxEventQueueSize !== void 0) {
|
|
905
|
+
this.maxEventQueueSize = options.maxEventQueueSize;
|
|
906
|
+
}
|
|
907
|
+
if (options?.maxEventRetries !== void 0) {
|
|
908
|
+
this.maxEventRetries = options.maxEventRetries;
|
|
909
|
+
}
|
|
910
|
+
if (options?.eventRetryInitialDelay !== void 0) {
|
|
911
|
+
this.eventRetryInitialDelay = options.eventRetryInitialDelay;
|
|
912
|
+
}
|
|
913
|
+
if (options?.eventRetryMaxDelay !== void 0) {
|
|
914
|
+
this.eventRetryMaxDelay = options.eventRetryMaxDelay;
|
|
915
|
+
}
|
|
916
|
+
if (options?.flagValueDefaults !== void 0) {
|
|
917
|
+
this.flagValueDefaults = options.flagValueDefaults;
|
|
918
|
+
}
|
|
919
|
+
if (options?.flagCheckDefaults !== void 0) {
|
|
920
|
+
this.flagCheckDefaults = options.flagCheckDefaults;
|
|
921
|
+
}
|
|
893
922
|
if (typeof window !== "undefined" && window?.addEventListener) {
|
|
894
923
|
window.addEventListener("beforeunload", () => {
|
|
895
924
|
this.flushEventQueue();
|
|
@@ -914,6 +943,62 @@ var Schematic = class {
|
|
|
914
943
|
this.debug("Initialized with debug mode enabled");
|
|
915
944
|
}
|
|
916
945
|
}
|
|
946
|
+
/**
|
|
947
|
+
* Resolve fallback value according to priority order:
|
|
948
|
+
* 1. Callsite fallback value (if provided)
|
|
949
|
+
* 2. Initialization fallback value (flagValueDefaults)
|
|
950
|
+
* 3. Default to false
|
|
951
|
+
*/
|
|
952
|
+
resolveFallbackValue(key, callsiteFallback) {
|
|
953
|
+
if (callsiteFallback !== void 0) {
|
|
954
|
+
return callsiteFallback;
|
|
955
|
+
}
|
|
956
|
+
if (key in this.flagValueDefaults) {
|
|
957
|
+
return this.flagValueDefaults[key];
|
|
958
|
+
}
|
|
959
|
+
return false;
|
|
960
|
+
}
|
|
961
|
+
/**
|
|
962
|
+
* Resolve complete CheckFlagReturn object according to priority order:
|
|
963
|
+
* 1. Use callsite fallback for boolean value, construct CheckFlagReturn
|
|
964
|
+
* 2. Use flagCheckDefaults if available for this flag
|
|
965
|
+
* 3. Use flagValueDefaults if available for this flag, construct CheckFlagReturn
|
|
966
|
+
* 4. Default CheckFlagReturn with value: false
|
|
967
|
+
*/
|
|
968
|
+
resolveFallbackCheckFlagReturn(key, callsiteFallback, reason = "Fallback value used", error) {
|
|
969
|
+
if (callsiteFallback !== void 0) {
|
|
970
|
+
return {
|
|
971
|
+
flag: key,
|
|
972
|
+
value: callsiteFallback,
|
|
973
|
+
reason,
|
|
974
|
+
error
|
|
975
|
+
};
|
|
976
|
+
}
|
|
977
|
+
if (key in this.flagCheckDefaults) {
|
|
978
|
+
const defaultReturn = this.flagCheckDefaults[key];
|
|
979
|
+
return {
|
|
980
|
+
...defaultReturn,
|
|
981
|
+
flag: key,
|
|
982
|
+
// Ensure flag matches the requested key
|
|
983
|
+
reason: error !== void 0 ? reason : defaultReturn.reason,
|
|
984
|
+
error
|
|
985
|
+
};
|
|
986
|
+
}
|
|
987
|
+
if (key in this.flagValueDefaults) {
|
|
988
|
+
return {
|
|
989
|
+
flag: key,
|
|
990
|
+
value: this.flagValueDefaults[key],
|
|
991
|
+
reason,
|
|
992
|
+
error
|
|
993
|
+
};
|
|
994
|
+
}
|
|
995
|
+
return {
|
|
996
|
+
flag: key,
|
|
997
|
+
value: false,
|
|
998
|
+
reason,
|
|
999
|
+
error
|
|
1000
|
+
};
|
|
1001
|
+
}
|
|
917
1002
|
/**
|
|
918
1003
|
* Get value for a single flag.
|
|
919
1004
|
* In WebSocket mode, returns cached values if connection is active, otherwise establishes
|
|
@@ -922,16 +1007,21 @@ var Schematic = class {
|
|
|
922
1007
|
* In REST mode, makes an API call for each check.
|
|
923
1008
|
*/
|
|
924
1009
|
async checkFlag(options) {
|
|
925
|
-
const { fallback
|
|
1010
|
+
const { fallback, key } = options;
|
|
926
1011
|
const context = options.context || this.context;
|
|
927
1012
|
const contextStr = contextString(context);
|
|
928
1013
|
this.debug(`checkFlag: ${key}`, { context, fallback });
|
|
929
1014
|
if (this.isOffline()) {
|
|
1015
|
+
const resolvedFallbackResult = this.resolveFallbackCheckFlagReturn(
|
|
1016
|
+
key,
|
|
1017
|
+
fallback,
|
|
1018
|
+
"Offline mode - using initialization defaults"
|
|
1019
|
+
);
|
|
930
1020
|
this.debug(`checkFlag offline result: ${key}`, {
|
|
931
|
-
value:
|
|
1021
|
+
value: resolvedFallbackResult.value,
|
|
932
1022
|
offlineMode: true
|
|
933
1023
|
});
|
|
934
|
-
return
|
|
1024
|
+
return resolvedFallbackResult.value;
|
|
935
1025
|
}
|
|
936
1026
|
if (!this.useWebSocket) {
|
|
937
1027
|
const requestUrl = `${this.apiUrl}/flags/${key}/check`;
|
|
@@ -959,14 +1049,14 @@ var Schematic = class {
|
|
|
959
1049
|
return result.value;
|
|
960
1050
|
}).catch((error) => {
|
|
961
1051
|
console.error("There was a problem with the fetch operation:", error);
|
|
962
|
-
const errorResult =
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
error
|
|
967
|
-
|
|
1052
|
+
const errorResult = this.resolveFallbackCheckFlagReturn(
|
|
1053
|
+
key,
|
|
1054
|
+
fallback,
|
|
1055
|
+
"API request failed",
|
|
1056
|
+
error instanceof Error ? error.message : String(error)
|
|
1057
|
+
);
|
|
968
1058
|
this.submitFlagCheckEvent(key, errorResult, context);
|
|
969
|
-
return
|
|
1059
|
+
return errorResult.value;
|
|
970
1060
|
});
|
|
971
1061
|
}
|
|
972
1062
|
try {
|
|
@@ -976,7 +1066,7 @@ var Schematic = class {
|
|
|
976
1066
|
return existingVals[key].value;
|
|
977
1067
|
}
|
|
978
1068
|
if (this.isOffline()) {
|
|
979
|
-
return fallback;
|
|
1069
|
+
return this.resolveFallbackValue(key, fallback);
|
|
980
1070
|
}
|
|
981
1071
|
try {
|
|
982
1072
|
await this.setContext(context);
|
|
@@ -989,10 +1079,10 @@ var Schematic = class {
|
|
|
989
1079
|
}
|
|
990
1080
|
const contextVals = this.checks[contextStr] ?? {};
|
|
991
1081
|
const flagCheck = contextVals[key];
|
|
992
|
-
const result = flagCheck?.value ?? fallback;
|
|
1082
|
+
const result = flagCheck?.value ?? this.resolveFallbackValue(key, fallback);
|
|
993
1083
|
this.debug(
|
|
994
1084
|
`checkFlag WebSocket result: ${key}`,
|
|
995
|
-
typeof flagCheck !== "undefined" ? flagCheck : { value:
|
|
1085
|
+
typeof flagCheck !== "undefined" ? flagCheck : { value: result, fallbackUsed: true }
|
|
996
1086
|
);
|
|
997
1087
|
if (typeof flagCheck !== "undefined") {
|
|
998
1088
|
this.submitFlagCheckEvent(key, flagCheck, context);
|
|
@@ -1000,14 +1090,14 @@ var Schematic = class {
|
|
|
1000
1090
|
return result;
|
|
1001
1091
|
} catch (error) {
|
|
1002
1092
|
console.error("Unexpected error in checkFlag:", error);
|
|
1003
|
-
const errorResult =
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
error
|
|
1008
|
-
|
|
1093
|
+
const errorResult = this.resolveFallbackCheckFlagReturn(
|
|
1094
|
+
key,
|
|
1095
|
+
fallback,
|
|
1096
|
+
"Unexpected error in flag check",
|
|
1097
|
+
error instanceof Error ? error.message : String(error)
|
|
1098
|
+
);
|
|
1009
1099
|
this.submitFlagCheckEvent(key, errorResult, context);
|
|
1010
|
-
return
|
|
1100
|
+
return errorResult.value;
|
|
1011
1101
|
}
|
|
1012
1102
|
}
|
|
1013
1103
|
/**
|
|
@@ -1050,11 +1140,12 @@ var Schematic = class {
|
|
|
1050
1140
|
*/
|
|
1051
1141
|
async fallbackToRest(key, context, fallback) {
|
|
1052
1142
|
if (this.isOffline()) {
|
|
1143
|
+
const resolvedFallback = this.resolveFallbackValue(key, fallback);
|
|
1053
1144
|
this.debug(`fallbackToRest offline result: ${key}`, {
|
|
1054
|
-
value:
|
|
1145
|
+
value: resolvedFallback,
|
|
1055
1146
|
offlineMode: true
|
|
1056
1147
|
});
|
|
1057
|
-
return
|
|
1148
|
+
return resolvedFallback;
|
|
1058
1149
|
}
|
|
1059
1150
|
try {
|
|
1060
1151
|
const requestUrl = `${this.apiUrl}/flags/${key}/check`;
|
|
@@ -1081,14 +1172,14 @@ var Schematic = class {
|
|
|
1081
1172
|
return result.value;
|
|
1082
1173
|
} catch (error) {
|
|
1083
1174
|
console.error("REST API call failed, using fallback value:", error);
|
|
1084
|
-
const errorResult =
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
error
|
|
1089
|
-
|
|
1175
|
+
const errorResult = this.resolveFallbackCheckFlagReturn(
|
|
1176
|
+
key,
|
|
1177
|
+
fallback,
|
|
1178
|
+
"API request failed (fallback)",
|
|
1179
|
+
error instanceof Error ? error.message : String(error)
|
|
1180
|
+
);
|
|
1090
1181
|
this.submitFlagCheckEvent(key, errorResult, context);
|
|
1091
|
-
return
|
|
1182
|
+
return errorResult.value;
|
|
1092
1183
|
}
|
|
1093
1184
|
}
|
|
1094
1185
|
/**
|
|
@@ -1305,11 +1396,53 @@ var Schematic = class {
|
|
|
1305
1396
|
}
|
|
1306
1397
|
}
|
|
1307
1398
|
};
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1399
|
+
startRetryTimer = () => {
|
|
1400
|
+
if (this.retryTimer !== null) {
|
|
1401
|
+
return;
|
|
1402
|
+
}
|
|
1403
|
+
this.retryTimer = setInterval(() => {
|
|
1404
|
+
this.flushEventQueue().catch((error) => {
|
|
1405
|
+
this.debug("Error in retry timer flush:", error);
|
|
1406
|
+
});
|
|
1407
|
+
if (this.eventQueue.length === 0) {
|
|
1408
|
+
this.stopRetryTimer();
|
|
1409
|
+
}
|
|
1410
|
+
}, 5e3);
|
|
1411
|
+
this.debug("Started retry timer");
|
|
1412
|
+
};
|
|
1413
|
+
stopRetryTimer = () => {
|
|
1414
|
+
if (this.retryTimer !== null) {
|
|
1415
|
+
clearInterval(this.retryTimer);
|
|
1416
|
+
this.retryTimer = null;
|
|
1417
|
+
this.debug("Stopped retry timer");
|
|
1418
|
+
}
|
|
1419
|
+
};
|
|
1420
|
+
flushEventQueue = async () => {
|
|
1421
|
+
if (this.eventQueue.length === 0) {
|
|
1422
|
+
return;
|
|
1423
|
+
}
|
|
1424
|
+
const now = Date.now();
|
|
1425
|
+
const readyEvents = [];
|
|
1426
|
+
const notReadyEvents = [];
|
|
1427
|
+
for (const event of this.eventQueue) {
|
|
1428
|
+
if (event.next_retry_at === void 0 || event.next_retry_at <= now) {
|
|
1429
|
+
readyEvents.push(event);
|
|
1430
|
+
} else {
|
|
1431
|
+
notReadyEvents.push(event);
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
1434
|
+
if (readyEvents.length === 0) {
|
|
1435
|
+
this.debug(`No events ready for retry yet (${notReadyEvents.length} still in backoff)`);
|
|
1436
|
+
return;
|
|
1437
|
+
}
|
|
1438
|
+
this.debug(`Flushing event queue: ${readyEvents.length} ready, ${notReadyEvents.length} waiting`);
|
|
1439
|
+
this.eventQueue = notReadyEvents;
|
|
1440
|
+
for (const event of readyEvents) {
|
|
1441
|
+
try {
|
|
1442
|
+
await this.sendEvent(event);
|
|
1443
|
+
this.debug(`Queued event sent successfully:`, event.type);
|
|
1444
|
+
} catch (error) {
|
|
1445
|
+
this.debug(`Failed to send queued event:`, error);
|
|
1313
1446
|
}
|
|
1314
1447
|
}
|
|
1315
1448
|
};
|
|
@@ -1357,12 +1490,37 @@ var Schematic = class {
|
|
|
1357
1490
|
},
|
|
1358
1491
|
body: payload
|
|
1359
1492
|
});
|
|
1493
|
+
if (!response.ok) {
|
|
1494
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
1495
|
+
}
|
|
1360
1496
|
this.debug(`event sent:`, {
|
|
1361
1497
|
status: response.status,
|
|
1362
1498
|
statusText: response.statusText
|
|
1363
1499
|
});
|
|
1364
1500
|
} catch (error) {
|
|
1365
|
-
|
|
1501
|
+
const retryCount = (event.retry_count ?? 0) + 1;
|
|
1502
|
+
if (retryCount <= this.maxEventRetries) {
|
|
1503
|
+
this.debug(`Event failed to send (attempt ${retryCount}/${this.maxEventRetries}), queueing for retry:`, error);
|
|
1504
|
+
const baseDelay = this.eventRetryInitialDelay * Math.pow(2, retryCount - 1);
|
|
1505
|
+
const jitterDelay = Math.min(baseDelay, this.eventRetryMaxDelay);
|
|
1506
|
+
const nextRetryAt = Date.now() + jitterDelay;
|
|
1507
|
+
const retryEvent = {
|
|
1508
|
+
...event,
|
|
1509
|
+
retry_count: retryCount,
|
|
1510
|
+
next_retry_at: nextRetryAt
|
|
1511
|
+
};
|
|
1512
|
+
if (this.eventQueue.length < this.maxEventQueueSize) {
|
|
1513
|
+
this.eventQueue.push(retryEvent);
|
|
1514
|
+
this.debug(`Event queued for retry in ${jitterDelay}ms (${this.eventQueue.length}/${this.maxEventQueueSize})`);
|
|
1515
|
+
} else {
|
|
1516
|
+
this.debug(`Event queue full (${this.maxEventQueueSize}), dropping oldest event`);
|
|
1517
|
+
this.eventQueue.shift();
|
|
1518
|
+
this.eventQueue.push(retryEvent);
|
|
1519
|
+
}
|
|
1520
|
+
this.startRetryTimer();
|
|
1521
|
+
} else {
|
|
1522
|
+
this.debug(`Event failed permanently after ${this.maxEventRetries} attempts, dropping:`, error);
|
|
1523
|
+
}
|
|
1366
1524
|
}
|
|
1367
1525
|
return Promise.resolve();
|
|
1368
1526
|
};
|
|
@@ -1387,6 +1545,7 @@ var Schematic = class {
|
|
|
1387
1545
|
clearTimeout(this.wsReconnectTimer);
|
|
1388
1546
|
this.wsReconnectTimer = null;
|
|
1389
1547
|
}
|
|
1548
|
+
this.stopRetryTimer();
|
|
1390
1549
|
if (this.conn) {
|
|
1391
1550
|
try {
|
|
1392
1551
|
const socket = await this.conn;
|
|
@@ -1434,16 +1593,15 @@ var Schematic = class {
|
|
|
1434
1593
|
* Handle browser coming back online
|
|
1435
1594
|
*/
|
|
1436
1595
|
handleNetworkOnline = () => {
|
|
1437
|
-
|
|
1438
|
-
this.debug("No context set, skipping reconnection");
|
|
1439
|
-
return;
|
|
1440
|
-
}
|
|
1596
|
+
this.debug("Network online, attempting reconnection and flushing queued events");
|
|
1441
1597
|
this.wsReconnectAttempts = 0;
|
|
1442
1598
|
if (this.wsReconnectTimer !== null) {
|
|
1443
1599
|
clearTimeout(this.wsReconnectTimer);
|
|
1444
1600
|
this.wsReconnectTimer = null;
|
|
1445
1601
|
}
|
|
1446
|
-
this.
|
|
1602
|
+
this.flushEventQueue().catch((error) => {
|
|
1603
|
+
this.debug("Error flushing event queue on network online:", error);
|
|
1604
|
+
});
|
|
1447
1605
|
this.attemptReconnect();
|
|
1448
1606
|
};
|
|
1449
1607
|
/**
|
|
@@ -1473,10 +1631,20 @@ var Schematic = class {
|
|
|
1473
1631
|
try {
|
|
1474
1632
|
this.conn = this.wsConnect();
|
|
1475
1633
|
const socket = await this.conn;
|
|
1634
|
+
this.debug(`Reconnection context check:`, {
|
|
1635
|
+
hasCompany: this.context.company !== void 0,
|
|
1636
|
+
hasUser: this.context.user !== void 0,
|
|
1637
|
+
context: this.context
|
|
1638
|
+
});
|
|
1476
1639
|
if (this.context.company !== void 0 || this.context.user !== void 0) {
|
|
1477
|
-
this.debug(`Reconnected, re-sending context`);
|
|
1478
|
-
await this.
|
|
1640
|
+
this.debug(`Reconnected, force re-sending context`);
|
|
1641
|
+
await this.wsSendContextAfterReconnection(socket, this.context);
|
|
1642
|
+
} else {
|
|
1643
|
+
this.debug(`No context to re-send after reconnection - websocket ready for new context`);
|
|
1479
1644
|
}
|
|
1645
|
+
this.flushEventQueue().catch((error) => {
|
|
1646
|
+
this.debug("Error flushing event queue after websocket reconnection:", error);
|
|
1647
|
+
});
|
|
1480
1648
|
this.debug(`Reconnection successful`);
|
|
1481
1649
|
} catch (error) {
|
|
1482
1650
|
this.debug(`Reconnection attempt failed:`, error);
|
|
@@ -1537,6 +1705,61 @@ var Schematic = class {
|
|
|
1537
1705
|
};
|
|
1538
1706
|
});
|
|
1539
1707
|
};
|
|
1708
|
+
// Send a message on the websocket after reconnection, forcing the send even if context appears unchanged
|
|
1709
|
+
// because the server has lost all state and needs the initial context
|
|
1710
|
+
wsSendContextAfterReconnection = (socket, context) => {
|
|
1711
|
+
if (this.isOffline()) {
|
|
1712
|
+
this.debug("wsSendContextAfterReconnection: skipped (offline mode)");
|
|
1713
|
+
this.setIsPending(false);
|
|
1714
|
+
return Promise.resolve();
|
|
1715
|
+
}
|
|
1716
|
+
return new Promise((resolve) => {
|
|
1717
|
+
this.debug(`WebSocket force sending context after reconnection:`, context);
|
|
1718
|
+
this.context = context;
|
|
1719
|
+
const sendMessage = () => {
|
|
1720
|
+
let resolved = false;
|
|
1721
|
+
const messageHandler = (event) => {
|
|
1722
|
+
const message = JSON.parse(event.data);
|
|
1723
|
+
this.debug(`WebSocket message received after reconnection:`, message);
|
|
1724
|
+
if (!(contextString(context) in this.checks)) {
|
|
1725
|
+
this.checks[contextString(context)] = {};
|
|
1726
|
+
}
|
|
1727
|
+
(message.flags ?? []).forEach((flag) => {
|
|
1728
|
+
const flagCheck = CheckFlagReturnFromJSON(flag);
|
|
1729
|
+
const contextStr = contextString(context);
|
|
1730
|
+
if (this.checks[contextStr] === void 0) {
|
|
1731
|
+
this.checks[contextStr] = {};
|
|
1732
|
+
}
|
|
1733
|
+
this.checks[contextStr][flagCheck.flag] = flagCheck;
|
|
1734
|
+
});
|
|
1735
|
+
this.useWebSocket = true;
|
|
1736
|
+
socket.removeEventListener("message", messageHandler);
|
|
1737
|
+
if (!resolved) {
|
|
1738
|
+
resolved = true;
|
|
1739
|
+
resolve(this.setIsPending(false));
|
|
1740
|
+
}
|
|
1741
|
+
};
|
|
1742
|
+
socket.addEventListener("message", messageHandler);
|
|
1743
|
+
const clientVersion = this.additionalHeaders["X-Schematic-Client-Version"] ?? `schematic-js@${version}`;
|
|
1744
|
+
const messagePayload = {
|
|
1745
|
+
apiKey: this.apiKey,
|
|
1746
|
+
clientVersion,
|
|
1747
|
+
data: context
|
|
1748
|
+
};
|
|
1749
|
+
this.debug(`WebSocket sending forced message after reconnection:`, messagePayload);
|
|
1750
|
+
socket.send(JSON.stringify(messagePayload));
|
|
1751
|
+
};
|
|
1752
|
+
if (socket.readyState === WebSocket.OPEN) {
|
|
1753
|
+
this.debug(`WebSocket already open, sending forced message after reconnection`);
|
|
1754
|
+
sendMessage();
|
|
1755
|
+
} else {
|
|
1756
|
+
socket.addEventListener("open", () => {
|
|
1757
|
+
this.debug(`WebSocket opened, sending forced message after reconnection`);
|
|
1758
|
+
sendMessage();
|
|
1759
|
+
});
|
|
1760
|
+
}
|
|
1761
|
+
});
|
|
1762
|
+
};
|
|
1540
1763
|
// Send a message on the websocket indicating interest in a particular evaluation context
|
|
1541
1764
|
// and wait for the initial set of flag values to be returned
|
|
1542
1765
|
wsSendMessage = (socket, context) => {
|
package/dist/schematic.d.ts
CHANGED
|
@@ -213,6 +213,8 @@ declare type Event_2 = {
|
|
|
213
213
|
tracker_event_id: string;
|
|
214
214
|
tracker_user_id: string;
|
|
215
215
|
type: EventType;
|
|
216
|
+
retry_count?: number;
|
|
217
|
+
next_retry_at?: number;
|
|
216
218
|
};
|
|
217
219
|
export { Event_2 as Event }
|
|
218
220
|
|
|
@@ -376,7 +378,29 @@ export declare class Schematic {
|
|
|
376
378
|
private wsReconnectAttempts;
|
|
377
379
|
private wsReconnectTimer;
|
|
378
380
|
private wsIntentionalDisconnect;
|
|
381
|
+
private maxEventQueueSize;
|
|
382
|
+
private maxEventRetries;
|
|
383
|
+
private eventRetryInitialDelay;
|
|
384
|
+
private eventRetryMaxDelay;
|
|
385
|
+
private retryTimer;
|
|
386
|
+
private flagValueDefaults;
|
|
387
|
+
private flagCheckDefaults;
|
|
379
388
|
constructor(apiKey: string, options?: SchematicOptions);
|
|
389
|
+
/**
|
|
390
|
+
* Resolve fallback value according to priority order:
|
|
391
|
+
* 1. Callsite fallback value (if provided)
|
|
392
|
+
* 2. Initialization fallback value (flagValueDefaults)
|
|
393
|
+
* 3. Default to false
|
|
394
|
+
*/
|
|
395
|
+
private resolveFallbackValue;
|
|
396
|
+
/**
|
|
397
|
+
* Resolve complete CheckFlagReturn object according to priority order:
|
|
398
|
+
* 1. Use callsite fallback for boolean value, construct CheckFlagReturn
|
|
399
|
+
* 2. Use flagCheckDefaults if available for this flag
|
|
400
|
+
* 3. Use flagValueDefaults if available for this flag, construct CheckFlagReturn
|
|
401
|
+
* 4. Default CheckFlagReturn with value: false
|
|
402
|
+
*/
|
|
403
|
+
private resolveFallbackCheckFlagReturn;
|
|
380
404
|
/**
|
|
381
405
|
* Get value for a single flag.
|
|
382
406
|
* In WebSocket mode, returns cached values if connection is active, otherwise establishes
|
|
@@ -442,6 +466,8 @@ export declare class Schematic {
|
|
|
442
466
|
*/
|
|
443
467
|
private hasContext;
|
|
444
468
|
private flushContextDependentEventQueue;
|
|
469
|
+
private startRetryTimer;
|
|
470
|
+
private stopRetryTimer;
|
|
445
471
|
private flushEventQueue;
|
|
446
472
|
private getAnonymousId;
|
|
447
473
|
private handleEvent;
|
|
@@ -474,6 +500,7 @@ export declare class Schematic {
|
|
|
474
500
|
*/
|
|
475
501
|
private attemptReconnect;
|
|
476
502
|
private wsConnect;
|
|
503
|
+
private wsSendContextAfterReconnection;
|
|
477
504
|
private wsSendMessage;
|
|
478
505
|
/**
|
|
479
506
|
* State management
|
|
@@ -529,6 +556,18 @@ export declare type SchematicOptions = {
|
|
|
529
556
|
webSocketInitialRetryDelay?: number;
|
|
530
557
|
/** Maximum retry delay in milliseconds for exponential backoff (default: 30000) */
|
|
531
558
|
webSocketMaxRetryDelay?: number;
|
|
559
|
+
/** Maximum number of events to queue for retry when network is down (default: 100) */
|
|
560
|
+
maxEventQueueSize?: number;
|
|
561
|
+
/** Maximum number of retry attempts for failed events (default: 5) */
|
|
562
|
+
maxEventRetries?: number;
|
|
563
|
+
/** Initial retry delay in milliseconds for failed events (default: 1000) */
|
|
564
|
+
eventRetryInitialDelay?: number;
|
|
565
|
+
/** Maximum retry delay in milliseconds for failed events (default: 30000) */
|
|
566
|
+
eventRetryMaxDelay?: number;
|
|
567
|
+
/** Default boolean values for flags when Schematic API cannot be reached and no callsite fallback is provided */
|
|
568
|
+
flagValueDefaults?: Record<string, boolean>;
|
|
569
|
+
/** Default CheckFlagReturn objects for flags when Schematic API cannot be reached and no callsite fallback is provided */
|
|
570
|
+
flagCheckDefaults?: Record<string, CheckFlagReturn>;
|
|
532
571
|
};
|
|
533
572
|
|
|
534
573
|
/** Optional type for implementing custom client-side storage */
|
package/dist/schematic.esm.js
CHANGED
|
@@ -781,7 +781,7 @@ function contextString(context) {
|
|
|
781
781
|
}
|
|
782
782
|
|
|
783
783
|
// src/version.ts
|
|
784
|
-
var version = "1.2.
|
|
784
|
+
var version = "1.2.9";
|
|
785
785
|
|
|
786
786
|
// src/index.ts
|
|
787
787
|
var anonymousIdKey = "schematicId";
|
|
@@ -813,6 +813,17 @@ var Schematic = class {
|
|
|
813
813
|
wsReconnectAttempts = 0;
|
|
814
814
|
wsReconnectTimer = null;
|
|
815
815
|
wsIntentionalDisconnect = false;
|
|
816
|
+
maxEventQueueSize = 100;
|
|
817
|
+
// Prevent memory issues with very long network outages
|
|
818
|
+
maxEventRetries = 5;
|
|
819
|
+
// Maximum retry attempts for failed events
|
|
820
|
+
eventRetryInitialDelay = 1e3;
|
|
821
|
+
// Initial retry delay in ms
|
|
822
|
+
eventRetryMaxDelay = 3e4;
|
|
823
|
+
// Maximum retry delay in ms
|
|
824
|
+
retryTimer = null;
|
|
825
|
+
flagValueDefaults = {};
|
|
826
|
+
flagCheckDefaults = {};
|
|
816
827
|
constructor(apiKey, options) {
|
|
817
828
|
this.apiKey = apiKey;
|
|
818
829
|
this.eventQueue = [];
|
|
@@ -871,6 +882,24 @@ var Schematic = class {
|
|
|
871
882
|
if (options?.webSocketMaxRetryDelay !== void 0) {
|
|
872
883
|
this.webSocketMaxRetryDelay = options.webSocketMaxRetryDelay;
|
|
873
884
|
}
|
|
885
|
+
if (options?.maxEventQueueSize !== void 0) {
|
|
886
|
+
this.maxEventQueueSize = options.maxEventQueueSize;
|
|
887
|
+
}
|
|
888
|
+
if (options?.maxEventRetries !== void 0) {
|
|
889
|
+
this.maxEventRetries = options.maxEventRetries;
|
|
890
|
+
}
|
|
891
|
+
if (options?.eventRetryInitialDelay !== void 0) {
|
|
892
|
+
this.eventRetryInitialDelay = options.eventRetryInitialDelay;
|
|
893
|
+
}
|
|
894
|
+
if (options?.eventRetryMaxDelay !== void 0) {
|
|
895
|
+
this.eventRetryMaxDelay = options.eventRetryMaxDelay;
|
|
896
|
+
}
|
|
897
|
+
if (options?.flagValueDefaults !== void 0) {
|
|
898
|
+
this.flagValueDefaults = options.flagValueDefaults;
|
|
899
|
+
}
|
|
900
|
+
if (options?.flagCheckDefaults !== void 0) {
|
|
901
|
+
this.flagCheckDefaults = options.flagCheckDefaults;
|
|
902
|
+
}
|
|
874
903
|
if (typeof window !== "undefined" && window?.addEventListener) {
|
|
875
904
|
window.addEventListener("beforeunload", () => {
|
|
876
905
|
this.flushEventQueue();
|
|
@@ -895,6 +924,62 @@ var Schematic = class {
|
|
|
895
924
|
this.debug("Initialized with debug mode enabled");
|
|
896
925
|
}
|
|
897
926
|
}
|
|
927
|
+
/**
|
|
928
|
+
* Resolve fallback value according to priority order:
|
|
929
|
+
* 1. Callsite fallback value (if provided)
|
|
930
|
+
* 2. Initialization fallback value (flagValueDefaults)
|
|
931
|
+
* 3. Default to false
|
|
932
|
+
*/
|
|
933
|
+
resolveFallbackValue(key, callsiteFallback) {
|
|
934
|
+
if (callsiteFallback !== void 0) {
|
|
935
|
+
return callsiteFallback;
|
|
936
|
+
}
|
|
937
|
+
if (key in this.flagValueDefaults) {
|
|
938
|
+
return this.flagValueDefaults[key];
|
|
939
|
+
}
|
|
940
|
+
return false;
|
|
941
|
+
}
|
|
942
|
+
/**
|
|
943
|
+
* Resolve complete CheckFlagReturn object according to priority order:
|
|
944
|
+
* 1. Use callsite fallback for boolean value, construct CheckFlagReturn
|
|
945
|
+
* 2. Use flagCheckDefaults if available for this flag
|
|
946
|
+
* 3. Use flagValueDefaults if available for this flag, construct CheckFlagReturn
|
|
947
|
+
* 4. Default CheckFlagReturn with value: false
|
|
948
|
+
*/
|
|
949
|
+
resolveFallbackCheckFlagReturn(key, callsiteFallback, reason = "Fallback value used", error) {
|
|
950
|
+
if (callsiteFallback !== void 0) {
|
|
951
|
+
return {
|
|
952
|
+
flag: key,
|
|
953
|
+
value: callsiteFallback,
|
|
954
|
+
reason,
|
|
955
|
+
error
|
|
956
|
+
};
|
|
957
|
+
}
|
|
958
|
+
if (key in this.flagCheckDefaults) {
|
|
959
|
+
const defaultReturn = this.flagCheckDefaults[key];
|
|
960
|
+
return {
|
|
961
|
+
...defaultReturn,
|
|
962
|
+
flag: key,
|
|
963
|
+
// Ensure flag matches the requested key
|
|
964
|
+
reason: error !== void 0 ? reason : defaultReturn.reason,
|
|
965
|
+
error
|
|
966
|
+
};
|
|
967
|
+
}
|
|
968
|
+
if (key in this.flagValueDefaults) {
|
|
969
|
+
return {
|
|
970
|
+
flag: key,
|
|
971
|
+
value: this.flagValueDefaults[key],
|
|
972
|
+
reason,
|
|
973
|
+
error
|
|
974
|
+
};
|
|
975
|
+
}
|
|
976
|
+
return {
|
|
977
|
+
flag: key,
|
|
978
|
+
value: false,
|
|
979
|
+
reason,
|
|
980
|
+
error
|
|
981
|
+
};
|
|
982
|
+
}
|
|
898
983
|
/**
|
|
899
984
|
* Get value for a single flag.
|
|
900
985
|
* In WebSocket mode, returns cached values if connection is active, otherwise establishes
|
|
@@ -903,16 +988,21 @@ var Schematic = class {
|
|
|
903
988
|
* In REST mode, makes an API call for each check.
|
|
904
989
|
*/
|
|
905
990
|
async checkFlag(options) {
|
|
906
|
-
const { fallback
|
|
991
|
+
const { fallback, key } = options;
|
|
907
992
|
const context = options.context || this.context;
|
|
908
993
|
const contextStr = contextString(context);
|
|
909
994
|
this.debug(`checkFlag: ${key}`, { context, fallback });
|
|
910
995
|
if (this.isOffline()) {
|
|
996
|
+
const resolvedFallbackResult = this.resolveFallbackCheckFlagReturn(
|
|
997
|
+
key,
|
|
998
|
+
fallback,
|
|
999
|
+
"Offline mode - using initialization defaults"
|
|
1000
|
+
);
|
|
911
1001
|
this.debug(`checkFlag offline result: ${key}`, {
|
|
912
|
-
value:
|
|
1002
|
+
value: resolvedFallbackResult.value,
|
|
913
1003
|
offlineMode: true
|
|
914
1004
|
});
|
|
915
|
-
return
|
|
1005
|
+
return resolvedFallbackResult.value;
|
|
916
1006
|
}
|
|
917
1007
|
if (!this.useWebSocket) {
|
|
918
1008
|
const requestUrl = `${this.apiUrl}/flags/${key}/check`;
|
|
@@ -940,14 +1030,14 @@ var Schematic = class {
|
|
|
940
1030
|
return result.value;
|
|
941
1031
|
}).catch((error) => {
|
|
942
1032
|
console.error("There was a problem with the fetch operation:", error);
|
|
943
|
-
const errorResult =
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
error
|
|
948
|
-
|
|
1033
|
+
const errorResult = this.resolveFallbackCheckFlagReturn(
|
|
1034
|
+
key,
|
|
1035
|
+
fallback,
|
|
1036
|
+
"API request failed",
|
|
1037
|
+
error instanceof Error ? error.message : String(error)
|
|
1038
|
+
);
|
|
949
1039
|
this.submitFlagCheckEvent(key, errorResult, context);
|
|
950
|
-
return
|
|
1040
|
+
return errorResult.value;
|
|
951
1041
|
});
|
|
952
1042
|
}
|
|
953
1043
|
try {
|
|
@@ -957,7 +1047,7 @@ var Schematic = class {
|
|
|
957
1047
|
return existingVals[key].value;
|
|
958
1048
|
}
|
|
959
1049
|
if (this.isOffline()) {
|
|
960
|
-
return fallback;
|
|
1050
|
+
return this.resolveFallbackValue(key, fallback);
|
|
961
1051
|
}
|
|
962
1052
|
try {
|
|
963
1053
|
await this.setContext(context);
|
|
@@ -970,10 +1060,10 @@ var Schematic = class {
|
|
|
970
1060
|
}
|
|
971
1061
|
const contextVals = this.checks[contextStr] ?? {};
|
|
972
1062
|
const flagCheck = contextVals[key];
|
|
973
|
-
const result = flagCheck?.value ?? fallback;
|
|
1063
|
+
const result = flagCheck?.value ?? this.resolveFallbackValue(key, fallback);
|
|
974
1064
|
this.debug(
|
|
975
1065
|
`checkFlag WebSocket result: ${key}`,
|
|
976
|
-
typeof flagCheck !== "undefined" ? flagCheck : { value:
|
|
1066
|
+
typeof flagCheck !== "undefined" ? flagCheck : { value: result, fallbackUsed: true }
|
|
977
1067
|
);
|
|
978
1068
|
if (typeof flagCheck !== "undefined") {
|
|
979
1069
|
this.submitFlagCheckEvent(key, flagCheck, context);
|
|
@@ -981,14 +1071,14 @@ var Schematic = class {
|
|
|
981
1071
|
return result;
|
|
982
1072
|
} catch (error) {
|
|
983
1073
|
console.error("Unexpected error in checkFlag:", error);
|
|
984
|
-
const errorResult =
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
error
|
|
989
|
-
|
|
1074
|
+
const errorResult = this.resolveFallbackCheckFlagReturn(
|
|
1075
|
+
key,
|
|
1076
|
+
fallback,
|
|
1077
|
+
"Unexpected error in flag check",
|
|
1078
|
+
error instanceof Error ? error.message : String(error)
|
|
1079
|
+
);
|
|
990
1080
|
this.submitFlagCheckEvent(key, errorResult, context);
|
|
991
|
-
return
|
|
1081
|
+
return errorResult.value;
|
|
992
1082
|
}
|
|
993
1083
|
}
|
|
994
1084
|
/**
|
|
@@ -1031,11 +1121,12 @@ var Schematic = class {
|
|
|
1031
1121
|
*/
|
|
1032
1122
|
async fallbackToRest(key, context, fallback) {
|
|
1033
1123
|
if (this.isOffline()) {
|
|
1124
|
+
const resolvedFallback = this.resolveFallbackValue(key, fallback);
|
|
1034
1125
|
this.debug(`fallbackToRest offline result: ${key}`, {
|
|
1035
|
-
value:
|
|
1126
|
+
value: resolvedFallback,
|
|
1036
1127
|
offlineMode: true
|
|
1037
1128
|
});
|
|
1038
|
-
return
|
|
1129
|
+
return resolvedFallback;
|
|
1039
1130
|
}
|
|
1040
1131
|
try {
|
|
1041
1132
|
const requestUrl = `${this.apiUrl}/flags/${key}/check`;
|
|
@@ -1062,14 +1153,14 @@ var Schematic = class {
|
|
|
1062
1153
|
return result.value;
|
|
1063
1154
|
} catch (error) {
|
|
1064
1155
|
console.error("REST API call failed, using fallback value:", error);
|
|
1065
|
-
const errorResult =
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
error
|
|
1070
|
-
|
|
1156
|
+
const errorResult = this.resolveFallbackCheckFlagReturn(
|
|
1157
|
+
key,
|
|
1158
|
+
fallback,
|
|
1159
|
+
"API request failed (fallback)",
|
|
1160
|
+
error instanceof Error ? error.message : String(error)
|
|
1161
|
+
);
|
|
1071
1162
|
this.submitFlagCheckEvent(key, errorResult, context);
|
|
1072
|
-
return
|
|
1163
|
+
return errorResult.value;
|
|
1073
1164
|
}
|
|
1074
1165
|
}
|
|
1075
1166
|
/**
|
|
@@ -1286,11 +1377,53 @@ var Schematic = class {
|
|
|
1286
1377
|
}
|
|
1287
1378
|
}
|
|
1288
1379
|
};
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1380
|
+
startRetryTimer = () => {
|
|
1381
|
+
if (this.retryTimer !== null) {
|
|
1382
|
+
return;
|
|
1383
|
+
}
|
|
1384
|
+
this.retryTimer = setInterval(() => {
|
|
1385
|
+
this.flushEventQueue().catch((error) => {
|
|
1386
|
+
this.debug("Error in retry timer flush:", error);
|
|
1387
|
+
});
|
|
1388
|
+
if (this.eventQueue.length === 0) {
|
|
1389
|
+
this.stopRetryTimer();
|
|
1390
|
+
}
|
|
1391
|
+
}, 5e3);
|
|
1392
|
+
this.debug("Started retry timer");
|
|
1393
|
+
};
|
|
1394
|
+
stopRetryTimer = () => {
|
|
1395
|
+
if (this.retryTimer !== null) {
|
|
1396
|
+
clearInterval(this.retryTimer);
|
|
1397
|
+
this.retryTimer = null;
|
|
1398
|
+
this.debug("Stopped retry timer");
|
|
1399
|
+
}
|
|
1400
|
+
};
|
|
1401
|
+
flushEventQueue = async () => {
|
|
1402
|
+
if (this.eventQueue.length === 0) {
|
|
1403
|
+
return;
|
|
1404
|
+
}
|
|
1405
|
+
const now = Date.now();
|
|
1406
|
+
const readyEvents = [];
|
|
1407
|
+
const notReadyEvents = [];
|
|
1408
|
+
for (const event of this.eventQueue) {
|
|
1409
|
+
if (event.next_retry_at === void 0 || event.next_retry_at <= now) {
|
|
1410
|
+
readyEvents.push(event);
|
|
1411
|
+
} else {
|
|
1412
|
+
notReadyEvents.push(event);
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1415
|
+
if (readyEvents.length === 0) {
|
|
1416
|
+
this.debug(`No events ready for retry yet (${notReadyEvents.length} still in backoff)`);
|
|
1417
|
+
return;
|
|
1418
|
+
}
|
|
1419
|
+
this.debug(`Flushing event queue: ${readyEvents.length} ready, ${notReadyEvents.length} waiting`);
|
|
1420
|
+
this.eventQueue = notReadyEvents;
|
|
1421
|
+
for (const event of readyEvents) {
|
|
1422
|
+
try {
|
|
1423
|
+
await this.sendEvent(event);
|
|
1424
|
+
this.debug(`Queued event sent successfully:`, event.type);
|
|
1425
|
+
} catch (error) {
|
|
1426
|
+
this.debug(`Failed to send queued event:`, error);
|
|
1294
1427
|
}
|
|
1295
1428
|
}
|
|
1296
1429
|
};
|
|
@@ -1338,12 +1471,37 @@ var Schematic = class {
|
|
|
1338
1471
|
},
|
|
1339
1472
|
body: payload
|
|
1340
1473
|
});
|
|
1474
|
+
if (!response.ok) {
|
|
1475
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
1476
|
+
}
|
|
1341
1477
|
this.debug(`event sent:`, {
|
|
1342
1478
|
status: response.status,
|
|
1343
1479
|
statusText: response.statusText
|
|
1344
1480
|
});
|
|
1345
1481
|
} catch (error) {
|
|
1346
|
-
|
|
1482
|
+
const retryCount = (event.retry_count ?? 0) + 1;
|
|
1483
|
+
if (retryCount <= this.maxEventRetries) {
|
|
1484
|
+
this.debug(`Event failed to send (attempt ${retryCount}/${this.maxEventRetries}), queueing for retry:`, error);
|
|
1485
|
+
const baseDelay = this.eventRetryInitialDelay * Math.pow(2, retryCount - 1);
|
|
1486
|
+
const jitterDelay = Math.min(baseDelay, this.eventRetryMaxDelay);
|
|
1487
|
+
const nextRetryAt = Date.now() + jitterDelay;
|
|
1488
|
+
const retryEvent = {
|
|
1489
|
+
...event,
|
|
1490
|
+
retry_count: retryCount,
|
|
1491
|
+
next_retry_at: nextRetryAt
|
|
1492
|
+
};
|
|
1493
|
+
if (this.eventQueue.length < this.maxEventQueueSize) {
|
|
1494
|
+
this.eventQueue.push(retryEvent);
|
|
1495
|
+
this.debug(`Event queued for retry in ${jitterDelay}ms (${this.eventQueue.length}/${this.maxEventQueueSize})`);
|
|
1496
|
+
} else {
|
|
1497
|
+
this.debug(`Event queue full (${this.maxEventQueueSize}), dropping oldest event`);
|
|
1498
|
+
this.eventQueue.shift();
|
|
1499
|
+
this.eventQueue.push(retryEvent);
|
|
1500
|
+
}
|
|
1501
|
+
this.startRetryTimer();
|
|
1502
|
+
} else {
|
|
1503
|
+
this.debug(`Event failed permanently after ${this.maxEventRetries} attempts, dropping:`, error);
|
|
1504
|
+
}
|
|
1347
1505
|
}
|
|
1348
1506
|
return Promise.resolve();
|
|
1349
1507
|
};
|
|
@@ -1368,6 +1526,7 @@ var Schematic = class {
|
|
|
1368
1526
|
clearTimeout(this.wsReconnectTimer);
|
|
1369
1527
|
this.wsReconnectTimer = null;
|
|
1370
1528
|
}
|
|
1529
|
+
this.stopRetryTimer();
|
|
1371
1530
|
if (this.conn) {
|
|
1372
1531
|
try {
|
|
1373
1532
|
const socket = await this.conn;
|
|
@@ -1415,16 +1574,15 @@ var Schematic = class {
|
|
|
1415
1574
|
* Handle browser coming back online
|
|
1416
1575
|
*/
|
|
1417
1576
|
handleNetworkOnline = () => {
|
|
1418
|
-
|
|
1419
|
-
this.debug("No context set, skipping reconnection");
|
|
1420
|
-
return;
|
|
1421
|
-
}
|
|
1577
|
+
this.debug("Network online, attempting reconnection and flushing queued events");
|
|
1422
1578
|
this.wsReconnectAttempts = 0;
|
|
1423
1579
|
if (this.wsReconnectTimer !== null) {
|
|
1424
1580
|
clearTimeout(this.wsReconnectTimer);
|
|
1425
1581
|
this.wsReconnectTimer = null;
|
|
1426
1582
|
}
|
|
1427
|
-
this.
|
|
1583
|
+
this.flushEventQueue().catch((error) => {
|
|
1584
|
+
this.debug("Error flushing event queue on network online:", error);
|
|
1585
|
+
});
|
|
1428
1586
|
this.attemptReconnect();
|
|
1429
1587
|
};
|
|
1430
1588
|
/**
|
|
@@ -1454,10 +1612,20 @@ var Schematic = class {
|
|
|
1454
1612
|
try {
|
|
1455
1613
|
this.conn = this.wsConnect();
|
|
1456
1614
|
const socket = await this.conn;
|
|
1615
|
+
this.debug(`Reconnection context check:`, {
|
|
1616
|
+
hasCompany: this.context.company !== void 0,
|
|
1617
|
+
hasUser: this.context.user !== void 0,
|
|
1618
|
+
context: this.context
|
|
1619
|
+
});
|
|
1457
1620
|
if (this.context.company !== void 0 || this.context.user !== void 0) {
|
|
1458
|
-
this.debug(`Reconnected, re-sending context`);
|
|
1459
|
-
await this.
|
|
1621
|
+
this.debug(`Reconnected, force re-sending context`);
|
|
1622
|
+
await this.wsSendContextAfterReconnection(socket, this.context);
|
|
1623
|
+
} else {
|
|
1624
|
+
this.debug(`No context to re-send after reconnection - websocket ready for new context`);
|
|
1460
1625
|
}
|
|
1626
|
+
this.flushEventQueue().catch((error) => {
|
|
1627
|
+
this.debug("Error flushing event queue after websocket reconnection:", error);
|
|
1628
|
+
});
|
|
1461
1629
|
this.debug(`Reconnection successful`);
|
|
1462
1630
|
} catch (error) {
|
|
1463
1631
|
this.debug(`Reconnection attempt failed:`, error);
|
|
@@ -1518,6 +1686,61 @@ var Schematic = class {
|
|
|
1518
1686
|
};
|
|
1519
1687
|
});
|
|
1520
1688
|
};
|
|
1689
|
+
// Send a message on the websocket after reconnection, forcing the send even if context appears unchanged
|
|
1690
|
+
// because the server has lost all state and needs the initial context
|
|
1691
|
+
wsSendContextAfterReconnection = (socket, context) => {
|
|
1692
|
+
if (this.isOffline()) {
|
|
1693
|
+
this.debug("wsSendContextAfterReconnection: skipped (offline mode)");
|
|
1694
|
+
this.setIsPending(false);
|
|
1695
|
+
return Promise.resolve();
|
|
1696
|
+
}
|
|
1697
|
+
return new Promise((resolve) => {
|
|
1698
|
+
this.debug(`WebSocket force sending context after reconnection:`, context);
|
|
1699
|
+
this.context = context;
|
|
1700
|
+
const sendMessage = () => {
|
|
1701
|
+
let resolved = false;
|
|
1702
|
+
const messageHandler = (event) => {
|
|
1703
|
+
const message = JSON.parse(event.data);
|
|
1704
|
+
this.debug(`WebSocket message received after reconnection:`, message);
|
|
1705
|
+
if (!(contextString(context) in this.checks)) {
|
|
1706
|
+
this.checks[contextString(context)] = {};
|
|
1707
|
+
}
|
|
1708
|
+
(message.flags ?? []).forEach((flag) => {
|
|
1709
|
+
const flagCheck = CheckFlagReturnFromJSON(flag);
|
|
1710
|
+
const contextStr = contextString(context);
|
|
1711
|
+
if (this.checks[contextStr] === void 0) {
|
|
1712
|
+
this.checks[contextStr] = {};
|
|
1713
|
+
}
|
|
1714
|
+
this.checks[contextStr][flagCheck.flag] = flagCheck;
|
|
1715
|
+
});
|
|
1716
|
+
this.useWebSocket = true;
|
|
1717
|
+
socket.removeEventListener("message", messageHandler);
|
|
1718
|
+
if (!resolved) {
|
|
1719
|
+
resolved = true;
|
|
1720
|
+
resolve(this.setIsPending(false));
|
|
1721
|
+
}
|
|
1722
|
+
};
|
|
1723
|
+
socket.addEventListener("message", messageHandler);
|
|
1724
|
+
const clientVersion = this.additionalHeaders["X-Schematic-Client-Version"] ?? `schematic-js@${version}`;
|
|
1725
|
+
const messagePayload = {
|
|
1726
|
+
apiKey: this.apiKey,
|
|
1727
|
+
clientVersion,
|
|
1728
|
+
data: context
|
|
1729
|
+
};
|
|
1730
|
+
this.debug(`WebSocket sending forced message after reconnection:`, messagePayload);
|
|
1731
|
+
socket.send(JSON.stringify(messagePayload));
|
|
1732
|
+
};
|
|
1733
|
+
if (socket.readyState === WebSocket.OPEN) {
|
|
1734
|
+
this.debug(`WebSocket already open, sending forced message after reconnection`);
|
|
1735
|
+
sendMessage();
|
|
1736
|
+
} else {
|
|
1737
|
+
socket.addEventListener("open", () => {
|
|
1738
|
+
this.debug(`WebSocket opened, sending forced message after reconnection`);
|
|
1739
|
+
sendMessage();
|
|
1740
|
+
});
|
|
1741
|
+
}
|
|
1742
|
+
});
|
|
1743
|
+
};
|
|
1521
1744
|
// Send a message on the websocket indicating interest in a particular evaluation context
|
|
1522
1745
|
// and wait for the initial set of flag values to be returned
|
|
1523
1746
|
wsSendMessage = (socket, context) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@schematichq/schematic-js",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.9",
|
|
4
4
|
"main": "dist/schematic.cjs.js",
|
|
5
5
|
"module": "dist/schematic.esm.js",
|
|
6
6
|
"types": "dist/schematic.d.ts",
|
|
@@ -39,8 +39,8 @@
|
|
|
39
39
|
"devDependencies": {
|
|
40
40
|
"@eslint/js": "^9.39.1",
|
|
41
41
|
"@microsoft/api-extractor": "^7.55.0",
|
|
42
|
-
"@openapitools/openapi-generator-cli": "^2.25.
|
|
43
|
-
"@vitest/browser": "^4.0.
|
|
42
|
+
"@openapitools/openapi-generator-cli": "^2.25.1",
|
|
43
|
+
"@vitest/browser": "^4.0.10",
|
|
44
44
|
"esbuild": "^0.27.0",
|
|
45
45
|
"eslint": "^9.39.1",
|
|
46
46
|
"globals": "^16.5.0",
|
|
@@ -50,8 +50,8 @@
|
|
|
50
50
|
"mock-socket": "^9.3.1",
|
|
51
51
|
"prettier": "^3.6.2",
|
|
52
52
|
"typescript": "^5.9.3",
|
|
53
|
-
"typescript-eslint": "^8.
|
|
54
|
-
"vitest": "^4.0.
|
|
53
|
+
"typescript-eslint": "^8.47.0",
|
|
54
|
+
"vitest": "^4.0.10"
|
|
55
55
|
},
|
|
56
56
|
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
|
|
57
57
|
}
|