@harnessio/ff-javascript-client-sdk
Version:
Basic library for integrating CF into javascript applications.
3 lines (2 loc) • 18.2 kB
JavaScript
var Oe=Object.create;var M=Object.defineProperty,Pe=Object.defineProperties,Fe=Object.getOwnPropertyDescriptor,xe=Object.getOwnPropertyDescriptors,Me=Object.getOwnPropertyNames,oe=Object.getOwnPropertySymbols,Ne=Object.getPrototypeOf,le=Object.prototype.hasOwnProperty,Ve=Object.prototype.propertyIsEnumerable;var se=(t,e,n)=>e in t?M(t,e,{enumerable:!0,configurable:!0,writable:!0,value:n}):t[e]=n,I=(t,e)=>{for(var n in e||(e={}))le.call(e,n)&&se(t,n,e[n]);if(oe)for(var n of oe(e))Ve.call(e,n)&&se(t,n,e[n]);return t},N=(t,e)=>Pe(t,xe(e));var _e=(t,e)=>{for(var n in e)M(t,n,{get:e[n],enumerable:!0})},ce=(t,e,n,o)=>{if(e&&typeof e=="object"||typeof e=="function")for(let r of Me(e))!le.call(t,r)&&r!==n&&M(t,r,{get:()=>e[r],enumerable:!(o=Fe(e,r))||o.enumerable});return t};var ue=(t,e,n)=>(n=t!=null?Oe(Ne(t)):{},ce(e||!t||!t.__esModule?M(n,"default",{value:t,enumerable:!0}):n,t)),ke=t=>ce(M({},"__esModule",{value:!0}),t);var E=(t,e,n)=>new Promise((o,r)=>{var l=R=>{try{p(n.next(R))}catch(u){r(u)}},m=R=>{try{p(n.throw(R))}catch(u){r(u)}},p=R=>R.done?o(R.value):Promise.resolve(R.value).then(l,m);p((n=n.apply(t,e)).next())});var Ye={};_e(Ye,{Event:()=>x,initialize:()=>Je});module.exports=ke(Ye);var Ee=ue(require("jwt-decode")),Re=ue(require("mitt"));var x=(b=>(b.READY="ready",b.CONNECTED="connected",b.DISCONNECTED="disconnected",b.STOPPED="stopped",b.POLLING="polling",b.POLLING_STOPPED="polling stopped",b.FLAGS_LOADED="flags loaded",b.CACHE_LOADED="cache loaded",b.CHANGED="changed",b.ERROR="error",b.ERROR_CACHE="cache error",b.ERROR_METRICS="metrics error",b.ERROR_AUTH="auth error",b.ERROR_FETCH_FLAGS="fetch flags error",b.ERROR_FETCH_FLAG="fetch flag error",b.ERROR_STREAM="stream error",b.ERROR_DEFAULT_VARIATION_RETURNED="default variation returned",b))(x||{});var Le={debug:!1,baseUrl:"https://config.ff.harness.io/api/1.0",eventUrl:"https://events.ff.harness.io/api/1.0",eventsSyncInterval:6e4,pollingInterval:6e4,enableAnalytics:!0,streamEnabled:!0,cache:!1,authRequestReadTimeout:0,maxStreamRetries:1/0},de=t=>{let e=I(I({},Le),t);return e.pollingEnabled===void 0&&(e.pollingEnabled=e.streamEnabled),e.eventsSyncInterval<6e4&&(e.eventsSyncInterval=6e4),e.pollingInterval<6e4&&(e.pollingInterval=6e4),(!e.logger||!e.logger.debug||!e.logger.error||!e.logger.info||!e.logger.warn)&&(e.logger=console),e},J=(t,e=!0)=>{e?setTimeout(t,0):t()},k=(t,e)=>Math.round(Math.random()*(e-t)+t),ge=t=>{let e="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",n="",o=0,r=He(JSON.stringify(t));for(;o<r.length;){let l=r.charCodeAt(o++),m=r.charCodeAt(o++),p=r.charCodeAt(o++),R=l>>2,u=(l&3)<<4|m>>4,S=(m&15)<<2|p>>6,O=p&63;isNaN(m)?S=O=64:isNaN(p)&&(O=64),n+=e.charAt(R)+e.charAt(u)+e.charAt(S)+e.charAt(O)}return n},He=t=>t.replace(/\r\n/g,`
`).split("").map(e=>{let n=e.charCodeAt(0);return n<128?String.fromCharCode(n):n>127&&n<2048?String.fromCharCode(n>>6|192)+String.fromCharCode(n&63|128):String.fromCharCode(n>>12|224)+String.fromCharCode(n>>6&63|128)+String.fromCharCode(n&63|128)}).join("");function L(t){return[...t].sort(({flag:e},{flag:n})=>e<n?-1:1)}function Y(t){return function(...n){let[o,r]=t(n);return fetch(o,r)}}var fe=3e4,H=class{constructor(e,n,o,r,l,m,p,R,u,S){this.eventBus=e;this.configurations=n;this.url=o;this.apiKey=r;this.standardHeaders=l;this.fallbackPoller=m;this.logDebug=p;this.logError=R;this.eventCallback=u;this.maxRetries=S;this.closed=!1;this.connectionOpened=!1;this.disconnectEventEmitted=!1;this.reconnectAttempts=0;this.retriesExhausted=!1}start(){let e=u=>{u.toString().split(/\r?\n/).forEach(n)},n=u=>{if(u.startsWith("data:")){let S=JSON.parse(u.substring(5));this.logDebugMessage("Received event from stream: ",S),this.eventCallback(S)}},o=()=>{this.logDebugMessage("Stream connected"),this.eventBus.emit("connected"),this.reconnectAttempts=0},r=()=>{clearInterval(this.readTimeoutCheckerId);let u=k(1e3,1e4);if(this.reconnectAttempts++,this.logDebugMessage("Stream disconnected, will reconnect in "+u+"ms"),this.disconnectEventEmitted||(this.eventBus.emit("disconnected"),this.disconnectEventEmitted=!0),this.reconnectAttempts>=5&&this.reconnectAttempts%5===0&&this.logErrorMessage(`Reconnection failed after ${this.reconnectAttempts} attempts; attempting further reconnections.`),this.reconnectAttempts>=this.maxRetries){this.retriesExhausted=!0,this.configurations.pollingEnabled?this.logErrorMessage("Max streaming retries reached. Staying in polling mode."):this.logErrorMessage("Max streaming retries reached. Polling mode is disabled and will receive no further flag updates until SDK client is restarted.");return}setTimeout(()=>this.start(),u)},l=u=>{this.retriesExhausted||(u&&this.logDebugMessage("Stream has issue",u),this.fallBackToPolling(),this.eventBus.emit("stream error",u),this.eventBus.emit("error",u),r())},m=I({"Cache-Control":"no-cache",Accept:"text/event-stream","API-Key":this.apiKey},this.standardHeaders);this.logDebugMessage("SSE HTTP start request",this.url),this.xhr=new XMLHttpRequest,this.xhr.open("GET",this.url);for(let[u,S]of Object.entries(m))this.xhr.setRequestHeader(u,S);this.xhr.timeout=24*60*60*1e3,this.xhr.onerror=()=>{this.connectionOpened=!1,l("XMLHttpRequest error on SSE stream")},this.xhr.onabort=()=>{this.connectionOpened=!1,this.logDebugMessage("SSE aborted"),this.closed||l(null)},this.xhr.ontimeout=()=>{this.connectionOpened=!1,l("SSE timeout")},this.xhr.onload=()=>{l(`Received XMLHttpRequest onLoad event: ${this.xhr.status}`)};let p=0,R=Date.now();this.xhr.onprogress=()=>{this.connectionOpened||(o(),this.connectionOpened=!0,this.disconnectEventEmitted=!1),this.stopFallBackPolling(),R=Date.now();let u=this.xhr.responseText.slice(p);p+=u.length,this.logDebugMessage("SSE GOT: "+u),e(u)},this.readTimeoutCheckerId=setInterval(()=>{R<Date.now()-fe&&(this.logDebugMessage("SSE read timeout"),this.xhr.abort())},fe),this.xhr.send()}close(){this.connectionOpened=!1,this.closed=!0,this.xhr&&this.xhr.abort(),clearInterval(this.readTimeoutCheckerId),this.eventBus.emit("stopped"),this.stopFallBackPolling()}fallBackToPolling(){!this.fallbackPoller.isPolling()&&this.configurations.pollingEnabled&&(this.logDebugMessage("Falling back to polling mode while stream recovers"),this.fallbackPoller.start())}stopFallBackPolling(){this.fallbackPoller.isPolling()&&(this.logDebugMessage("Stopping fallback polling mode"),this.fallbackPoller.stop())}logDebugMessage(e,...n){this.configurations.debug&&this.logDebug(`Streaming: ${e}`,...n)}logErrorMessage(e,...n){this.logError(`Streaming: ${e}`,...n)}};function he(t,e,n,o,r,l){let m=t in n,p=m?n[t]:e;if(m)o(t,p);else{let R={flag:t,defaultVariation:e};r.emit("default variation returned",R)}return l?{value:p,isDefaultValue:!m}:p}var V=class{constructor(e,n,o,r,l){this.fetchFlagsFn=e;this.configurations=n;this.eventBus=o;this.logDebug=r;this.logError=l;this.maxAttempts=5}start(){if(this.isPolling()){this.logDebugMessage("Already polling.");return}this.isRunning=!0,this.eventBus.emit("polling"),this.logDebugMessage(`Starting poller, first poll will be in ${this.configurations.pollingInterval}ms`),this.timeoutId=setTimeout(()=>this.poll(),this.configurations.pollingInterval)}poll(){this.isRunning&&this.attemptFetch().finally(()=>{this.isRunning&&(this.timeoutId=setTimeout(()=>this.poll(),this.configurations.pollingInterval))})}attemptFetch(){return E(this,null,function*(){for(let e=1;e<=this.maxAttempts;e++){let n=yield this.fetchFlagsFn();if(n.type==="success"){this.logDebugMessage(`Successfully polled for flag updates, next poll in ${this.configurations.pollingInterval}ms. `);return}if(this.logErrorMessage("Error when polling for flag updates",n.error),e>=this.maxAttempts){this.logDebugMessage(`Maximum attempts reached for polling for flags. Next poll in ${this.configurations.pollingInterval}ms.`);return}this.logDebugMessage(`Polling for flags attempt #${e} failed. Remaining attempts: ${this.maxAttempts-e}`,n.error);let o=k(1e3,1e4);yield new Promise(r=>setTimeout(r,o))}})}stop(){this.timeoutId&&(this.isRunning=!1,clearTimeout(this.timeoutId),this.timeoutId=void 0,this.eventBus.emit("polling stopped"),this.logDebugMessage("Polling stopped"))}isPolling(){return this.isRunning}logDebugMessage(e,...n){this.configurations.debug&&this.logDebug(`Poller: ${e}`,...n)}logErrorMessage(e,...n){this.logError(`Poller: ${e}`,...n)}};function me(n){return E(this,arguments,function*(t,e={}){let o=yield Be(t),r=Ke(e);return{loadFromCache:()=>X(o,r,e),saveToCache:l=>z(o,r,l),updateCachedEvaluation:l=>Ge(o,r,l),removeCachedEvaluation:l=>qe(o,r,l)}})}function X(o,r){return E(this,arguments,function*(t,e,n={}){let l=parseInt(yield e.getItem(t+".ts"));if(n!=null&&n.ttl&&!isNaN(l)&&l+n.ttl<Date.now())return yield $e(t,e),[];let m=yield e.getItem(t);if(m)try{return JSON.parse(m)}catch(p){}return[]})}function $e(t,e){return E(this,null,function*(){yield e.removeItem(t),yield e.removeItem(t+".ts")})}function z(t,e,n){return E(this,null,function*(){yield e.setItem(t,JSON.stringify(L(n))),yield e.setItem(t+".ts",Date.now().toString())})}function Ge(t,e,n){return E(this,null,function*(){let o=yield X(t,e),r=o.find(({flag:l})=>l===n.flag);r?Object.assign(r,n):o.push(n),yield z(t,e,o)})}function qe(t,e,n){return E(this,null,function*(){let o=yield X(t,e),r=o.findIndex(({flag:l})=>l===n);r>-1&&(o.splice(r,1),yield z(t,e,o))})}function ve(t,e,n={}){return n.deriveKeyFromTargetAttributes?JSON.stringify(Object.keys(t.attributes||{}).sort().filter(o=>!Array.isArray(n.deriveKeyFromTargetAttributes)||n.deriveKeyFromTargetAttributes.includes(o)).reduce((o,r)=>N(I({},o),{[r]:t.attributes[r]}),{}))+t.identifier+e:t.identifier+e}function Be(t){return E(this,null,function*(){var n,o;let e=t;if(globalThis!=null&&globalThis.TextEncoder&&((o=(n=globalThis==null?void 0:globalThis.crypto)==null?void 0:n.subtle)!=null&&o.digest)){let l=new TextEncoder().encode(t),m=yield crypto.subtle.digest("SHA-256",l);e=Array.from(new Uint8Array(m)).map(R=>R.toString(16).padStart(2,"0")).join("")}else globalThis.btoa&&(e=btoa(t));return"HARNESS_FF_CACHE_"+e})}function Ke(t){let e;return!t.storage||typeof t.storage!="object"||!("getItem"in t.storage)||!("setItem"in t.storage)||!("removeItem"in t.storage)?globalThis.localStorage?e=globalThis.localStorage:globalThis.sessionStorage?e=globalThis.sessionStorage:e=Ue:e=t.storage,{getItem(o){return E(this,null,function*(){let r=e.getItem(o);return r instanceof Promise?yield r:r})},setItem(o,r){return E(this,null,function*(){let l=e.setItem(o,r);l instanceof Promise&&(yield l)})},removeItem(o){return E(this,null,function*(){let r=e.removeItem(o);r instanceof Promise&&(yield r)})}}}var Ue={getItem:()=>null,setItem:()=>{},removeItem:()=>{}};var be="1.31.0",pe=`Javascript ${be} Client`,je=500,We=globalThis.fetch,Q=!!globalThis.Proxy,Je=(t,e,n)=>{let o=!1,r,l,m,p,R,u,S={},O=Y(i=>i),Z=0,ee=!1,$=!1,C=[],h=de(n),b=h.enableAnalytics,g=(0,Re.default)(),G=()=>{$=!0},q=()=>{$=!1},P=()=>b&&!$,y=(i,...a)=>{h.debug&&h.logger.debug(`[FF-SDK] ${i}`,...a)},T=(i,...a)=>{h.logger.error(`[FF-SDK] ${i}`,...a)},ye=(i,...a)=>{h.logger.warn(`[FF-SDK] ${i}`,...a)},B=i=>{let{value:a}=i;try{switch(i.kind.toLowerCase()){case"int":case"number":a=Number(a);break;case"boolean":a=a.toString().toLowerCase()==="true";break;case"json":a=JSON.parse(a);break}}catch(c){T(c)}return a},K=i=>{if(P()){let a=Date.now();a-i.lastAccessed>je&&(i.count++,i.lastAccessed=a)}},Se=()=>E(void 0,null,function*(){if(h.cache){y("initializing cache");try{let i=!0,a=typeof h.cache=="boolean"?{}:h.cache,c=yield me(ve(e,t,a),a),f=yield c.loadFromCache();f!=null&&f.length&&J(()=>{y("loading from cache",f),ae(f,!1),g.emit("cache loaded",f)}),W("flags loaded",v=>E(void 0,null,function*(){yield c.saveToCache(v),i=!1})),W("changed",v=>E(void 0,null,function*(){i||(v.deleted?yield c.removeCachedEvaluation(v.flag):yield c.updateCachedEvaluation(v))}))}catch(i){T("Cache error: ",i),g.emit("cache error",i),g.emit("error",i)}}}),Ie=(i,a)=>E(void 0,null,function*(){let c=`${a.baseUrl}/client/auth`,f={method:"POST",headers:{"Content-Type":"application/json","Harness-SDK-Info":pe},body:JSON.stringify({apiKey:i,target:N(I({},e),{identifier:String(e.identifier)})})},v,s;window.AbortController&&h.authRequestReadTimeout>0?(s=new AbortController,f.signal=s.signal,v=window.setTimeout(()=>s.abort(),a.authRequestReadTimeout)):a.authRequestReadTimeout>0&&ye("AbortController is not available, auth request will not timeout");try{let d=yield We(c,f);if(!d.ok)throw new Error(`${d.status}: ${d.statusText}`);return(yield d.json()).authToken}catch(d){if(s&&s.signal.aborted)throw new Error(`Request to ${c} failed: Request timeout via configured authRequestTimeout of ${h.authRequestReadTimeout}`);let D=d instanceof Error?d.message:String(d);throw new Error(`Request to ${c} failed: ${D}`)}finally{v&&clearTimeout(v)}}),U=0,j=()=>{if(P())if(C.length){y("Sending metrics...",{metrics:C,evaluations:A});let i={metricsData:C.map(a=>({timestamp:Date.now(),count:a.count,metricsType:"FFMETRICS",attributes:[{key:"featureIdentifier",value:a.featureIdentifier},{key:"featureName",value:a.featureIdentifier},{key:"variationIdentifier",value:a.variationIdentifier},{key:"target",value:e.identifier},{key:"SDK_NAME",value:"JavaScript"},{key:"SDK_LANGUAGE",value:"JavaScript"},{key:"SDK_TYPE",value:"client"},{key:"SDK_VERSION",value:be}]}))};O(`${h.eventUrl}/metrics/${r}?cluster=${l}`,{method:"POST",headers:I({"Content-Type":"application/json"},S),body:JSON.stringify(i)}).then(()=>{C=[],U=0}).catch(a=>{U++&&(C=[],U=0),y(a),g.emit("metrics error",a)}).finally(()=>{u=window.setTimeout(j,h.eventsSyncInterval)})}else u=window.setTimeout(j,h.eventsSyncInterval)},A={},Ae=i=>{y("Sending event for",i.flag),Q?g.emit("changed",new Proxy(i,{get(a,c){var f;if(P()&&a.hasOwnProperty(c)&&c==="value"){let v=a.flag,s=i.value,d=C.find(D=>D.featureIdentifier===v&&D.featureValue===s);d?(K(d),d.variationIdentifier=((f=A[v])==null?void 0:f.identifier)||""):C.push({featureIdentifier:v,featureValue:String(s),variationIdentifier:A[v].identifier||"",count:1,lastAccessed:Date.now()}),y("Metrics event: Flag",c,"has been read with value via stream update",s)}return c==="value"?B(i):i[c]}})):g.emit("changed",{deleted:i.deleted,flag:i.flag,value:B(i)})},te=function(){return Q?new Proxy({},{get(i,a){var f,v,s;let c=i[a];if(P()&&i.hasOwnProperty(a)){let d=i[a],D=C.find(re=>re.featureIdentifier===a&&d===re.featureValue);D?(D.variationIdentifier=((f=A[a])==null?void 0:f.identifier)||"",K(D)):C.push({featureIdentifier:a,featureValue:d,variationIdentifier:((v=A[a])==null?void 0:v.identifier)||"",count:1,lastAccessed:Date.now()}),y("Metrics event: Flag:",a,"has been read with value:",d,"variationIdentifier:",(s=A[a])==null?void 0:s.identifier)}return c}}):{}},w=te();Se().then(()=>Ie(t,h).then(i=>E(void 0,null,function*(){if(o)return;R=i;let a=(0,Ee.default)(i);S={Authorization:`Bearer ${R}`,"Harness-AccountID":a.accountID,"Harness-EnvironmentID":a.environmentIdentifier,"Harness-SDK-Info":pe};let c=ge(e);c.length<262144&&(S["Harness-Target"]=c),y("Authenticated",a),P()&&(u=window.setTimeout(j,h.eventsSyncInterval)),r=a.environment,l=a.clusterIdentifier;let f=!!Object.keys(A).length;if((yield _()).type==="success"&&y("Fetch all flags ok",w),!o){if(h.streamEnabled?(y("Streaming mode enabled"),Ce()):h.pollingEnabled?(y("Polling mode enabled"),Te()):y("Streaming and polling mode disabled"),!f){G();let s=I({},w);q(),g.emit("ready",s)}ee=!0}})).catch(i=>{T("Authentication error: ",i),g.emit("auth error",i),g.emit("error",i)}));let _=()=>E(void 0,null,function*(){try{let i=yield O(`${h.baseUrl}/client/env/${r}/target/${e.identifier}/evaluations?cluster=${l}`,{headers:S});if(i.ok){let a=L(yield i.json());return a.forEach(F),g.emit("flags loaded",a),{type:"success",data:a}}else return T("Features fetch operation error: ",i),g.emit("fetch flags error",i),g.emit("error",i),{type:"error",error:i}}catch(i){return T("Features fetch operation error: ",i),g.emit("fetch flags error",i),g.emit("error",i),{type:"error",error:i}}}),ie=i=>E(void 0,null,function*(){try{let a=yield O(`${h.baseUrl}/client/env/${r}/target/${e.identifier}/evaluations/${i}?cluster=${l}`,{headers:S});if(a.ok){let c=yield a.json();F(c)}else T("Feature fetch operation error: ",a),g.emit("fetch flag error",a),g.emit("error",a)}catch(a){T("Feature fetch operation error: ",a),g.emit("fetch flag error",a),g.emit("error",a)}}),F=i=>{G();let a=B(i);a!==w[i.flag]&&(y("Flag variation has changed for ",i.identifier),w[i.flag]=a,A[i.flag]=N(I({},i),{value:a}),Ae(i)),q()};p=new V(_,h,g,y,T);let Ce=()=>{let i=s=>{switch(s.event){case"create":c(s.evaluations)?s.evaluations.forEach(d=>{F(d)}):setTimeout(()=>ie(s.identifier),1e3);break;case"patch":c(s.evaluations)?s.evaluations.forEach(d=>{F(d)}):ie(s.identifier);break;case"delete":delete w[s.identifier],g.emit("changed",{flag:s.identifier,value:void 0,deleted:!0}),y("Evaluation deleted",{message:s,storage:w});break}},a=s=>!(!s||!s.flag||!s.identifier||!s.kind||!s.value),c=s=>!(!s||s.length==0||!s.every(d=>a(d))),f=s=>{s.event==="patch"&&(c(s.evaluations)?s.evaluations.forEach(d=>{F(d)}):_())},v=`${h.baseUrl}/stream?cluster=${l}`;m=new H(g,h,v,t,S,p,y,T,s=>{s.domain==="flag"?i(s):s.domain==="target-segment"&&f(s)},h.maxStreamRetries),m.start()},Te=()=>{p.start()},W=(i,a)=>g.on(i,a),we=(i,a)=>{i?g.off(i,a):ne()},De=(i,a)=>{var s;if(!P()||Q||a===void 0)return;let c=a,f=i,v=C.find(d=>d.featureIdentifier===f&&d.featureValue===c);v?(K(v),v.variationIdentifier=((s=A[f])==null?void 0:s.identifier)||""):C.push({featureIdentifier:f,featureValue:c,count:1,variationIdentifier:A[f].identifier||"",lastAccessed:Date.now()})},ne=()=>{o=!0,h.streamEnabled&&(y("Closing event stream"),typeof(m==null?void 0:m.close)=="function"&&m.close(),g.all.clear()),h.pollingEnabled&&p.isPolling()&&(y("Closing Poller"),p.stop()),w=te(),A={},clearTimeout(u)},ae=(i,a=!0)=>{i.length&&J(()=>{let c=!!Object.keys(A).length;if(i.forEach(F),!c){G();let f=I({},w);q(),g.emit("ready",f)}},a)};return{on:W,off:we,close:ne,setEvaluations:ae,registerAPIRequestMiddleware:i=>{O=Y(i)},refreshEvaluations:()=>{ee&&!o&&Date.now()-Z>=6e4&&(_(),Z=Date.now())},variation:(i,a,c=!1)=>he(i,a,w,De,g,c)}};0&&(module.exports={Event,initialize});