@configdirector/server-sdk 0.9.1 → 0.9.2
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.
|
@@ -2,4 +2,4 @@ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});var e=Object.
|
|
|
2
2
|
`?(t.push(e.slice(n,r)),i===`\r`&&e[r+1]===`
|
|
3
3
|
`&&r++,r++,n=r):r++}return this.bufferedInput=e.slice(n),t}dispatchLine(e){if(e.startsWith(`:`)){this.onComment?.(this.extractValue(e,1));return}if(e===``){this.emitEvent();return}this.applyField(this.parseField(e))}emitEvent(){let e=this.currentEvent.data.endsWith(`
|
|
4
4
|
`)?this.currentEvent.data.slice(0,-1):this.currentEvent.data;e!==``&&this.onEvent({...this.currentEvent,id:this.lastEventId,data:e}),this.currentEvent={data:``}}applyField(e){switch(e.field){case`event`:this.currentEvent.type=e.value;break;case`data`:this.currentEvent.data+=e.value+`
|
|
5
|
-
`;break;case`id`:e.value.includes(`\0`)||(this.lastEventId=e.value);break;case`retry`:/^\d+$/.test(e.value)&&this.onRetry?.(Number.parseInt(e.value,10));break;default:break}}parseField(e){let t=e.indexOf(`:`);return t===-1?{field:e,value:``}:{field:e.slice(0,t),value:this.extractValue(e,t+1)}}extractValue(e,t){return e[t]===` `?e.slice(t+1):e.slice(t)}};let y=function(e){return e.OPEN=`open`,e.CLOSED=`closed`,e.CONNECTING=`connecting`,e}({});var b=class{constructor(e){this.DEFAULT_SHOULD_RECONNECT=()=>!0,this.DEFAULT_CALCULATE_RECONNECT_DELAY=()=>this.serverReconnectionTime,this.reconnectAttempt=0,this.abortController=new AbortController,this._readyState=y.CLOSED,this.serverReconnectionTime=2e3,this.url=e.url.toString(),this.lastEventId=e.lastEventId,this.headers={Accept:`text/event-stream`,...e.headers},this.requestOptions={body:e.body,method:e.method??`GET`,mode:e.mode,credentials:e.credentials,redirect:e.redirect,referrer:e.referrer,referrerPolicy:e.referrerPolicy},this._fetch=e.fetch??((e,t)=>fetch(e,t)),this._onError=this.resolveCallback(e.onError,()=>{},`onError`),this._onConnect=this.resolveCallback(e.onConnect,()=>{},`onConnect`),this._onDisconnect=this.resolveCallback(e.onDisconnect,()=>{},`onDisconnect`),this._onMessage=this.resolveCallback(e.onMessage,()=>{},`onMessage`),this._onComment=this.resolveCallback(e.onComment,()=>{},`onComment`),this._shouldReconnect=this.resolveCallback(e.shouldReconnect,this.DEFAULT_SHOULD_RECONNECT,`shouldReconnect`),this._calculateReconnectDelay=this.resolveCallback(e.calculateReconnectDelay,this.DEFAULT_CALCULATE_RECONNECT_DELAY,`calculateReconnectDelay`)}set onError(e){this._onError=this.resolveCallback(e,()=>{},`onError`)}set onMessage(e){this._onMessage=this.resolveCallback(e,()=>{},`onMessage`)}set onComment(e){this._onComment=this.resolveCallback(e,()=>{},`onComment`)}set onConnect(e){this._onConnect=this.resolveCallback(e,()=>{},`onConnect`)}set onDisconnect(e){this._onDisconnect=this.resolveCallback(e,()=>{},`onDisconnect`)}set shouldReconnect(e){this._shouldReconnect=this.resolveCallback(e,this.DEFAULT_SHOULD_RECONNECT,`shouldReconnect`)}set calculateReconnectDelay(e){this._calculateReconnectDelay=this.resolveCallback(e,this.DEFAULT_CALCULATE_RECONNECT_DELAY,`shouldReconnect`)}get readyState(){return this._readyState}connect(){this._readyState===y.CLOSED&&(this.reconnectAttempt=0,this.initiateConnection())}close(){clearTimeout(this.reconnectTimeout),this._readyState=y.CLOSED,this.abortController.abort()}initiateConnection(){this.abortController=new AbortController,this._readyState=y.CONNECTING,this._fetch(this.url,{...this.requestOptions,headers:this.buildRequestHeaders(),signal:this.abortController.signal,cache:`no-store`}).then(e=>this.handleFetchResponse(e)).catch(e=>{if(e.name===`AbortError`||this.abortController.signal.aborted){this.close();return}this._onError(e),this.scheduleReconnect({error:e})})}async handleFetchResponse(e){let{body:t,status:n}=e;if(n===204){this.disconnected();return}if(n>=400){this.scheduleReconnect({status:n});return}if(!t)throw new ee(`The server response did not have a body`);this._readyState=y.OPEN,this._onConnect(),this.reconnectAttempt=0;let r=new v({onEvent:e=>{typeof e.id==`string`&&(this.lastEventId=e.id),this._onMessage(e)},onComment:e=>this._onComment(e),onRetry:e=>this.serverReconnectionTime=e});await this.readResponseStream(t,r),this.scheduleReconnect({status:n,error:new h(`The server response stream was closed`)})}async readResponseStream(e,t){let n=e.getReader(),r=new TextDecoder,i=!1;for(;!i;){let e=await n.read();e.value&&t.parse(r.decode(e.value,{stream:!0})),i=e.done}t.finish()}disconnected(){clearTimeout(this.reconnectTimeout),this._readyState=y.CLOSED,this._onDisconnect()}buildRequestHeaders(){let e=this.lastEventId?{"Last-Event-ID":this.lastEventId}:void 0;return{...this.headers,...e}}scheduleReconnect(e){if(this.abortController.signal.aborted)return;clearTimeout(this.reconnectTimeout),this.reconnectAttempt+=1;let t={attempt:this.reconnectAttempt,status:e.status,error:e.error,serverReconnectionTime:this.serverReconnectionTime};if(!this._shouldReconnect(t)){this.disconnected();return}this._readyState=y.CONNECTING;let n=this._calculateReconnectDelay(t);(!Number.isFinite(n)||n<1||n>1e3*60*60)&&(this._onError(new _(`The calculated reconnect delay is out of range: ${n}. Defaulting to ${this.serverReconnectionTime}`)),n=this.serverReconnectionTime),this.reconnectTimeout=setTimeout(()=>this.initiateConnection(),n)}resolveCallback(e,t,n){if(e==null)return t;if(typeof e!=`function`)throw new g(`${n} must be a function`);return e}};const x=e=>new b(e);var S=class{constructor(e){this.options=e,this.eventEmitter=new c.EventEmitter,this.options=e,this.logger=e.logger,this.url=new URL(`server/sse/v1`,e.baseUrl)}async connect(e){this.eventSource&&this.close();let t=new Promise((e,t)=>{this.eventSource=x({url:this.url,method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({metaContext:this.options.metaContext,serverSdkKey:this.options.serverSdkKey}),onMessage:({data:e})=>{this.dispatchMessage(e)},onConnect:()=>{this.logger.debug(`[StreamingTransport] Connected`),e(this)},shouldReconnect:e=>{let n=!this.isStatusFatal(e.status);return n||t(this.prepareFatalError(e.status,e.error)),n},calculateReconnectDelay:e=>{let t=2**Math.min(e.attempt,9)*1e3,n=`[StreamingTransport] Scheduling reconnect attempt #${e.attempt} in ${t}ms.`;return e.attempt<=5?this.logger.info(n):this.logger.warn(n),t},onDisconnect:()=>{this.logger.debug(`[StreamingTransport] Disconnected`)}}),this.eventSource.connect()});return Promise.race([t,new Promise(t=>{setTimeout(()=>t(this),e)})])}dispatchMessage(e){try{let t=JSON.parse(e);this.eventEmitter.emit(`configBundleReceived`,t)}catch(e){this.logger.error(`[StreamingTransport] Error parsing and dispatching config data update: `,e)}}prepareFatalError(e,t){let n=e??0;return new p(`${`Connection failed with status: ${e??`unknown`}.`}${t?` Error: ${t}.`:``} This is an unrecoverable error, will not attempt to reconnect.`,n)}isStatusFatal(e){return!!e&&e>=400&&e<500}get isConnected(){return this.eventSource?.readyState==`open`}on(e,t){this.eventEmitter.on(e,t)}off(e,t){t?this.eventEmitter.off(e,t):this.eventEmitter.removeAllListeners(e)}clear(){this.eventEmitter.removeAllListeners()}close(){this.eventSource?.close()}dispose(){this.close(),this.clear()}},C=class{constructor(){this.fatalError=!1,this.pollingIntervalSeconds=60}clearPollingInterval(){clearInterval(this.pollingInterval)}schedulePollingInterval(e){clearInterval(this.pollingInterval),!(this.pollingIntervalSeconds<=0)&&(this.pollingInterval=setInterval(e,this.pollingIntervalSeconds*1e3))}async handleNonOkResponse(e){if(!e.ok)throw this.isStatusFatal(e.status)?(this.fatalError=!0,this.prepareFatalResponseStatusError(e.status,await e.text())):new p(`Connection failed with status: ${e.status}`,e.status)}handleFetchError(e){throw this.isFetchErrorFatal(e)?(this.fatalError=!0,this.close(),new p(`Connection failed with fatal error: ${e}. This is an unrecoverable error, retry attempts will be ignored.`)):e instanceof SyntaxError?new p(`Failed to parse the response from the server: ${e}`):new p(`Connection failed with error: ${e}.`)}prepareFatalResponseStatusError(e,t){let n=e??0;return new p(`${`Connection failed with status: ${e??`unknown`}`}${(t?.trim()?.length??0)>0?` (${t})`:``}. This is an unrecoverable error, retry attempts will be ignored.`,n)}isStatusFatal(e){return!!e&&e>=400&&e<500}isFetchErrorFatal(e){return e?.name===`NotAllowedError`?!0:e instanceof TypeError}get isConnected(){return this.pollingInterval!==void 0}close(){clearInterval(this.pollingInterval),this.pollingInterval=void 0}dispose(){this.close(),this.clear()}};const w=async(e,t,n,r)=>{let i=new AbortController,a=setTimeout(()=>{r.debug(`[fetchWithTimeout] Reached timeout, aborting request`),i.abort()},e);try{let e=await fetch(typeof t==`string`||t instanceof Request?t:t.toString(),{...n,signal:i.signal});return clearTimeout(a),e}catch(e){throw r.warn(`[fetchWithTimeout] Fetch error: `,e),clearTimeout(a),e}};var T=class extends C{constructor(e){super(),this.options=e,this.eventEmitter=new c.EventEmitter,this.options=e,this.logger=e.logger,this.url=new URL(`server/polling/v1`,e.baseUrl),this.pollingIntervalSeconds=e.pollingInterval??60}async connect(e){return this.clearPollingInterval(),await this.fetchConfigs(e),this.schedulePollingInterval(()=>{this.fetchConfigs(e).catch(e=>{this.logger.error(`[PollingTransport] Error during polling:`,e)})}),this}async fetchConfigs(e){if(this.fatalError){this.logger.warn(`[PollingTransport] There was a prior unrecoverable error. Ignoring attempt to reconnect.`);return}try{let t=await w(e,this.url,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({metaContext:this.options.metaContext,serverSdkKey:this.options.serverSdkKey,lastUpdateTimestamp:this.lastUpdateTimestamp})},this.logger);if(await this.handleNonOkResponse(t),t.status===200){let e=JSON.parse(await t.text());this.lastUpdateTimestamp=e.timestamp,this.eventEmitter.emit(`configBundleReceived`,e)}}catch(e){this.handleFetchError(e)}}on(e,t){this.eventEmitter.on(e,t)}off(e,t){t?this.eventEmitter.off(e,t):this.eventEmitter.removeAllListeners(e)}clear(){this.eventEmitter.removeAllListeners()}},te=class extends T{constructor(e){super(e),this.pollingIntervalSeconds=0}};const E=(e,t,n)=>{switch(t.toLowerCase()){case`=`:case`equals`:return e==n[0];case`!=`:case`does not equal`:return e!=n[0];case`is one of`:return n.includes(e);case`is not one of`:return!n.includes(e);case`starts with any of`:return n.findIndex(t=>e?.startsWith(t))>=0;case`does not start with any of`:return n.findIndex(t=>e?.startsWith(t))<0;case`ends with any of`:return n.findIndex(t=>e?.endsWith(t))>=0;case`does not end with any of`:return n.findIndex(t=>e?.endsWith(t))<0;case`matches regex`:return D(n[0],e);case`does not match regex`:return!D(n[0],e);default:return!1}},D=(e,t)=>{try{return new RegExp(e).test(t)}catch{return!1}},O=(e,t,n)=>{if(typeof e==`boolean`)return!1;let r=t.toLowerCase();if(e==null)return r==`!=`||r==`does not equal`;let{value:i,target:a}=k(e,n[0]);switch(r){case`=`:case`equals`:return i==a;case`!=`:case`does not equal`:return i!=a;case`<`:return i<a;case`<=`:return i<=a;case`>`:return i>a;case`>=`:return i>=a;default:return!1}},k=(e,t)=>typeof e==`number`?{value:e,target:A(t)}:typeof e==`bigint`?{value:e,target:BigInt(t)}:{value:A(e),target:A(t)},A=e=>{let t=Number.parseFloat(e.toString());if(isNaN(t)||!isFinite(t))throw TypeError(`Invalid numeric value: ${e}`);return t},j=(e,t,n)=>{if(e==null)return!1;try{let r=new Date(e),i=new Date(n[0]);switch(t){case`is after`:return r>i;case`is before`:return r<i;default:return!1}}catch{return!1}},M=(e,t,n)=>{let r=t.toLowerCase();if(e==null||e.trim().length==0)return r==`is not one of`;let i=u.coerce(e)??e,a=n.map(e=>u.coerce(e)??e);try{switch(r){case`=`:return N(i,a[0]);case`<`:return u.lt(i,a[0]);case`<=`:return u.lte(i,a[0]);case`>`:return u.gt(i,a[0]);case`>=`:return u.gte(i,a[0]);case`is one of`:return a.findIndex(e=>N(i,e))>=0;case`is not one of`:return a.findIndex(e=>N(i,e))<0;default:return!1}}catch{return!1}},N=(e,t)=>{try{return u.eq(e,t)}catch{return!1}},P=(e,t,n)=>{let r=t.toLowerCase();if(!Array.isArray(e))return r==`does not contain any of`;switch(r){case`contains any of`:return e.some(e=>n.includes(e));case`does not contain any of`:return!e.some(e=>n.includes(e));default:return!1}};var F=class{evaluate(e,t){if(!this.isConditionValidForComparison(e))return!1;switch(e.attribute){case`identifier`:return this.compare(t?.context?.id,e);case`name`:return this.compare(t?.context?.name,e);case`appName`:return this.compare(t?.metadata?.appName,e);case`appVersion`:return this.compare(t?.metadata?.appVersion,e);case`traits`:if(e.trait){let n=this.findTraitValue(t?.context?.traits,e.trait);return this.compare(n,e)}}return!1}compare(e,t){switch(t.targetType){case`text`:return E(e?.toString(),t.operator,t.targetValues);case`number`:return O(e,t.operator,t.targetValues);case`datetime`:return j(e?.toString(),t.operator,t.targetValues);case`semver`:return M(e?.toString(),t.operator,t.targetValues);case`array`:return P(e,t.operator,t.targetValues)}return!1}findTraitValue(e,t){try{return(0,l.findByPointer)(t,e).val}catch{return}}isConditionValidForComparison(e){return Array.isArray(e.targetValues)&&e.targetValues.length>0}};const I=e=>{let t=`${e.contextIdentifier}-${e.configId}`;return Number((0,d.rapidhash)(t,{seed:15426857351n})%1000n)/10};var L=class{constructor(e){if(this.logger=e,this.conditionEvaluator=new F,!e||!e.warn||!e.error||!e.info)throw TypeError(`The provided logger is not a valid 'ConfigDirectorLogger'`);this.logger=e}evaluate(e,t){return{id:e.id,key:e.key,type:e.type,value:this.getConfigValue(e,t)}}getConfigValue(e,t){let n=[...e.target?.rules??[]].sort((e,t)=>(e.order??2**53-1)-(t.order??2**53-1));for(let r of n){let n=this.evaluateRule(r,e,t);if(n.success)return n.value}return e.target?.defaultValue}evaluateRule(e,t,n){try{if(e.type==`percentage`)return this.evaluatePercentageRule(e,t,n);if(e.type==`conditional`)return this.evaluateConditionalRule(e,t,n)}catch(n){this.logger.warn(`There was an error while evaluating a targeting rule '${e?.id}' for '${t?.key}'. The rule will be disregarded.`,{error:n,configKey:t?.key,ruleId:e?.id})}return{success:!1}}evaluatePercentageRule(e,t,n){return this.evaluatePercentage(e.percentages??[],t,n)}evaluatePercentage(e,t,n){let r=I({configId:t.id,contextIdentifier:n?.context?.id??crypto.randomUUID()}),i=0,a=0,o;for(;!o&&i<e.length;)r<=e[i].percentage+a&&(o=e[i]),a+=e[i].percentage,i+=1;return o?.value==null?{success:!1}:{success:!0,value:o.value.toString()}}evaluateConditionalRule(e,t,n){let r=e.conditions??[],i=0,a;for(;!a&&i<r.length;)this.conditionEvaluator.evaluate(r[i],n)&&(a=r[i]),i+=1;return a&&e.target==`value`&&e.value!=null?{success:!0,value:e.value.toString()}:a&&e.target==`percentage`?this.evaluatePercentage(e.percentages??[],t,n):{success:!1}}};const R=e=>{let t=typeof e;if(t===`object`)try{return e.constructor?.name??t}catch{return t}else if(t===`function`)try{let n=e.name;return n?`function: ${n}`:t}catch{return t}return t},z=e=>e===`number`||e===`bigint`,B=(e,t)=>{let n=e.value,r=R(t);if(!n)return{parsedValue:t,parsedValueId:void 0,requestedType:r,usedDefault:!0,reason:`value-missing`};if(e.type===`json`)return W(n,t,r,e.valueId);if(typeof t==`string`)return{parsedValue:n,parsedValueId:e.valueId,requestedType:r,usedDefault:!1,reason:`found-match`};if(typeof t==`boolean`&&(e.type===`boolean`||e.type===`string`)){let i=V(n),a=typeof i==`boolean`;return{parsedValue:a?i:t,parsedValueId:a?e.valueId:void 0,requestedType:r,usedDefault:!a,reason:a?`found-match`:`invalid-boolean`}}if(z(typeof t)&&e.type===`integer`){let i=H(n),a=typeof i==`number`;return{parsedValue:a?i:t,parsedValueId:a?e.valueId:void 0,requestedType:r,usedDefault:!a,reason:a?`found-match`:`invalid-number`}}if(z(typeof t)&&(e.type===`float`||e.type===`enum`||e.type===`string`)){let i=U(n),a=typeof i==`number`;return{parsedValue:a?i:t,parsedValueId:a?e.valueId:void 0,requestedType:r,usedDefault:!a,reason:a?`found-match`:`invalid-number`}}return{parsedValue:n,parsedValueId:e.valueId,requestedType:r,usedDefault:!1,reason:`found-match`}},V=e=>{if(!e)return;let t=e.toLowerCase();if(!(t!=`true`&&t!=`false`))return t===`true`},H=e=>{if(!e)return;let t=Number.parseInt(e);if(!(isNaN(t)||!isFinite(t)))return t},U=e=>{if(!e)return;let t=Number.parseFloat(e);if(!(isNaN(t)||!isFinite(t)))return t},W=(e,t,n,r)=>{if(typeof t==`string`)return{parsedValue:e,parsedValueId:r,requestedType:n,usedDefault:!1,reason:`found-match`};try{return{parsedValue:JSON.parse(e),parsedValueId:r,requestedType:n,usedDefault:!1,reason:`found-match`}}catch{return{parsedValue:t,parsedValueId:void 0,requestedType:n,usedDefault:!0,reason:`invalid-json`}}},G={off:-1,error:0,warn:1,info:2,debug:3},ne=()=>{let e={year:`numeric`,month:`2-digit`,day:`2-digit`,hour:`2-digit`,minute:`2-digit`,second:`2-digit`,timeZoneName:`short`};try{return new Intl.DateTimeFormat(void 0,{...e,fractionalSecondDigits:3})}catch{return new Intl.DateTimeFormat(void 0,e)}};var K=class{constructor(e,t){this.level=e,this.decorator=t,this.level=e,this.decorator=t,this.dateFormatter=ne()}debug(e,...t){this.log(console.debug,`debug`,e,...t)}info(e,...t){this.log(console.info,`info`,e,...t)}warn(e,...t){this.log(console.warn,`warn`,e,...t)}error(e,...t){this.log(console.error,`error`,e,...t)}log(e,t,n,...r){G[this.level]>=G[t]&&e(`[${this.dateFormatter.format(new Date)}] ${this.decorator?.decorateMessage(n)}`,...r)}},q=class{decorateMessage(e){return`[ConfigDirector:js-server-sdk] ${e}`}};const J=(e,t)=>new K(e??`warn`,t??new q),Y=(e,t)=>new URL(e,t?.toString()),X=async(e,t)=>e.valueId?{valueId:e.valueId}:e.value?e.value.length<=500&&e.type!=`json`?{value:e.value}:{valueId:await t(e.value)}:e,Z=e=>{if(e.type===`json`||e.type==null&&typeof e.value==`object`){if(e.valueId)return{valueId:e.valueId,type:`json`};try{return{value:JSON.stringify(e.value),type:`json`}}catch{return{value:e.value.toString(),type:`json`}}}let t=e.value?.toString();return t&&t.length<=500?{value:t}:e.valueId?{valueId:e.valueId}:{value:t}},Q=e=>{let t=Object.keys(e);if(t.length==0)return!0;for(let n of t)if((e[n]?.length??0)>0)return!1;return!0},re=e=>{if(!e)return!0;let t=Object.keys(e);if(t.length==0)return!0;for(let n of t)if((e[n]??0)>0)return!1;return!0};var ie=class{constructor(e){this.executeRequests=!0,this.sdkKey=e.sdkKey,this.logger=e.logger,this.url=new URL(`server/telemetry/v1`,e.baseUrl.toString())}async report({discreteEvents:e,aggregatedEvents:t,droppedEvents:n}){if(!this.executeRequests)return{success:!1,fatalError:!0};let r={serverSdkKey:this.sdkKey,discreteEvents:e,aggregatedEvents:t,droppedEvents:n};if(this.isReportEmpty(r))return{success:!0,fatalError:!1};let i=await this.sendReport(r);return i.fatalError&&(this.executeRequests=!1),i}isReportEmpty(e){return Q(e.discreteEvents)&&Q(e.aggregatedEvents)&&re(e.droppedEvents)}async sendReport(e){try{let t=await w(5e3,this.url,{method:`POST`,body:JSON.stringify(e)},this.logger),n=this.isStatusFatal(t.status);return n&&this.logger.warn(`[EventReporter] Received a fatal status response (${t.status}) from the telemetry endpoint. No more telemetry data will be sent.`),{success:t.ok,fatalError:n}}catch(e){return this.logger.warn(`[EventReporter] Error attempting to send telemetry data: ${e}`,{error:e}),{success:!1,fatalError:!1}}}isStatusFatal(e){return!!e&&e>=400&&e<500}},ae=class{aggregate(e){if(e.events.length==0)return[];let t=new Map;for(let n of e.events){let e=JSON.stringify(n),r=t.get(e)?.count||0;t.set(e,{event:n,count:r+1})}return Array.from(t).map(([,t])=>({startTime:e.startTime,endTime:e.endTime,count:t.count,event:t.event}))}},oe=class{constructor(e){this._events=[],this._droppedEventCount=0,this.limit=e??1e3}get events(){return this._events}get reachedLimit(){return this._events.length>=this.limit}get droppedEventCount(){return this._droppedEventCount}push(...e){this.startTime||=new Date;let t=Math.max(0,e.length-this.limit),n=e.slice(t,e.length);if(this._droppedEventCount+=t,this._events.length+n.length>this.limit){let e=this._events.length+n.length-this.limit;this._events.splice(0,e),this._droppedEventCount+=e}return this._events.push(...n)}takeSnapshot(){let e=new Date,t=this.startTime??e,n=this._events.splice(0),r=this._droppedEventCount;return this.startTime=void 0,this._droppedEventCount=0,{startTime:t,endTime:e,events:n,droppedCount:r}}clear(){this._events=[],this.startTime=void 0,this._droppedEventCount=0}},$=class{constructor(e){this.aggregator=new ae,this.collectEvents=!0,this.logger=e.logger,this.evaluationEventQueue=new oe(e.evaluationQueueLimit??1e3),this.flushIntervalDelay=e.flushIntervalDelay??3e4;let t=e.initialFlushIntervalDelay??5e3;this.flushTimeout=setTimeout(()=>this.flushAndScheduleNext(),t)}async flushAndScheduleNext(){(await this.flush()).fatalError?(this.collectEvents=!1,this.close(),this.logger.warn(`[TelemetryEventCollector] Received a fatal error from telemetry collection. No longer collecting events.`)):this.flushTimeout=setTimeout(()=>this.flushAndScheduleNext(),this.flushIntervalDelay)}cancelScheduledFlush(){clearTimeout(this.flushTimeout)}async close(){this.collectEvents=!1,clearTimeout(this.flushTimeout),await this.flush(),this.evaluationEventQueue.clear()}dispose(){this.close()}},se=class extends Map{constructor(e,t){super(t),this._droppedCount=0,this.limit=e,this.enforceLimit()}set(e,t){return super.set(e,t),this.enforceLimit(),this}get droppedCount(){return this._droppedCount}clearAndReset(){this.clear(),this._droppedCount=0}enforceLimit(){for(;this.size>this.limit;){let e=this.keys().next().value;e!==void 0&&(this.delete(e),this._droppedCount+=1)}}},ce=class{constructor(e){this.valueIdGenerator=e,this.valueIdGenerator=e}async process(e){let t=await Promise.all(e.events.map(async e=>({...e,defaultValue:await X(e.defaultValue,this.valueIdGenerator),evaluatedValue:await X(e.evaluatedValue,this.valueIdGenerator)})));return{...e,events:t}}},le=class extends ${constructor(e){super(e),this.reporter=new ie(e),this.contexts=new se(e.contextLimit??1e3),this.evaluationEventSnapshotPreprocessor=new ce(e.valueIdGenerator)}evaluatedConfig(e){if(!this.collectEvents)return;e.context?.id&&!e.context.anonymous&&this.contexts.set(e.context.id,e.context);let t={...e.evaluation,contextId:e.context?.anonymous?void 0:e.context?.id};this.evaluationEventQueue.push(this.sanitizeEvaluatedConfigEvent(t))}sanitizeEvaluatedConfigEvent(e){return{...e,defaultValue:Z({value:e.defaultValue,type:e.type}),evaluatedValue:Z({value:e.evaluatedValue,valueId:e.evaluatedValueId,type:e.type})}}snapshotContexts(){let e=Array.from(this.contexts.values()),t=this.contexts.droppedCount;return this.contexts.clearAndReset(),{contexts:e,droppedCount:t}}async flush(){if(!this.reporter)return{success:!1,fatalError:!1};let e=await this.evaluationEventSnapshotPreprocessor.process(this.evaluationEventQueue.takeSnapshot()),t=this.snapshotContexts();return await this.reporter?.report({context:this._context,discreteEvents:{capturedContexts:t.contexts},aggregatedEvents:{evaluatedConfig:this.aggregator.aggregate(e)},droppedEvents:{evaluatedConfig:e.droppedCount,capturedContexts:t.droppedCount}})}};const ue=(e,t)=>{let n=BigInt(0),r=BigInt(62),i=Array.from(e).map(e=>e.toString(16).padStart(2,`0`)).join(``),a=BigInt(`0x`+i),o=``;for(;a>n;)o=`0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz`[Number(a%r)]+o,a/=r;return o.padStart(t,`0`)},de=Math.ceil(128/Math.log2(62)),fe=async e=>ue((0,f.createHash)(`sha256`).update(e?.toString()??``).digest().subarray(0,16),de),pe=new URL(`https://server-sdk-api.configdirector.com`);var me=class{constructor(e,t,n){this.handlersMap=new Map,this.eventEmitter=new c.default,this.ready=!1,this.logger=n?.logger??J(),this.timeout=n?.connection?.timeout??3e3,this.metaContext=n?.metadata??{},this.validateSdkKeyPresence(e),this.configEvaluator=new L(this.logger);let r=this.parseUrl(n?.connection?.url)??pe;this.connectionMode=n?.connection?.mode??`streaming`;let i=this.getTransportConstructor(this.connectionMode),a=n?.telemetry?.eventQueueLimit??5e3;this.usageEventCollector=new le({sdkKey:e,logger:this.logger,baseUrl:r,flushIntervalDelay:n?.telemetry?.flushInterval??3e4,evaluationQueueLimit:Math.ceil(a*.7),contextLimit:Math.floor(a*.3),urlFactory:Y,valueIdGenerator:fe}),this.transport=new i({serverSdkKey:e,baseUrl:r,metaContext:{...n?.metadata,sdkName:t.sdkName,sdkVersion:t.sdkVersion},logger:this.logger,pollingInterval:n?.connection?.pollingInterval}),this.transport.on(`configBundleReceived`,e=>{this.readyResolve?.();let t=Object.keys(e.configs);!this.configSet||e.kind==`full`?(this.configSet=e,this.eventEmitter.emit(`configsUpdated`,{keys:t}),this.updateWatchers(e.configs)):(this.configSet.configs={...this.configSet.configs,...e.configs},this.eventEmitter.emit(`configsUpdated`,{keys:t}),this.updateWatchers(e.configs)),this.logger.debug(`[ConfigDirectorClient] ConfigSet updated from server with ${t.length} key(s)`,{keys:t})}),this.registerHandler(`clientReady`,n?.hooks?.clientReady),this.registerHandler(`configEvaluated`,n?.hooks?.configEvaluated),this.registerHandler(`configsUpdated`,n?.hooks?.configsUpdated)}registerHandler(e,t){if(!t)return;let n=Array.isArray(t)?t:[t];for(let t of n)typeof t==`function`&&this.on(e,t)}async initialize(){try{this.ready=!1,this.readyPromise=new Promise(e=>{this.readyResolve=e}).then(()=>{this.ready=!0,this.eventEmitter.emit(`clientReady`),this.logger.debug(`[ConfigDirectorClient] Received initial payload from the server, client is ready`)});let e=new Date().getTime();await this.transport.connect(this.timeout);let t=new Date().getTime()-e,n=this.timeout-t;if(n>0&&await Promise.race([this.readyPromise,new Promise(e=>{setTimeout(()=>e(),n)})]),!this.ready){let e=this.connectionMode===`streaming`?`The client will continue to retry since there were no fatal errors detected. Configs will return the default value until the connection succeeds.`:`Since the client was configured without streaming, configs may not update and always return the default value.`;this.logger.warn(`[ConfigDirectorClient] Timed out waiting for initialization after ${this.timeout}ms. ${e}`)}}catch(e){this.logger.error(`[ConfigDirectorClient] An error occurred during initialization: `,e)}}getTransportConstructor(e){switch(e){case`one-time`:return te;case`polling`:return T;default:return S}}updateWatchers(e){Object.values(e).forEach(e=>this.updateWatchersForConfig(e))}updateWatchersForConfig(e){this.handlersMap.get(e.key)?.forEach(t=>{let n=this.getValueFromConfigDefinition(e.key,e,t.defaultValue,t.context);t.handler(n)})}watch(e,t,n,r){this.validateDefaultValue(t);let i=this.handlersMap.get(e),a={handler:n,defaultValue:t,requestedType:typeof t,context:r};return i?i.push(a):this.handlersMap.set(e,[a]),()=>this.unwatch(e,n)}unwatch(e,t){let n=this.handlersMap.get(e);if(n)if(t){let e=n.findIndex(e=>e.handler==t);e>=0&&n?.splice(e,1)}else n.splice(0)}getValue(e,t,n){this.validateDefaultValue(t);let r=this.configSet?.configs[e];return this.getValueFromConfigDefinition(e,r,t,n)}getValueFromConfigDefinition(e,t,n,r){if(t){let i=this.configEvaluator.evaluate(t,{context:r,metadata:this.metaContext}),a=B(i,n);return this.usageEventCollector.evaluatedConfig({context:r,evaluation:{key:e,type:i.type,defaultValue:n,requestedType:a.requestedType,evaluatedValue:a.parsedValue,evaluatedValueId:a.parsedValueId,usedDefault:a.usedDefault,evaluationReason:a.reason}}),this.dispatchEvaluationEvent({key:e,value:a.parsedValue,valueId:a.parsedValueId??void 0,isDefaultValue:a.usedDefault,reason:a.reason,context:r}),this.logger.debug(`[ConfigDirectorClient] Evaluated '${e}' to '${a.parsedValue}'`),a.parsedValue}else{this.logger.debug(`[ConfigDirectorClient] No config state found for '${e}', returning default value '${n}'`);let t=this.isReady?`config-state-missing`:`client-not-ready`;return this.usageEventCollector.evaluatedConfig({context:r,evaluation:{key:e,defaultValue:n,requestedType:R(n),evaluatedValue:n,usedDefault:!0,evaluationReason:t}}),this.dispatchEvaluationEvent({key:e,value:n,isDefaultValue:!0,reason:t,context:r}),n}}dispatchEvaluationEvent(e){setTimeout(()=>this.eventEmitter.emit(`configEvaluated`,{evaluation:e}),0)}parseUrl(e){if(e)try{return new URL(e)}catch(t){throw new m(`Invalid base URL '${e}'. Parsing failed: ${t}`)}}validateSdkKeyPresence(e){if(!e||e.trim().length==0)throw new m(`No server SDK key was provided, the client cannot be instantiated without a valid server SDK key`)}validateDefaultValue(e){if(e==null)throw new m(`Invalid default value. The default value for a config must be defined and non-null.`);if(typeof e==`function`)throw new m(`Invalid default value. The default value for a config cannot be a function.`)}get isReady(){return this.ready}on(e,t){this.eventEmitter.on(e,t)}off(e,t){t?this.eventEmitter.off(e,t):this.eventEmitter.removeAllListeners(e)}removeAllObservers(){this.logger.debug(`[ConfigDirectorClient] removeAllObservers() has been called, removing all observers`),this.eventEmitter.removeAllListeners(),this.handlersMap.clear()}unwatchAll(){this.handlersMap.clear()}getAllConfigs(e){if(!this.configSet)return{};let t=Object.entries(this.configSet.configs),n=e?.configKeys?t.filter(([t])=>e.configKeys?.includes(t)):t;return Object.fromEntries(n.map(([t,n])=>[t,this.configEvaluator.evaluate(n,{context:e?.context,metadata:this.metaContext})]))}closeConnection(){this.logger.debug(`[ConfigDirectorClient] closeConnection() has been called, closing connection to server`),this.transport.close(),this.ready=!1}dispose(){this.removeAllObservers(),this.closeConnection()}};const he=(e,t)=>new me(e,{sdkName:`js-server-sdk`,sdkVersion:`0.9.1`},t),ge=(e,t)=>J(e,t);exports.createClient=he,exports.createConsoleLogger=ge;
|
|
5
|
+
`;break;case`id`:e.value.includes(`\0`)||(this.lastEventId=e.value);break;case`retry`:/^\d+$/.test(e.value)&&this.onRetry?.(Number.parseInt(e.value,10));break;default:break}}parseField(e){let t=e.indexOf(`:`);return t===-1?{field:e,value:``}:{field:e.slice(0,t),value:this.extractValue(e,t+1)}}extractValue(e,t){return e[t]===` `?e.slice(t+1):e.slice(t)}};let y=function(e){return e.OPEN=`open`,e.CLOSED=`closed`,e.CONNECTING=`connecting`,e}({});var b=class{constructor(e){this.DEFAULT_SHOULD_RECONNECT=()=>!0,this.DEFAULT_CALCULATE_RECONNECT_DELAY=()=>this.serverReconnectionTime,this.reconnectAttempt=0,this.abortController=new AbortController,this._readyState=y.CLOSED,this.serverReconnectionTime=2e3,this.url=e.url.toString(),this.lastEventId=e.lastEventId,this.headers={Accept:`text/event-stream`,...e.headers},this.requestOptions={body:e.body,method:e.method??`GET`,mode:e.mode,credentials:e.credentials,redirect:e.redirect,referrer:e.referrer,referrerPolicy:e.referrerPolicy},this._fetch=e.fetch??((e,t)=>fetch(e,t)),this._onError=this.resolveCallback(e.onError,()=>{},`onError`),this._onConnect=this.resolveCallback(e.onConnect,()=>{},`onConnect`),this._onDisconnect=this.resolveCallback(e.onDisconnect,()=>{},`onDisconnect`),this._onMessage=this.resolveCallback(e.onMessage,()=>{},`onMessage`),this._onComment=this.resolveCallback(e.onComment,()=>{},`onComment`),this._shouldReconnect=this.resolveCallback(e.shouldReconnect,this.DEFAULT_SHOULD_RECONNECT,`shouldReconnect`),this._calculateReconnectDelay=this.resolveCallback(e.calculateReconnectDelay,this.DEFAULT_CALCULATE_RECONNECT_DELAY,`calculateReconnectDelay`)}set onError(e){this._onError=this.resolveCallback(e,()=>{},`onError`)}set onMessage(e){this._onMessage=this.resolveCallback(e,()=>{},`onMessage`)}set onComment(e){this._onComment=this.resolveCallback(e,()=>{},`onComment`)}set onConnect(e){this._onConnect=this.resolveCallback(e,()=>{},`onConnect`)}set onDisconnect(e){this._onDisconnect=this.resolveCallback(e,()=>{},`onDisconnect`)}set shouldReconnect(e){this._shouldReconnect=this.resolveCallback(e,this.DEFAULT_SHOULD_RECONNECT,`shouldReconnect`)}set calculateReconnectDelay(e){this._calculateReconnectDelay=this.resolveCallback(e,this.DEFAULT_CALCULATE_RECONNECT_DELAY,`shouldReconnect`)}get readyState(){return this._readyState}connect(){this._readyState===y.CLOSED&&(this.reconnectAttempt=0,this.initiateConnection())}close(){clearTimeout(this.reconnectTimeout),this._readyState=y.CLOSED,this.abortController.abort()}initiateConnection(){this.abortController=new AbortController,this._readyState=y.CONNECTING,this._fetch(this.url,{...this.requestOptions,headers:this.buildRequestHeaders(),signal:this.abortController.signal,cache:`no-store`}).then(e=>this.handleFetchResponse(e)).catch(e=>{if(e.name===`AbortError`||this.abortController.signal.aborted){this.close();return}this._onError(e),this.scheduleReconnect({error:e})})}async handleFetchResponse(e){let{body:t,status:n}=e;if(n===204){this.disconnected();return}if(n>=400){this.scheduleReconnect({status:n});return}if(!t)throw new ee(`The server response did not have a body`);this._readyState=y.OPEN,this._onConnect(),this.reconnectAttempt=0;let r=new v({onEvent:e=>{typeof e.id==`string`&&(this.lastEventId=e.id),this._onMessage(e)},onComment:e=>this._onComment(e),onRetry:e=>this.serverReconnectionTime=e});await this.readResponseStream(t,r),this.scheduleReconnect({status:n,error:new h(`The server response stream was closed`)})}async readResponseStream(e,t){let n=e.getReader(),r=new TextDecoder,i=!1;for(;!i;){let e=await n.read();e.value&&t.parse(r.decode(e.value,{stream:!0})),i=e.done}t.finish()}disconnected(){clearTimeout(this.reconnectTimeout),this._readyState=y.CLOSED,this._onDisconnect()}buildRequestHeaders(){let e=this.lastEventId?{"Last-Event-ID":this.lastEventId}:void 0;return{...this.headers,...e}}scheduleReconnect(e){if(this.abortController.signal.aborted)return;clearTimeout(this.reconnectTimeout),this.reconnectAttempt+=1;let t={attempt:this.reconnectAttempt,status:e.status,error:e.error,serverReconnectionTime:this.serverReconnectionTime};if(!this._shouldReconnect(t)){this.disconnected();return}this._readyState=y.CONNECTING;let n=this._calculateReconnectDelay(t);(!Number.isFinite(n)||n<1||n>1e3*60*60)&&(this._onError(new _(`The calculated reconnect delay is out of range: ${n}. Defaulting to ${this.serverReconnectionTime}`)),n=this.serverReconnectionTime),this.reconnectTimeout=setTimeout(()=>this.initiateConnection(),n)}resolveCallback(e,t,n){if(e==null)return t;if(typeof e!=`function`)throw new g(`${n} must be a function`);return e}};const x=e=>new b(e);var S=class{constructor(e){this.options=e,this.eventEmitter=new c.EventEmitter,this.options=e,this.logger=e.logger,this.url=new URL(`server/sse/v1`,e.baseUrl)}async connect(e){this.eventSource&&this.close();let t=new Promise((e,t)=>{this.eventSource=x({url:this.url,method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({metaContext:this.options.metaContext,serverSdkKey:this.options.serverSdkKey}),onMessage:({data:e})=>{this.dispatchMessage(e)},onConnect:()=>{this.logger.debug(`[StreamingTransport] Connected`),e(this)},shouldReconnect:e=>{let n=!this.isStatusFatal(e.status);return n||t(this.prepareFatalError(e.status,e.error)),n},calculateReconnectDelay:e=>{let t=2**Math.min(e.attempt,9)*1e3,n=`[StreamingTransport] Scheduling reconnect attempt #${e.attempt} in ${t}ms.`;return e.attempt<=5?this.logger.info(n):this.logger.warn(n),t},onDisconnect:()=>{this.logger.debug(`[StreamingTransport] Disconnected`)}}),this.eventSource.connect()});return Promise.race([t,new Promise(t=>{setTimeout(()=>t(this),e)})])}dispatchMessage(e){try{let t=JSON.parse(e);this.eventEmitter.emit(`configBundleReceived`,t)}catch(e){this.logger.error(`[StreamingTransport] Error parsing and dispatching config data update: `,e)}}prepareFatalError(e,t){let n=e??0;return new p(`${`Connection failed with status: ${e??`unknown`}.`}${t?` Error: ${t}.`:``} This is an unrecoverable error, will not attempt to reconnect.`,n)}isStatusFatal(e){return!!e&&e>=400&&e<500}get isConnected(){return this.eventSource?.readyState==`open`}on(e,t){this.eventEmitter.on(e,t)}off(e,t){t?this.eventEmitter.off(e,t):this.eventEmitter.removeAllListeners(e)}clear(){this.eventEmitter.removeAllListeners()}close(){this.eventSource?.close()}dispose(){this.close(),this.clear()}},C=class{constructor(){this.fatalError=!1,this.pollingIntervalSeconds=60}clearPollingInterval(){clearInterval(this.pollingInterval)}schedulePollingInterval(e){clearInterval(this.pollingInterval),!(this.pollingIntervalSeconds<=0)&&(this.pollingInterval=setInterval(e,this.pollingIntervalSeconds*1e3))}async handleNonOkResponse(e){if(!e.ok)throw this.isStatusFatal(e.status)?(this.fatalError=!0,this.prepareFatalResponseStatusError(e.status,await e.text())):new p(`Connection failed with status: ${e.status}`,e.status)}handleFetchError(e){throw this.isFetchErrorFatal(e)?(this.fatalError=!0,this.close(),new p(`Connection failed with fatal error: ${e}. This is an unrecoverable error, retry attempts will be ignored.`)):e instanceof SyntaxError?new p(`Failed to parse the response from the server: ${e}`):new p(`Connection failed with error: ${e}.`)}prepareFatalResponseStatusError(e,t){let n=e??0;return new p(`${`Connection failed with status: ${e??`unknown`}`}${(t?.trim()?.length??0)>0?` (${t})`:``}. This is an unrecoverable error, retry attempts will be ignored.`,n)}isStatusFatal(e){return!!e&&e>=400&&e<500}isFetchErrorFatal(e){return e?.name===`NotAllowedError`?!0:e instanceof TypeError}get isConnected(){return this.pollingInterval!==void 0}close(){clearInterval(this.pollingInterval),this.pollingInterval=void 0}dispose(){this.close(),this.clear()}};const w=async(e,t,n,r)=>{let i=new AbortController,a=setTimeout(()=>{r.debug(`[fetchWithTimeout] Reached timeout, aborting request`),i.abort()},e);try{let e=await fetch(typeof t==`string`||t instanceof Request?t:t.toString(),{...n,signal:i.signal});return clearTimeout(a),e}catch(e){throw r.warn(`[fetchWithTimeout] Fetch error: `,e),clearTimeout(a),e}};var T=class extends C{constructor(e){super(),this.options=e,this.eventEmitter=new c.EventEmitter,this.options=e,this.logger=e.logger,this.url=new URL(`server/polling/v1`,e.baseUrl),this.pollingIntervalSeconds=e.pollingInterval??60}async connect(e){return this.clearPollingInterval(),await this.fetchConfigs(e),this.schedulePollingInterval(()=>{this.fetchConfigs(e).catch(e=>{this.logger.error(`[PollingTransport] Error during polling:`,e)})}),this}async fetchConfigs(e){if(this.fatalError){this.logger.warn(`[PollingTransport] There was a prior unrecoverable error. Ignoring attempt to reconnect.`);return}try{let t=await w(e,this.url,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({metaContext:this.options.metaContext,serverSdkKey:this.options.serverSdkKey,lastUpdateTimestamp:this.lastUpdateTimestamp})},this.logger);if(await this.handleNonOkResponse(t),t.status===200){let e=JSON.parse(await t.text());this.lastUpdateTimestamp=e.timestamp,this.eventEmitter.emit(`configBundleReceived`,e)}}catch(e){this.handleFetchError(e)}}on(e,t){this.eventEmitter.on(e,t)}off(e,t){t?this.eventEmitter.off(e,t):this.eventEmitter.removeAllListeners(e)}clear(){this.eventEmitter.removeAllListeners()}},te=class extends T{constructor(e){super(e),this.pollingIntervalSeconds=0}};const E=(e,t,n)=>{switch(t.toLowerCase()){case`=`:case`equals`:return e==n[0];case`!=`:case`does not equal`:return e!=n[0];case`is one of`:return n.includes(e);case`is not one of`:return!n.includes(e);case`starts with any of`:return n.findIndex(t=>e?.startsWith(t))>=0;case`does not start with any of`:return n.findIndex(t=>e?.startsWith(t))<0;case`ends with any of`:return n.findIndex(t=>e?.endsWith(t))>=0;case`does not end with any of`:return n.findIndex(t=>e?.endsWith(t))<0;case`matches regex`:return D(n[0],e);case`does not match regex`:return!D(n[0],e);default:return!1}},D=(e,t)=>{try{return new RegExp(e).test(t)}catch{return!1}},O=(e,t,n)=>{if(typeof e==`boolean`)return!1;let r=t.toLowerCase();if(e==null)return r==`!=`||r==`does not equal`;let{value:i,target:a}=k(e,n[0]);switch(r){case`=`:case`equals`:return i==a;case`!=`:case`does not equal`:return i!=a;case`<`:return i<a;case`<=`:return i<=a;case`>`:return i>a;case`>=`:return i>=a;default:return!1}},k=(e,t)=>typeof e==`number`?{value:e,target:A(t)}:typeof e==`bigint`?{value:e,target:BigInt(t)}:{value:A(e),target:A(t)},A=e=>{let t=Number.parseFloat(e.toString());if(isNaN(t)||!isFinite(t))throw TypeError(`Invalid numeric value: ${e}`);return t},j=(e,t,n)=>{if(e==null)return!1;try{let r=new Date(e),i=new Date(n[0]);switch(t){case`is after`:return r>i;case`is before`:return r<i;default:return!1}}catch{return!1}},M=(e,t,n)=>{let r=t.toLowerCase();if(e==null||e.trim().length==0)return r==`is not one of`;let i=u.coerce(e)??e,a=n.map(e=>u.coerce(e)??e);try{switch(r){case`=`:return N(i,a[0]);case`<`:return u.lt(i,a[0]);case`<=`:return u.lte(i,a[0]);case`>`:return u.gt(i,a[0]);case`>=`:return u.gte(i,a[0]);case`is one of`:return a.findIndex(e=>N(i,e))>=0;case`is not one of`:return a.findIndex(e=>N(i,e))<0;default:return!1}}catch{return!1}},N=(e,t)=>{try{return u.eq(e,t)}catch{return!1}},P=(e,t,n)=>{let r=t.toLowerCase();if(!Array.isArray(e))return r==`does not contain any of`;switch(r){case`contains any of`:return e.some(e=>n.includes(e));case`does not contain any of`:return!e.some(e=>n.includes(e));default:return!1}};var F=class{evaluate(e,t){if(!this.isConditionValidForComparison(e))return!1;switch(e.attribute){case`identifier`:return this.compare(t?.context?.id,e);case`name`:return this.compare(t?.context?.name,e);case`appName`:return this.compare(t?.metadata?.appName,e);case`appVersion`:return this.compare(t?.metadata?.appVersion,e);case`traits`:if(e.trait){let n=this.findTraitValue(t?.context?.traits,e.trait);return this.compare(n,e)}}return!1}compare(e,t){switch(t.targetType){case`text`:return E(e?.toString(),t.operator,t.targetValues);case`number`:return O(e,t.operator,t.targetValues);case`datetime`:return j(e?.toString(),t.operator,t.targetValues);case`semver`:return M(e?.toString(),t.operator,t.targetValues);case`array`:return P(e,t.operator,t.targetValues)}return!1}findTraitValue(e,t){try{return(0,l.findByPointer)(t,e).val}catch{return}}isConditionValidForComparison(e){return Array.isArray(e.targetValues)&&e.targetValues.length>0}};const I=e=>{let t=`${e.contextIdentifier}-${e.configId}`;return Number((0,d.rapidhash)(t,{seed:15426857351n})%1000n)/10};var L=class{constructor(e){if(this.logger=e,this.conditionEvaluator=new F,!e||!e.warn||!e.error||!e.info)throw TypeError(`The provided logger is not a valid 'ConfigDirectorLogger'`);this.logger=e}evaluate(e,t){return{id:e.id,key:e.key,type:e.type,value:this.getConfigValue(e,t)}}getConfigValue(e,t){let n=[...e.target?.rules??[]].sort((e,t)=>(e.order??2**53-1)-(t.order??2**53-1));for(let r of n){let n=this.evaluateRule(r,e,t);if(n.success)return n.value}return e.target?.defaultValue}evaluateRule(e,t,n){try{if(e.type==`percentage`)return this.evaluatePercentageRule(e,t,n);if(e.type==`conditional`)return this.evaluateConditionalRule(e,t,n)}catch(n){this.logger.warn(`There was an error while evaluating a targeting rule '${e?.id}' for '${t?.key}'. The rule will be disregarded.`,{error:n,configKey:t?.key,ruleId:e?.id})}return{success:!1}}evaluatePercentageRule(e,t,n){return this.evaluatePercentage(e.percentages??[],t,n)}evaluatePercentage(e,t,n){let r=I({configId:t.id,contextIdentifier:n?.context?.id??crypto.randomUUID()}),i=0,a=0,o;for(;!o&&i<e.length;)r<=e[i].percentage+a&&(o=e[i]),a+=e[i].percentage,i+=1;return o?.value==null?{success:!1}:{success:!0,value:o.value.toString()}}evaluateConditionalRule(e,t,n){let r=e.conditions??[],i=0,a;for(;!a&&i<r.length;)this.conditionEvaluator.evaluate(r[i],n)&&(a=r[i]),i+=1;return a&&e.target==`value`&&e.value!=null?{success:!0,value:e.value.toString()}:a&&e.target==`percentage`?this.evaluatePercentage(e.percentages??[],t,n):{success:!1}}};const R=e=>{let t=typeof e;if(t===`object`)try{return e.constructor?.name??t}catch{return t}else if(t===`function`)try{let n=e.name;return n?`function: ${n}`:t}catch{return t}return t},z=e=>e===`number`||e===`bigint`,B=(e,t)=>{let n=e.value,r=R(t);if(!n)return{parsedValue:t,parsedValueId:void 0,requestedType:r,usedDefault:!0,reason:`value-missing`};if(e.type===`json`)return W(n,t,r,e.valueId);if(typeof t==`string`)return{parsedValue:n,parsedValueId:e.valueId,requestedType:r,usedDefault:!1,reason:`found-match`};if(typeof t==`boolean`&&(e.type===`boolean`||e.type===`string`)){let i=V(n),a=typeof i==`boolean`;return{parsedValue:a?i:t,parsedValueId:a?e.valueId:void 0,requestedType:r,usedDefault:!a,reason:a?`found-match`:`invalid-boolean`}}if(z(typeof t)&&e.type===`integer`){let i=H(n),a=typeof i==`number`;return{parsedValue:a?i:t,parsedValueId:a?e.valueId:void 0,requestedType:r,usedDefault:!a,reason:a?`found-match`:`invalid-number`}}if(z(typeof t)&&(e.type===`float`||e.type===`enum`||e.type===`string`)){let i=U(n),a=typeof i==`number`;return{parsedValue:a?i:t,parsedValueId:a?e.valueId:void 0,requestedType:r,usedDefault:!a,reason:a?`found-match`:`invalid-number`}}return{parsedValue:n,parsedValueId:e.valueId,requestedType:r,usedDefault:!1,reason:`found-match`}},V=e=>{if(!e)return;let t=e.toLowerCase();if(!(t!=`true`&&t!=`false`))return t===`true`},H=e=>{if(!e)return;let t=Number.parseInt(e);if(!(isNaN(t)||!isFinite(t)))return t},U=e=>{if(!e)return;let t=Number.parseFloat(e);if(!(isNaN(t)||!isFinite(t)))return t},W=(e,t,n,r)=>{if(typeof t==`string`)return{parsedValue:e,parsedValueId:r,requestedType:n,usedDefault:!1,reason:`found-match`};try{return{parsedValue:JSON.parse(e),parsedValueId:r,requestedType:n,usedDefault:!1,reason:`found-match`}}catch{return{parsedValue:t,parsedValueId:void 0,requestedType:n,usedDefault:!0,reason:`invalid-json`}}},G={off:-1,error:0,warn:1,info:2,debug:3},ne=()=>{let e={year:`numeric`,month:`2-digit`,day:`2-digit`,hour:`2-digit`,minute:`2-digit`,second:`2-digit`,timeZoneName:`short`};try{return new Intl.DateTimeFormat(void 0,{...e,fractionalSecondDigits:3})}catch{return new Intl.DateTimeFormat(void 0,e)}};var K=class{constructor(e,t){this.level=e,this.decorator=t,this.level=e,this.decorator=t,this.dateFormatter=ne()}debug(e,...t){this.log(console.debug,`debug`,e,...t)}info(e,...t){this.log(console.info,`info`,e,...t)}warn(e,...t){this.log(console.warn,`warn`,e,...t)}error(e,...t){this.log(console.error,`error`,e,...t)}log(e,t,n,...r){G[this.level]>=G[t]&&e(`[${this.dateFormatter.format(new Date)}] ${this.decorator?.decorateMessage(n)}`,...r)}},q=class{decorateMessage(e){return`[ConfigDirector:js-server-sdk] ${e}`}};const J=(e,t)=>new K(e??`warn`,t??new q),Y=(e,t)=>new URL(e,t?.toString()),X=async(e,t)=>e.valueId?{valueId:e.valueId}:e.value?e.value.length<=500&&e.type!=`json`?{value:e.value}:{valueId:await t(e.value)}:e,Z=e=>{if(e.type===`json`||e.type==null&&typeof e.value==`object`){if(e.valueId)return{valueId:e.valueId,type:`json`};try{return{value:JSON.stringify(e.value),type:`json`}}catch{return{value:e.value.toString(),type:`json`}}}let t=e.value?.toString();return t&&t.length<=500?{value:t}:e.valueId?{valueId:e.valueId}:{value:t}},Q=e=>{let t=Object.keys(e);if(t.length==0)return!0;for(let n of t)if((e[n]?.length??0)>0)return!1;return!0},re=e=>{if(!e)return!0;let t=Object.keys(e);if(t.length==0)return!0;for(let n of t)if((e[n]??0)>0)return!1;return!0};var ie=class{constructor(e){this.executeRequests=!0,this.sdkKey=e.sdkKey,this.logger=e.logger,this.url=new URL(`server/telemetry/v1`,e.baseUrl.toString())}async report({discreteEvents:e,aggregatedEvents:t,droppedEvents:n}){if(!this.executeRequests)return{success:!1,fatalError:!0};let r={serverSdkKey:this.sdkKey,discreteEvents:e,aggregatedEvents:t,droppedEvents:n};if(this.isReportEmpty(r))return{success:!0,fatalError:!1};let i=await this.sendReport(r);return i.fatalError&&(this.executeRequests=!1),i}isReportEmpty(e){return Q(e.discreteEvents)&&Q(e.aggregatedEvents)&&re(e.droppedEvents)}async sendReport(e){try{let t=await w(5e3,this.url,{method:`POST`,body:JSON.stringify(e)},this.logger),n=this.isStatusFatal(t.status);return n&&this.logger.warn(`[EventReporter] Received a fatal status response (${t.status}) from the telemetry endpoint. No more telemetry data will be sent.`),{success:t.ok,fatalError:n}}catch(e){return this.logger.warn(`[EventReporter] Error attempting to send telemetry data: ${e}`,{error:e}),{success:!1,fatalError:!1}}}isStatusFatal(e){return!!e&&e>=400&&e<500}},ae=class{aggregate(e){if(e.events.length==0)return[];let t=new Map;for(let n of e.events){let e=JSON.stringify(n),r=t.get(e)?.count||0;t.set(e,{event:n,count:r+1})}return Array.from(t).map(([,t])=>({startTime:e.startTime,endTime:e.endTime,count:t.count,event:t.event}))}},oe=class{constructor(e){this._events=[],this._droppedEventCount=0,this.limit=e??1e3}get events(){return this._events}get reachedLimit(){return this._events.length>=this.limit}get droppedEventCount(){return this._droppedEventCount}push(...e){this.startTime||=new Date;let t=Math.max(0,e.length-this.limit),n=e.slice(t,e.length);if(this._droppedEventCount+=t,this._events.length+n.length>this.limit){let e=this._events.length+n.length-this.limit;this._events.splice(0,e),this._droppedEventCount+=e}return this._events.push(...n)}takeSnapshot(){let e=new Date,t=this.startTime??e,n=this._events.splice(0),r=this._droppedEventCount;return this.startTime=void 0,this._droppedEventCount=0,{startTime:t,endTime:e,events:n,droppedCount:r}}clear(){this._events=[],this.startTime=void 0,this._droppedEventCount=0}},$=class{constructor(e){this.aggregator=new ae,this.collectEvents=!0,this.logger=e.logger,this.evaluationEventQueue=new oe(e.evaluationQueueLimit??1e3),this.flushIntervalDelay=e.flushIntervalDelay??3e4;let t=e.initialFlushIntervalDelay??5e3;this.flushTimeout=setTimeout(()=>this.flushAndScheduleNext(),t)}async flushAndScheduleNext(){(await this.flush()).fatalError?(this.collectEvents=!1,this.close(),this.logger.warn(`[TelemetryEventCollector] Received a fatal error from telemetry collection. No longer collecting events.`)):this.flushTimeout=setTimeout(()=>this.flushAndScheduleNext(),this.flushIntervalDelay)}cancelScheduledFlush(){clearTimeout(this.flushTimeout)}async close(){this.collectEvents=!1,clearTimeout(this.flushTimeout),await this.flush(),this.evaluationEventQueue.clear()}dispose(){this.close()}},se=class extends Map{constructor(e,t){super(t),this._droppedCount=0,this.limit=e,this.enforceLimit()}set(e,t){return super.set(e,t),this.enforceLimit(),this}get droppedCount(){return this._droppedCount}clearAndReset(){this.clear(),this._droppedCount=0}enforceLimit(){for(;this.size>this.limit;){let e=this.keys().next().value;e!==void 0&&(this.delete(e),this._droppedCount+=1)}}},ce=class{constructor(e){this.valueIdGenerator=e,this.valueIdGenerator=e}async process(e){let t=await Promise.all(e.events.map(async e=>({...e,defaultValue:await X(e.defaultValue,this.valueIdGenerator),evaluatedValue:await X(e.evaluatedValue,this.valueIdGenerator)})));return{...e,events:t}}},le=class extends ${constructor(e){super(e),this.reporter=new ie(e),this.contexts=new se(e.contextLimit??1e3),this.evaluationEventSnapshotPreprocessor=new ce(e.valueIdGenerator)}evaluatedConfig(e){if(!this.collectEvents)return;e.context?.id&&!e.context.anonymous&&this.contexts.set(e.context.id,e.context);let t={...e.evaluation,contextId:e.context?.anonymous?void 0:e.context?.id};this.evaluationEventQueue.push(this.sanitizeEvaluatedConfigEvent(t))}sanitizeEvaluatedConfigEvent(e){return{...e,defaultValue:Z({value:e.defaultValue,type:e.type}),evaluatedValue:Z({value:e.evaluatedValue,valueId:e.evaluatedValueId,type:e.type})}}snapshotContexts(){let e=Array.from(this.contexts.values()),t=this.contexts.droppedCount;return this.contexts.clearAndReset(),{contexts:e,droppedCount:t}}async flush(){if(!this.reporter)return{success:!1,fatalError:!1};let e=await this.evaluationEventSnapshotPreprocessor.process(this.evaluationEventQueue.takeSnapshot()),t=this.snapshotContexts();return await this.reporter?.report({context:this._context,discreteEvents:{capturedContexts:t.contexts},aggregatedEvents:{evaluatedConfig:this.aggregator.aggregate(e)},droppedEvents:{evaluatedConfig:e.droppedCount,capturedContexts:t.droppedCount}})}};const ue=(e,t)=>{let n=BigInt(0),r=BigInt(62),i=Array.from(e).map(e=>e.toString(16).padStart(2,`0`)).join(``),a=BigInt(`0x`+i),o=``;for(;a>n;)o=`0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz`[Number(a%r)]+o,a/=r;return o.padStart(t,`0`)},de=Math.ceil(128/Math.log2(62)),fe=async e=>ue((0,f.createHash)(`sha256`).update(e?.toString()??``).digest().subarray(0,16),de),pe=new URL(`https://server-sdk-api.configdirector.com`);var me=class{constructor(e,t,n){this.handlersMap=new Map,this.eventEmitter=new c.default,this.ready=!1,this.logger=n?.logger??J(),this.timeout=n?.connection?.timeout??3e3,this.metaContext=n?.metadata??{},this.validateSdkKeyPresence(e),this.configEvaluator=new L(this.logger);let r=this.parseUrl(n?.connection?.url)??pe;this.connectionMode=n?.connection?.mode??`streaming`;let i=this.getTransportConstructor(this.connectionMode),a=n?.telemetry?.eventQueueLimit??5e3;this.usageEventCollector=new le({sdkKey:e,logger:this.logger,baseUrl:r,flushIntervalDelay:n?.telemetry?.flushInterval??3e4,evaluationQueueLimit:Math.ceil(a*.7),contextLimit:Math.floor(a*.3),urlFactory:Y,valueIdGenerator:fe}),this.transport=new i({serverSdkKey:e,baseUrl:r,metaContext:{...n?.metadata,sdkName:t.sdkName,sdkVersion:t.sdkVersion},logger:this.logger,pollingInterval:n?.connection?.pollingInterval}),this.transport.on(`configBundleReceived`,e=>{this.readyResolve?.();let t=Object.keys(e.configs);!this.configSet||e.kind==`full`?(this.configSet=e,this.eventEmitter.emit(`configsUpdated`,{keys:t}),this.updateWatchers(e.configs)):(this.configSet.configs={...this.configSet.configs,...e.configs},this.eventEmitter.emit(`configsUpdated`,{keys:t}),this.updateWatchers(e.configs)),this.logger.debug(`[ConfigDirectorClient] ConfigSet updated from server with ${t.length} key(s)`,{keys:t})}),this.registerHandler(`clientReady`,n?.hooks?.clientReady),this.registerHandler(`configEvaluated`,n?.hooks?.configEvaluated),this.registerHandler(`configsUpdated`,n?.hooks?.configsUpdated)}registerHandler(e,t){if(!t)return;let n=Array.isArray(t)?t:[t];for(let t of n)typeof t==`function`&&this.on(e,t)}async initialize(){try{this.ready=!1,this.readyPromise=new Promise(e=>{this.readyResolve=e}).then(()=>{this.ready=!0,this.eventEmitter.emit(`clientReady`),this.logger.debug(`[ConfigDirectorClient] Received initial payload from the server, client is ready`)});let e=new Date().getTime();await this.transport.connect(this.timeout);let t=new Date().getTime()-e,n=this.timeout-t;if(n>0&&await Promise.race([this.readyPromise,new Promise(e=>{setTimeout(()=>e(),n)})]),!this.ready){let e=this.connectionMode===`streaming`?`The client will continue to retry since there were no fatal errors detected. Configs will return the default value until the connection succeeds.`:`Since the client was configured without streaming, configs may not update and always return the default value.`;this.logger.warn(`[ConfigDirectorClient] Timed out waiting for initialization after ${this.timeout}ms. ${e}`)}}catch(e){this.logger.error(`[ConfigDirectorClient] An error occurred during initialization: `,e)}}getTransportConstructor(e){switch(e){case`one-time`:return te;case`polling`:return T;default:return S}}updateWatchers(e){Object.values(e).forEach(e=>this.updateWatchersForConfig(e))}updateWatchersForConfig(e){this.handlersMap.get(e.key)?.forEach(t=>{let n=this.getValueFromConfigDefinition(e.key,e,t.defaultValue,t.context);t.handler(n)})}watch(e,t,n,r){this.validateDefaultValue(t);let i=this.handlersMap.get(e),a={handler:n,defaultValue:t,requestedType:typeof t,context:r};return i?i.push(a):this.handlersMap.set(e,[a]),()=>this.unwatch(e,n)}unwatch(e,t){let n=this.handlersMap.get(e);if(n)if(t){let e=n.findIndex(e=>e.handler==t);e>=0&&n?.splice(e,1)}else n.splice(0)}getValue(e,t,n){this.validateDefaultValue(t);let r=this.configSet?.configs[e];return this.getValueFromConfigDefinition(e,r,t,n)}getValueFromConfigDefinition(e,t,n,r){if(t){let i=this.configEvaluator.evaluate(t,{context:r,metadata:this.metaContext}),a=B(i,n);return this.usageEventCollector.evaluatedConfig({context:r,evaluation:{key:e,type:i.type,defaultValue:n,requestedType:a.requestedType,evaluatedValue:a.parsedValue,evaluatedValueId:a.parsedValueId,usedDefault:a.usedDefault,evaluationReason:a.reason}}),this.dispatchEvaluationEvent({key:e,value:a.parsedValue,valueId:a.parsedValueId??void 0,isDefaultValue:a.usedDefault,reason:a.reason,context:r}),this.logger.debug(`[ConfigDirectorClient] Evaluated '${e}' to '${a.parsedValue}'`),a.parsedValue}else{this.logger.debug(`[ConfigDirectorClient] No config state found for '${e}', returning default value '${n}'`);let t=this.isReady?`config-state-missing`:`client-not-ready`;return this.usageEventCollector.evaluatedConfig({context:r,evaluation:{key:e,defaultValue:n,requestedType:R(n),evaluatedValue:n,usedDefault:!0,evaluationReason:t}}),this.dispatchEvaluationEvent({key:e,value:n,isDefaultValue:!0,reason:t,context:r}),n}}dispatchEvaluationEvent(e){setTimeout(()=>this.eventEmitter.emit(`configEvaluated`,{evaluation:e}),0)}parseUrl(e){if(e)try{return new URL(e)}catch(t){throw new m(`Invalid base URL '${e}'. Parsing failed: ${t}`)}}validateSdkKeyPresence(e){if(!e||e.trim().length==0)throw new m(`No server SDK key was provided, the client cannot be instantiated without a valid server SDK key`)}validateDefaultValue(e){if(e==null)throw new m(`Invalid default value. The default value for a config must be defined and non-null.`);if(typeof e==`function`)throw new m(`Invalid default value. The default value for a config cannot be a function.`)}get isReady(){return this.ready}on(e,t){this.eventEmitter.on(e,t)}off(e,t){t?this.eventEmitter.off(e,t):this.eventEmitter.removeAllListeners(e)}removeAllObservers(){this.logger.debug(`[ConfigDirectorClient] removeAllObservers() has been called, removing all observers`),this.eventEmitter.removeAllListeners(),this.handlersMap.clear()}unwatchAll(){this.handlersMap.clear()}getAllConfigs(e){if(!this.configSet)return{};let t=Object.entries(this.configSet.configs),n=e?.configKeys?t.filter(([t])=>e.configKeys?.includes(t)):t;return Object.fromEntries(n.map(([t,n])=>[t,this.configEvaluator.evaluate(n,{context:e?.context,metadata:this.metaContext})]))}closeConnection(){this.logger.debug(`[ConfigDirectorClient] closeConnection() has been called, closing connection to server`),this.transport.close(),this.ready=!1}dispose(){this.removeAllObservers(),this.closeConnection()}};const he=(e,t)=>new me(e,{sdkName:`js-server-sdk`,sdkVersion:`0.9.2`},t),ge=(e,t)=>J(e,t);exports.createClient=he,exports.createConsoleLogger=ge;
|
|
@@ -2,5 +2,5 @@ import e,{EventEmitter as t}from"node:events";import{findByPointer as n}from"@js
|
|
|
2
2
|
`?(t.push(e.slice(n,r)),i===`\r`&&e[r+1]===`
|
|
3
3
|
`&&r++,r++,n=r):r++}return this.bufferedInput=e.slice(n),t}dispatchLine(e){if(e.startsWith(`:`)){this.onComment?.(this.extractValue(e,1));return}if(e===``){this.emitEvent();return}this.applyField(this.parseField(e))}emitEvent(){let e=this.currentEvent.data.endsWith(`
|
|
4
4
|
`)?this.currentEvent.data.slice(0,-1):this.currentEvent.data;e!==``&&this.onEvent({...this.currentEvent,id:this.lastEventId,data:e}),this.currentEvent={data:``}}applyField(e){switch(e.field){case`event`:this.currentEvent.type=e.value;break;case`data`:this.currentEvent.data+=e.value+`
|
|
5
|
-
`;break;case`id`:e.value.includes(`\0`)||(this.lastEventId=e.value);break;case`retry`:/^\d+$/.test(e.value)&&this.onRetry?.(Number.parseInt(e.value,10));break;default:break}}parseField(e){let t=e.indexOf(`:`);return t===-1?{field:e,value:``}:{field:e.slice(0,t),value:this.extractValue(e,t+1)}}extractValue(e,t){return e[t]===` `?e.slice(t+1):e.slice(t)}};let p=function(e){return e.OPEN=`open`,e.CLOSED=`closed`,e.CONNECTING=`connecting`,e}({});var m=class{constructor(e){this.DEFAULT_SHOULD_RECONNECT=()=>!0,this.DEFAULT_CALCULATE_RECONNECT_DELAY=()=>this.serverReconnectionTime,this.reconnectAttempt=0,this.abortController=new AbortController,this._readyState=p.CLOSED,this.serverReconnectionTime=2e3,this.url=e.url.toString(),this.lastEventId=e.lastEventId,this.headers={Accept:`text/event-stream`,...e.headers},this.requestOptions={body:e.body,method:e.method??`GET`,mode:e.mode,credentials:e.credentials,redirect:e.redirect,referrer:e.referrer,referrerPolicy:e.referrerPolicy},this._fetch=e.fetch??((e,t)=>fetch(e,t)),this._onError=this.resolveCallback(e.onError,()=>{},`onError`),this._onConnect=this.resolveCallback(e.onConnect,()=>{},`onConnect`),this._onDisconnect=this.resolveCallback(e.onDisconnect,()=>{},`onDisconnect`),this._onMessage=this.resolveCallback(e.onMessage,()=>{},`onMessage`),this._onComment=this.resolveCallback(e.onComment,()=>{},`onComment`),this._shouldReconnect=this.resolveCallback(e.shouldReconnect,this.DEFAULT_SHOULD_RECONNECT,`shouldReconnect`),this._calculateReconnectDelay=this.resolveCallback(e.calculateReconnectDelay,this.DEFAULT_CALCULATE_RECONNECT_DELAY,`calculateReconnectDelay`)}set onError(e){this._onError=this.resolveCallback(e,()=>{},`onError`)}set onMessage(e){this._onMessage=this.resolveCallback(e,()=>{},`onMessage`)}set onComment(e){this._onComment=this.resolveCallback(e,()=>{},`onComment`)}set onConnect(e){this._onConnect=this.resolveCallback(e,()=>{},`onConnect`)}set onDisconnect(e){this._onDisconnect=this.resolveCallback(e,()=>{},`onDisconnect`)}set shouldReconnect(e){this._shouldReconnect=this.resolveCallback(e,this.DEFAULT_SHOULD_RECONNECT,`shouldReconnect`)}set calculateReconnectDelay(e){this._calculateReconnectDelay=this.resolveCallback(e,this.DEFAULT_CALCULATE_RECONNECT_DELAY,`shouldReconnect`)}get readyState(){return this._readyState}connect(){this._readyState===p.CLOSED&&(this.reconnectAttempt=0,this.initiateConnection())}close(){clearTimeout(this.reconnectTimeout),this._readyState=p.CLOSED,this.abortController.abort()}initiateConnection(){this.abortController=new AbortController,this._readyState=p.CONNECTING,this._fetch(this.url,{...this.requestOptions,headers:this.buildRequestHeaders(),signal:this.abortController.signal,cache:`no-store`}).then(e=>this.handleFetchResponse(e)).catch(e=>{if(e.name===`AbortError`||this.abortController.signal.aborted){this.close();return}this._onError(e),this.scheduleReconnect({error:e})})}async handleFetchResponse(e){let{body:t,status:n}=e;if(n===204){this.disconnected();return}if(n>=400){this.scheduleReconnect({status:n});return}if(!t)throw new l(`The server response did not have a body`);this._readyState=p.OPEN,this._onConnect(),this.reconnectAttempt=0;let r=new f({onEvent:e=>{typeof e.id==`string`&&(this.lastEventId=e.id),this._onMessage(e)},onComment:e=>this._onComment(e),onRetry:e=>this.serverReconnectionTime=e});await this.readResponseStream(t,r),this.scheduleReconnect({status:n,error:new c(`The server response stream was closed`)})}async readResponseStream(e,t){let n=e.getReader(),r=new TextDecoder,i=!1;for(;!i;){let e=await n.read();e.value&&t.parse(r.decode(e.value,{stream:!0})),i=e.done}t.finish()}disconnected(){clearTimeout(this.reconnectTimeout),this._readyState=p.CLOSED,this._onDisconnect()}buildRequestHeaders(){let e=this.lastEventId?{"Last-Event-ID":this.lastEventId}:void 0;return{...this.headers,...e}}scheduleReconnect(e){if(this.abortController.signal.aborted)return;clearTimeout(this.reconnectTimeout),this.reconnectAttempt+=1;let t={attempt:this.reconnectAttempt,status:e.status,error:e.error,serverReconnectionTime:this.serverReconnectionTime};if(!this._shouldReconnect(t)){this.disconnected();return}this._readyState=p.CONNECTING;let n=this._calculateReconnectDelay(t);(!Number.isFinite(n)||n<1||n>1e3*60*60)&&(this._onError(new d(`The calculated reconnect delay is out of range: ${n}. Defaulting to ${this.serverReconnectionTime}`)),n=this.serverReconnectionTime),this.reconnectTimeout=setTimeout(()=>this.initiateConnection(),n)}resolveCallback(e,t,n){if(e==null)return t;if(typeof e!=`function`)throw new u(`${n} must be a function`);return e}};const h=e=>new m(e);var g=class{constructor(e){this.options=e,this.eventEmitter=new t,this.options=e,this.logger=e.logger,this.url=new URL(`server/sse/v1`,e.baseUrl)}async connect(e){this.eventSource&&this.close();let t=new Promise((e,t)=>{this.eventSource=h({url:this.url,method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({metaContext:this.options.metaContext,serverSdkKey:this.options.serverSdkKey}),onMessage:({data:e})=>{this.dispatchMessage(e)},onConnect:()=>{this.logger.debug(`[StreamingTransport] Connected`),e(this)},shouldReconnect:e=>{let n=!this.isStatusFatal(e.status);return n||t(this.prepareFatalError(e.status,e.error)),n},calculateReconnectDelay:e=>{let t=2**Math.min(e.attempt,9)*1e3,n=`[StreamingTransport] Scheduling reconnect attempt #${e.attempt} in ${t}ms.`;return e.attempt<=5?this.logger.info(n):this.logger.warn(n),t},onDisconnect:()=>{this.logger.debug(`[StreamingTransport] Disconnected`)}}),this.eventSource.connect()});return Promise.race([t,new Promise(t=>{setTimeout(()=>t(this),e)})])}dispatchMessage(e){try{let t=JSON.parse(e);this.eventEmitter.emit(`configBundleReceived`,t)}catch(e){this.logger.error(`[StreamingTransport] Error parsing and dispatching config data update: `,e)}}prepareFatalError(e,t){let n=e??0;return new o(`${`Connection failed with status: ${e??`unknown`}.`}${t?` Error: ${t}.`:``} This is an unrecoverable error, will not attempt to reconnect.`,n)}isStatusFatal(e){return!!e&&e>=400&&e<500}get isConnected(){return this.eventSource?.readyState==`open`}on(e,t){this.eventEmitter.on(e,t)}off(e,t){t?this.eventEmitter.off(e,t):this.eventEmitter.removeAllListeners(e)}clear(){this.eventEmitter.removeAllListeners()}close(){this.eventSource?.close()}dispose(){this.close(),this.clear()}},_=class{constructor(){this.fatalError=!1,this.pollingIntervalSeconds=60}clearPollingInterval(){clearInterval(this.pollingInterval)}schedulePollingInterval(e){clearInterval(this.pollingInterval),!(this.pollingIntervalSeconds<=0)&&(this.pollingInterval=setInterval(e,this.pollingIntervalSeconds*1e3))}async handleNonOkResponse(e){if(!e.ok)throw this.isStatusFatal(e.status)?(this.fatalError=!0,this.prepareFatalResponseStatusError(e.status,await e.text())):new o(`Connection failed with status: ${e.status}`,e.status)}handleFetchError(e){throw this.isFetchErrorFatal(e)?(this.fatalError=!0,this.close(),new o(`Connection failed with fatal error: ${e}. This is an unrecoverable error, retry attempts will be ignored.`)):e instanceof SyntaxError?new o(`Failed to parse the response from the server: ${e}`):new o(`Connection failed with error: ${e}.`)}prepareFatalResponseStatusError(e,t){let n=e??0;return new o(`${`Connection failed with status: ${e??`unknown`}`}${(t?.trim()?.length??0)>0?` (${t})`:``}. This is an unrecoverable error, retry attempts will be ignored.`,n)}isStatusFatal(e){return!!e&&e>=400&&e<500}isFetchErrorFatal(e){return e?.name===`NotAllowedError`?!0:e instanceof TypeError}get isConnected(){return this.pollingInterval!==void 0}close(){clearInterval(this.pollingInterval),this.pollingInterval=void 0}dispose(){this.close(),this.clear()}};const v=async(e,t,n,r)=>{let i=new AbortController,a=setTimeout(()=>{r.debug(`[fetchWithTimeout] Reached timeout, aborting request`),i.abort()},e);try{let e=await fetch(typeof t==`string`||t instanceof Request?t:t.toString(),{...n,signal:i.signal});return clearTimeout(a),e}catch(e){throw r.warn(`[fetchWithTimeout] Fetch error: `,e),clearTimeout(a),e}};var y=class extends _{constructor(e){super(),this.options=e,this.eventEmitter=new t,this.options=e,this.logger=e.logger,this.url=new URL(`server/polling/v1`,e.baseUrl),this.pollingIntervalSeconds=e.pollingInterval??60}async connect(e){return this.clearPollingInterval(),await this.fetchConfigs(e),this.schedulePollingInterval(()=>{this.fetchConfigs(e).catch(e=>{this.logger.error(`[PollingTransport] Error during polling:`,e)})}),this}async fetchConfigs(e){if(this.fatalError){this.logger.warn(`[PollingTransport] There was a prior unrecoverable error. Ignoring attempt to reconnect.`);return}try{let t=await v(e,this.url,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({metaContext:this.options.metaContext,serverSdkKey:this.options.serverSdkKey,lastUpdateTimestamp:this.lastUpdateTimestamp})},this.logger);if(await this.handleNonOkResponse(t),t.status===200){let e=JSON.parse(await t.text());this.lastUpdateTimestamp=e.timestamp,this.eventEmitter.emit(`configBundleReceived`,e)}}catch(e){this.handleFetchError(e)}}on(e,t){this.eventEmitter.on(e,t)}off(e,t){t?this.eventEmitter.off(e,t):this.eventEmitter.removeAllListeners(e)}clear(){this.eventEmitter.removeAllListeners()}},b=class extends y{constructor(e){super(e),this.pollingIntervalSeconds=0}};const ee=(e,t,n)=>{switch(t.toLowerCase()){case`=`:case`equals`:return e==n[0];case`!=`:case`does not equal`:return e!=n[0];case`is one of`:return n.includes(e);case`is not one of`:return!n.includes(e);case`starts with any of`:return n.findIndex(t=>e?.startsWith(t))>=0;case`does not start with any of`:return n.findIndex(t=>e?.startsWith(t))<0;case`ends with any of`:return n.findIndex(t=>e?.endsWith(t))>=0;case`does not end with any of`:return n.findIndex(t=>e?.endsWith(t))<0;case`matches regex`:return x(n[0],e);case`does not match regex`:return!x(n[0],e);default:return!1}},x=(e,t)=>{try{return new RegExp(e).test(t)}catch{return!1}},S=(e,t,n)=>{if(typeof e==`boolean`)return!1;let r=t.toLowerCase();if(e==null)return r==`!=`||r==`does not equal`;let{value:i,target:a}=C(e,n[0]);switch(r){case`=`:case`equals`:return i==a;case`!=`:case`does not equal`:return i!=a;case`<`:return i<a;case`<=`:return i<=a;case`>`:return i>a;case`>=`:return i>=a;default:return!1}},C=(e,t)=>typeof e==`number`?{value:e,target:w(t)}:typeof e==`bigint`?{value:e,target:BigInt(t)}:{value:w(e),target:w(t)},w=e=>{let t=Number.parseFloat(e.toString());if(isNaN(t)||!isFinite(t))throw TypeError(`Invalid numeric value: ${e}`);return t},T=(e,t,n)=>{if(e==null)return!1;try{let r=new Date(e),i=new Date(n[0]);switch(t){case`is after`:return r>i;case`is before`:return r<i;default:return!1}}catch{return!1}},E=(e,t,n)=>{let i=t.toLowerCase();if(e==null||e.trim().length==0)return i==`is not one of`;let a=r.coerce(e)??e,o=n.map(e=>r.coerce(e)??e);try{switch(i){case`=`:return D(a,o[0]);case`<`:return r.lt(a,o[0]);case`<=`:return r.lte(a,o[0]);case`>`:return r.gt(a,o[0]);case`>=`:return r.gte(a,o[0]);case`is one of`:return o.findIndex(e=>D(a,e))>=0;case`is not one of`:return o.findIndex(e=>D(a,e))<0;default:return!1}}catch{return!1}},D=(e,t)=>{try{return r.eq(e,t)}catch{return!1}},O=(e,t,n)=>{let r=t.toLowerCase();if(!Array.isArray(e))return r==`does not contain any of`;switch(r){case`contains any of`:return e.some(e=>n.includes(e));case`does not contain any of`:return!e.some(e=>n.includes(e));default:return!1}};var k=class{evaluate(e,t){if(!this.isConditionValidForComparison(e))return!1;switch(e.attribute){case`identifier`:return this.compare(t?.context?.id,e);case`name`:return this.compare(t?.context?.name,e);case`appName`:return this.compare(t?.metadata?.appName,e);case`appVersion`:return this.compare(t?.metadata?.appVersion,e);case`traits`:if(e.trait){let n=this.findTraitValue(t?.context?.traits,e.trait);return this.compare(n,e)}}return!1}compare(e,t){switch(t.targetType){case`text`:return ee(e?.toString(),t.operator,t.targetValues);case`number`:return S(e,t.operator,t.targetValues);case`datetime`:return T(e?.toString(),t.operator,t.targetValues);case`semver`:return E(e?.toString(),t.operator,t.targetValues);case`array`:return O(e,t.operator,t.targetValues)}return!1}findTraitValue(e,t){try{return n(t,e).val}catch{return}}isConditionValidForComparison(e){return Array.isArray(e.targetValues)&&e.targetValues.length>0}};const A=e=>{let t=`${e.contextIdentifier}-${e.configId}`;return Number(i(t,{seed:15426857351n})%1000n)/10};var j=class{constructor(e){if(this.logger=e,this.conditionEvaluator=new k,!e||!e.warn||!e.error||!e.info)throw TypeError(`The provided logger is not a valid 'ConfigDirectorLogger'`);this.logger=e}evaluate(e,t){return{id:e.id,key:e.key,type:e.type,value:this.getConfigValue(e,t)}}getConfigValue(e,t){let n=[...e.target?.rules??[]].sort((e,t)=>(e.order??2**53-1)-(t.order??2**53-1));for(let r of n){let n=this.evaluateRule(r,e,t);if(n.success)return n.value}return e.target?.defaultValue}evaluateRule(e,t,n){try{if(e.type==`percentage`)return this.evaluatePercentageRule(e,t,n);if(e.type==`conditional`)return this.evaluateConditionalRule(e,t,n)}catch(n){this.logger.warn(`There was an error while evaluating a targeting rule '${e?.id}' for '${t?.key}'. The rule will be disregarded.`,{error:n,configKey:t?.key,ruleId:e?.id})}return{success:!1}}evaluatePercentageRule(e,t,n){return this.evaluatePercentage(e.percentages??[],t,n)}evaluatePercentage(e,t,n){let r=A({configId:t.id,contextIdentifier:n?.context?.id??crypto.randomUUID()}),i=0,a=0,o;for(;!o&&i<e.length;)r<=e[i].percentage+a&&(o=e[i]),a+=e[i].percentage,i+=1;return o?.value==null?{success:!1}:{success:!0,value:o.value.toString()}}evaluateConditionalRule(e,t,n){let r=e.conditions??[],i=0,a;for(;!a&&i<r.length;)this.conditionEvaluator.evaluate(r[i],n)&&(a=r[i]),i+=1;return a&&e.target==`value`&&e.value!=null?{success:!0,value:e.value.toString()}:a&&e.target==`percentage`?this.evaluatePercentage(e.percentages??[],t,n):{success:!1}}};const M=e=>{let t=typeof e;if(t===`object`)try{return e.constructor?.name??t}catch{return t}else if(t===`function`)try{let n=e.name;return n?`function: ${n}`:t}catch{return t}return t},N=e=>e===`number`||e===`bigint`,P=(e,t)=>{let n=e.value,r=M(t);if(!n)return{parsedValue:t,parsedValueId:void 0,requestedType:r,usedDefault:!0,reason:`value-missing`};if(e.type===`json`)return R(n,t,r,e.valueId);if(typeof t==`string`)return{parsedValue:n,parsedValueId:e.valueId,requestedType:r,usedDefault:!1,reason:`found-match`};if(typeof t==`boolean`&&(e.type===`boolean`||e.type===`string`)){let i=F(n),a=typeof i==`boolean`;return{parsedValue:a?i:t,parsedValueId:a?e.valueId:void 0,requestedType:r,usedDefault:!a,reason:a?`found-match`:`invalid-boolean`}}if(N(typeof t)&&e.type===`integer`){let i=I(n),a=typeof i==`number`;return{parsedValue:a?i:t,parsedValueId:a?e.valueId:void 0,requestedType:r,usedDefault:!a,reason:a?`found-match`:`invalid-number`}}if(N(typeof t)&&(e.type===`float`||e.type===`enum`||e.type===`string`)){let i=L(n),a=typeof i==`number`;return{parsedValue:a?i:t,parsedValueId:a?e.valueId:void 0,requestedType:r,usedDefault:!a,reason:a?`found-match`:`invalid-number`}}return{parsedValue:n,parsedValueId:e.valueId,requestedType:r,usedDefault:!1,reason:`found-match`}},F=e=>{if(!e)return;let t=e.toLowerCase();if(!(t!=`true`&&t!=`false`))return t===`true`},I=e=>{if(!e)return;let t=Number.parseInt(e);if(!(isNaN(t)||!isFinite(t)))return t},L=e=>{if(!e)return;let t=Number.parseFloat(e);if(!(isNaN(t)||!isFinite(t)))return t},R=(e,t,n,r)=>{if(typeof t==`string`)return{parsedValue:e,parsedValueId:r,requestedType:n,usedDefault:!1,reason:`found-match`};try{return{parsedValue:JSON.parse(e),parsedValueId:r,requestedType:n,usedDefault:!1,reason:`found-match`}}catch{return{parsedValue:t,parsedValueId:void 0,requestedType:n,usedDefault:!0,reason:`invalid-json`}}},z={off:-1,error:0,warn:1,info:2,debug:3},B=()=>{let e={year:`numeric`,month:`2-digit`,day:`2-digit`,hour:`2-digit`,minute:`2-digit`,second:`2-digit`,timeZoneName:`short`};try{return new Intl.DateTimeFormat(void 0,{...e,fractionalSecondDigits:3})}catch{return new Intl.DateTimeFormat(void 0,e)}};var te=class{constructor(e,t){this.level=e,this.decorator=t,this.level=e,this.decorator=t,this.dateFormatter=B()}debug(e,...t){this.log(console.debug,`debug`,e,...t)}info(e,...t){this.log(console.info,`info`,e,...t)}warn(e,...t){this.log(console.warn,`warn`,e,...t)}error(e,...t){this.log(console.error,`error`,e,...t)}log(e,t,n,...r){z[this.level]>=z[t]&&e(`[${this.dateFormatter.format(new Date)}] ${this.decorator?.decorateMessage(n)}`,...r)}},V=class{decorateMessage(e){return`[ConfigDirector:js-server-sdk] ${e}`}};const H=(e,t)=>new te(e??`warn`,t??new V),U=(e,t)=>new URL(e,t?.toString()),W=async(e,t)=>e.valueId?{valueId:e.valueId}:e.value?e.value.length<=500&&e.type!=`json`?{value:e.value}:{valueId:await t(e.value)}:e,G=e=>{if(e.type===`json`||e.type==null&&typeof e.value==`object`){if(e.valueId)return{valueId:e.valueId,type:`json`};try{return{value:JSON.stringify(e.value),type:`json`}}catch{return{value:e.value.toString(),type:`json`}}}let t=e.value?.toString();return t&&t.length<=500?{value:t}:e.valueId?{valueId:e.valueId}:{value:t}},K=e=>{let t=Object.keys(e);if(t.length==0)return!0;for(let n of t)if((e[n]?.length??0)>0)return!1;return!0},q=e=>{if(!e)return!0;let t=Object.keys(e);if(t.length==0)return!0;for(let n of t)if((e[n]??0)>0)return!1;return!0};var J=class{constructor(e){this.executeRequests=!0,this.sdkKey=e.sdkKey,this.logger=e.logger,this.url=new URL(`server/telemetry/v1`,e.baseUrl.toString())}async report({discreteEvents:e,aggregatedEvents:t,droppedEvents:n}){if(!this.executeRequests)return{success:!1,fatalError:!0};let r={serverSdkKey:this.sdkKey,discreteEvents:e,aggregatedEvents:t,droppedEvents:n};if(this.isReportEmpty(r))return{success:!0,fatalError:!1};let i=await this.sendReport(r);return i.fatalError&&(this.executeRequests=!1),i}isReportEmpty(e){return K(e.discreteEvents)&&K(e.aggregatedEvents)&&q(e.droppedEvents)}async sendReport(e){try{let t=await v(5e3,this.url,{method:`POST`,body:JSON.stringify(e)},this.logger),n=this.isStatusFatal(t.status);return n&&this.logger.warn(`[EventReporter] Received a fatal status response (${t.status}) from the telemetry endpoint. No more telemetry data will be sent.`),{success:t.ok,fatalError:n}}catch(e){return this.logger.warn(`[EventReporter] Error attempting to send telemetry data: ${e}`,{error:e}),{success:!1,fatalError:!1}}}isStatusFatal(e){return!!e&&e>=400&&e<500}},Y=class{aggregate(e){if(e.events.length==0)return[];let t=new Map;for(let n of e.events){let e=JSON.stringify(n),r=t.get(e)?.count||0;t.set(e,{event:n,count:r+1})}return Array.from(t).map(([,t])=>({startTime:e.startTime,endTime:e.endTime,count:t.count,event:t.event}))}},X=class{constructor(e){this._events=[],this._droppedEventCount=0,this.limit=e??1e3}get events(){return this._events}get reachedLimit(){return this._events.length>=this.limit}get droppedEventCount(){return this._droppedEventCount}push(...e){this.startTime||(this.startTime=new Date);let t=Math.max(0,e.length-this.limit),n=e.slice(t,e.length);if(this._droppedEventCount+=t,this._events.length+n.length>this.limit){let e=this._events.length+n.length-this.limit;this._events.splice(0,e),this._droppedEventCount+=e}return this._events.push(...n)}takeSnapshot(){let e=new Date,t=this.startTime??e,n=this._events.splice(0),r=this._droppedEventCount;return this.startTime=void 0,this._droppedEventCount=0,{startTime:t,endTime:e,events:n,droppedCount:r}}clear(){this._events=[],this.startTime=void 0,this._droppedEventCount=0}},Z=class{constructor(e){this.aggregator=new Y,this.collectEvents=!0,this.logger=e.logger,this.evaluationEventQueue=new X(e.evaluationQueueLimit??1e3),this.flushIntervalDelay=e.flushIntervalDelay??3e4;let t=e.initialFlushIntervalDelay??5e3;this.flushTimeout=setTimeout(()=>this.flushAndScheduleNext(),t)}async flushAndScheduleNext(){(await this.flush()).fatalError?(this.collectEvents=!1,this.close(),this.logger.warn(`[TelemetryEventCollector] Received a fatal error from telemetry collection. No longer collecting events.`)):this.flushTimeout=setTimeout(()=>this.flushAndScheduleNext(),this.flushIntervalDelay)}cancelScheduledFlush(){clearTimeout(this.flushTimeout)}async close(){this.collectEvents=!1,clearTimeout(this.flushTimeout),await this.flush(),this.evaluationEventQueue.clear()}dispose(){this.close()}},Q=class extends Map{constructor(e,t){super(t),this._droppedCount=0,this.limit=e,this.enforceLimit()}set(e,t){return super.set(e,t),this.enforceLimit(),this}get droppedCount(){return this._droppedCount}clearAndReset(){this.clear(),this._droppedCount=0}enforceLimit(){for(;this.size>this.limit;){let e=this.keys().next().value;e!==void 0&&(this.delete(e),this._droppedCount+=1)}}},ne=class{constructor(e){this.valueIdGenerator=e,this.valueIdGenerator=e}async process(e){let t=await Promise.all(e.events.map(async e=>({...e,defaultValue:await W(e.defaultValue,this.valueIdGenerator),evaluatedValue:await W(e.evaluatedValue,this.valueIdGenerator)})));return{...e,events:t}}},re=class extends Z{constructor(e){super(e),this.reporter=new J(e),this.contexts=new Q(e.contextLimit??1e3),this.evaluationEventSnapshotPreprocessor=new ne(e.valueIdGenerator)}evaluatedConfig(e){if(!this.collectEvents)return;e.context?.id&&!e.context.anonymous&&this.contexts.set(e.context.id,e.context);let t={...e.evaluation,contextId:e.context?.anonymous?void 0:e.context?.id};this.evaluationEventQueue.push(this.sanitizeEvaluatedConfigEvent(t))}sanitizeEvaluatedConfigEvent(e){return{...e,defaultValue:G({value:e.defaultValue,type:e.type}),evaluatedValue:G({value:e.evaluatedValue,valueId:e.evaluatedValueId,type:e.type})}}snapshotContexts(){let e=Array.from(this.contexts.values()),t=this.contexts.droppedCount;return this.contexts.clearAndReset(),{contexts:e,droppedCount:t}}async flush(){if(!this.reporter)return{success:!1,fatalError:!1};let e=await this.evaluationEventSnapshotPreprocessor.process(this.evaluationEventQueue.takeSnapshot()),t=this.snapshotContexts();return await this.reporter?.report({context:this._context,discreteEvents:{capturedContexts:t.contexts},aggregatedEvents:{evaluatedConfig:this.aggregator.aggregate(e)},droppedEvents:{evaluatedConfig:e.droppedCount,capturedContexts:t.droppedCount}})}};const ie=(e,t)=>{let n=BigInt(0),r=BigInt(62),i=Array.from(e).map(e=>e.toString(16).padStart(2,`0`)).join(``),a=BigInt(`0x`+i),o=``;for(;a>n;)o=`0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz`[Number(a%r)]+o,a/=r;return o.padStart(t,`0`)},ae=Math.ceil(128/Math.log2(62)),oe=async e=>ie(a(`sha256`).update(e?.toString()??``).digest().subarray(0,16),ae),$=new URL(`https://server-sdk-api.configdirector.com`);var se=class{constructor(t,n,r){this.handlersMap=new Map,this.eventEmitter=new e,this.ready=!1,this.logger=r?.logger??H(),this.timeout=r?.connection?.timeout??3e3,this.metaContext=r?.metadata??{},this.validateSdkKeyPresence(t),this.configEvaluator=new j(this.logger);let i=this.parseUrl(r?.connection?.url)??$;this.connectionMode=r?.connection?.mode??`streaming`;let a=this.getTransportConstructor(this.connectionMode),o=r?.telemetry?.eventQueueLimit??5e3;this.usageEventCollector=new re({sdkKey:t,logger:this.logger,baseUrl:i,flushIntervalDelay:r?.telemetry?.flushInterval??3e4,evaluationQueueLimit:Math.ceil(o*.7),contextLimit:Math.floor(o*.3),urlFactory:U,valueIdGenerator:oe}),this.transport=new a({serverSdkKey:t,baseUrl:i,metaContext:{...r?.metadata,sdkName:n.sdkName,sdkVersion:n.sdkVersion},logger:this.logger,pollingInterval:r?.connection?.pollingInterval}),this.transport.on(`configBundleReceived`,e=>{this.readyResolve?.();let t=Object.keys(e.configs);!this.configSet||e.kind==`full`?(this.configSet=e,this.eventEmitter.emit(`configsUpdated`,{keys:t}),this.updateWatchers(e.configs)):(this.configSet.configs={...this.configSet.configs,...e.configs},this.eventEmitter.emit(`configsUpdated`,{keys:t}),this.updateWatchers(e.configs)),this.logger.debug(`[ConfigDirectorClient] ConfigSet updated from server with ${t.length} key(s)`,{keys:t})}),this.registerHandler(`clientReady`,r?.hooks?.clientReady),this.registerHandler(`configEvaluated`,r?.hooks?.configEvaluated),this.registerHandler(`configsUpdated`,r?.hooks?.configsUpdated)}registerHandler(e,t){if(!t)return;let n=Array.isArray(t)?t:[t];for(let t of n)typeof t==`function`&&this.on(e,t)}async initialize(){try{this.ready=!1,this.readyPromise=new Promise(e=>{this.readyResolve=e}).then(()=>{this.ready=!0,this.eventEmitter.emit(`clientReady`),this.logger.debug(`[ConfigDirectorClient] Received initial payload from the server, client is ready`)});let e=new Date().getTime();await this.transport.connect(this.timeout);let t=new Date().getTime()-e,n=this.timeout-t;if(n>0&&await Promise.race([this.readyPromise,new Promise(e=>{setTimeout(()=>e(),n)})]),!this.ready){let e=this.connectionMode===`streaming`?`The client will continue to retry since there were no fatal errors detected. Configs will return the default value until the connection succeeds.`:`Since the client was configured without streaming, configs may not update and always return the default value.`;this.logger.warn(`[ConfigDirectorClient] Timed out waiting for initialization after ${this.timeout}ms. ${e}`)}}catch(e){this.logger.error(`[ConfigDirectorClient] An error occurred during initialization: `,e)}}getTransportConstructor(e){switch(e){case`one-time`:return b;case`polling`:return y;default:return g}}updateWatchers(e){Object.values(e).forEach(e=>this.updateWatchersForConfig(e))}updateWatchersForConfig(e){this.handlersMap.get(e.key)?.forEach(t=>{let n=this.getValueFromConfigDefinition(e.key,e,t.defaultValue,t.context);t.handler(n)})}watch(e,t,n,r){this.validateDefaultValue(t);let i=this.handlersMap.get(e),a={handler:n,defaultValue:t,requestedType:typeof t,context:r};return i?i.push(a):this.handlersMap.set(e,[a]),()=>this.unwatch(e,n)}unwatch(e,t){let n=this.handlersMap.get(e);if(n)if(t){let e=n.findIndex(e=>e.handler==t);e>=0&&n?.splice(e,1)}else n.splice(0)}getValue(e,t,n){this.validateDefaultValue(t);let r=this.configSet?.configs[e];return this.getValueFromConfigDefinition(e,r,t,n)}getValueFromConfigDefinition(e,t,n,r){if(t){let i=this.configEvaluator.evaluate(t,{context:r,metadata:this.metaContext}),a=P(i,n);return this.usageEventCollector.evaluatedConfig({context:r,evaluation:{key:e,type:i.type,defaultValue:n,requestedType:a.requestedType,evaluatedValue:a.parsedValue,evaluatedValueId:a.parsedValueId,usedDefault:a.usedDefault,evaluationReason:a.reason}}),this.dispatchEvaluationEvent({key:e,value:a.parsedValue,valueId:a.parsedValueId??void 0,isDefaultValue:a.usedDefault,reason:a.reason,context:r}),this.logger.debug(`[ConfigDirectorClient] Evaluated '${e}' to '${a.parsedValue}'`),a.parsedValue}else{this.logger.debug(`[ConfigDirectorClient] No config state found for '${e}', returning default value '${n}'`);let t=this.isReady?`config-state-missing`:`client-not-ready`;return this.usageEventCollector.evaluatedConfig({context:r,evaluation:{key:e,defaultValue:n,requestedType:M(n),evaluatedValue:n,usedDefault:!0,evaluationReason:t}}),this.dispatchEvaluationEvent({key:e,value:n,isDefaultValue:!0,reason:t,context:r}),n}}dispatchEvaluationEvent(e){setTimeout(()=>this.eventEmitter.emit(`configEvaluated`,{evaluation:e}),0)}parseUrl(e){if(e)try{return new URL(e)}catch(t){throw new s(`Invalid base URL '${e}'. Parsing failed: ${t}`)}}validateSdkKeyPresence(e){if(!e||e.trim().length==0)throw new s(`No server SDK key was provided, the client cannot be instantiated without a valid server SDK key`)}validateDefaultValue(e){if(e==null)throw new s(`Invalid default value. The default value for a config must be defined and non-null.`);if(typeof e==`function`)throw new s(`Invalid default value. The default value for a config cannot be a function.`)}get isReady(){return this.ready}on(e,t){this.eventEmitter.on(e,t)}off(e,t){t?this.eventEmitter.off(e,t):this.eventEmitter.removeAllListeners(e)}removeAllObservers(){this.logger.debug(`[ConfigDirectorClient] removeAllObservers() has been called, removing all observers`),this.eventEmitter.removeAllListeners(),this.handlersMap.clear()}unwatchAll(){this.handlersMap.clear()}getAllConfigs(e){if(!this.configSet)return{};let t=Object.entries(this.configSet.configs),n=e?.configKeys?t.filter(([t])=>e.configKeys?.includes(t)):t;return Object.fromEntries(n.map(([t,n])=>[t,this.configEvaluator.evaluate(n,{context:e?.context,metadata:this.metaContext})]))}closeConnection(){this.logger.debug(`[ConfigDirectorClient] closeConnection() has been called, closing connection to server`),this.transport.close(),this.ready=!1}dispose(){this.removeAllObservers(),this.closeConnection()}};const ce=(e,t)=>new se(e,{sdkName:`js-server-sdk`,sdkVersion:`0.9.1`},t),le=(e,t)=>H(e,t);export{ce as createClient,le as createConsoleLogger};
|
|
5
|
+
`;break;case`id`:e.value.includes(`\0`)||(this.lastEventId=e.value);break;case`retry`:/^\d+$/.test(e.value)&&this.onRetry?.(Number.parseInt(e.value,10));break;default:break}}parseField(e){let t=e.indexOf(`:`);return t===-1?{field:e,value:``}:{field:e.slice(0,t),value:this.extractValue(e,t+1)}}extractValue(e,t){return e[t]===` `?e.slice(t+1):e.slice(t)}};let p=function(e){return e.OPEN=`open`,e.CLOSED=`closed`,e.CONNECTING=`connecting`,e}({});var m=class{constructor(e){this.DEFAULT_SHOULD_RECONNECT=()=>!0,this.DEFAULT_CALCULATE_RECONNECT_DELAY=()=>this.serverReconnectionTime,this.reconnectAttempt=0,this.abortController=new AbortController,this._readyState=p.CLOSED,this.serverReconnectionTime=2e3,this.url=e.url.toString(),this.lastEventId=e.lastEventId,this.headers={Accept:`text/event-stream`,...e.headers},this.requestOptions={body:e.body,method:e.method??`GET`,mode:e.mode,credentials:e.credentials,redirect:e.redirect,referrer:e.referrer,referrerPolicy:e.referrerPolicy},this._fetch=e.fetch??((e,t)=>fetch(e,t)),this._onError=this.resolveCallback(e.onError,()=>{},`onError`),this._onConnect=this.resolveCallback(e.onConnect,()=>{},`onConnect`),this._onDisconnect=this.resolveCallback(e.onDisconnect,()=>{},`onDisconnect`),this._onMessage=this.resolveCallback(e.onMessage,()=>{},`onMessage`),this._onComment=this.resolveCallback(e.onComment,()=>{},`onComment`),this._shouldReconnect=this.resolveCallback(e.shouldReconnect,this.DEFAULT_SHOULD_RECONNECT,`shouldReconnect`),this._calculateReconnectDelay=this.resolveCallback(e.calculateReconnectDelay,this.DEFAULT_CALCULATE_RECONNECT_DELAY,`calculateReconnectDelay`)}set onError(e){this._onError=this.resolveCallback(e,()=>{},`onError`)}set onMessage(e){this._onMessage=this.resolveCallback(e,()=>{},`onMessage`)}set onComment(e){this._onComment=this.resolveCallback(e,()=>{},`onComment`)}set onConnect(e){this._onConnect=this.resolveCallback(e,()=>{},`onConnect`)}set onDisconnect(e){this._onDisconnect=this.resolveCallback(e,()=>{},`onDisconnect`)}set shouldReconnect(e){this._shouldReconnect=this.resolveCallback(e,this.DEFAULT_SHOULD_RECONNECT,`shouldReconnect`)}set calculateReconnectDelay(e){this._calculateReconnectDelay=this.resolveCallback(e,this.DEFAULT_CALCULATE_RECONNECT_DELAY,`shouldReconnect`)}get readyState(){return this._readyState}connect(){this._readyState===p.CLOSED&&(this.reconnectAttempt=0,this.initiateConnection())}close(){clearTimeout(this.reconnectTimeout),this._readyState=p.CLOSED,this.abortController.abort()}initiateConnection(){this.abortController=new AbortController,this._readyState=p.CONNECTING,this._fetch(this.url,{...this.requestOptions,headers:this.buildRequestHeaders(),signal:this.abortController.signal,cache:`no-store`}).then(e=>this.handleFetchResponse(e)).catch(e=>{if(e.name===`AbortError`||this.abortController.signal.aborted){this.close();return}this._onError(e),this.scheduleReconnect({error:e})})}async handleFetchResponse(e){let{body:t,status:n}=e;if(n===204){this.disconnected();return}if(n>=400){this.scheduleReconnect({status:n});return}if(!t)throw new l(`The server response did not have a body`);this._readyState=p.OPEN,this._onConnect(),this.reconnectAttempt=0;let r=new f({onEvent:e=>{typeof e.id==`string`&&(this.lastEventId=e.id),this._onMessage(e)},onComment:e=>this._onComment(e),onRetry:e=>this.serverReconnectionTime=e});await this.readResponseStream(t,r),this.scheduleReconnect({status:n,error:new c(`The server response stream was closed`)})}async readResponseStream(e,t){let n=e.getReader(),r=new TextDecoder,i=!1;for(;!i;){let e=await n.read();e.value&&t.parse(r.decode(e.value,{stream:!0})),i=e.done}t.finish()}disconnected(){clearTimeout(this.reconnectTimeout),this._readyState=p.CLOSED,this._onDisconnect()}buildRequestHeaders(){let e=this.lastEventId?{"Last-Event-ID":this.lastEventId}:void 0;return{...this.headers,...e}}scheduleReconnect(e){if(this.abortController.signal.aborted)return;clearTimeout(this.reconnectTimeout),this.reconnectAttempt+=1;let t={attempt:this.reconnectAttempt,status:e.status,error:e.error,serverReconnectionTime:this.serverReconnectionTime};if(!this._shouldReconnect(t)){this.disconnected();return}this._readyState=p.CONNECTING;let n=this._calculateReconnectDelay(t);(!Number.isFinite(n)||n<1||n>1e3*60*60)&&(this._onError(new d(`The calculated reconnect delay is out of range: ${n}. Defaulting to ${this.serverReconnectionTime}`)),n=this.serverReconnectionTime),this.reconnectTimeout=setTimeout(()=>this.initiateConnection(),n)}resolveCallback(e,t,n){if(e==null)return t;if(typeof e!=`function`)throw new u(`${n} must be a function`);return e}};const h=e=>new m(e);var g=class{constructor(e){this.options=e,this.eventEmitter=new t,this.options=e,this.logger=e.logger,this.url=new URL(`server/sse/v1`,e.baseUrl)}async connect(e){this.eventSource&&this.close();let t=new Promise((e,t)=>{this.eventSource=h({url:this.url,method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({metaContext:this.options.metaContext,serverSdkKey:this.options.serverSdkKey}),onMessage:({data:e})=>{this.dispatchMessage(e)},onConnect:()=>{this.logger.debug(`[StreamingTransport] Connected`),e(this)},shouldReconnect:e=>{let n=!this.isStatusFatal(e.status);return n||t(this.prepareFatalError(e.status,e.error)),n},calculateReconnectDelay:e=>{let t=2**Math.min(e.attempt,9)*1e3,n=`[StreamingTransport] Scheduling reconnect attempt #${e.attempt} in ${t}ms.`;return e.attempt<=5?this.logger.info(n):this.logger.warn(n),t},onDisconnect:()=>{this.logger.debug(`[StreamingTransport] Disconnected`)}}),this.eventSource.connect()});return Promise.race([t,new Promise(t=>{setTimeout(()=>t(this),e)})])}dispatchMessage(e){try{let t=JSON.parse(e);this.eventEmitter.emit(`configBundleReceived`,t)}catch(e){this.logger.error(`[StreamingTransport] Error parsing and dispatching config data update: `,e)}}prepareFatalError(e,t){let n=e??0;return new o(`${`Connection failed with status: ${e??`unknown`}.`}${t?` Error: ${t}.`:``} This is an unrecoverable error, will not attempt to reconnect.`,n)}isStatusFatal(e){return!!e&&e>=400&&e<500}get isConnected(){return this.eventSource?.readyState==`open`}on(e,t){this.eventEmitter.on(e,t)}off(e,t){t?this.eventEmitter.off(e,t):this.eventEmitter.removeAllListeners(e)}clear(){this.eventEmitter.removeAllListeners()}close(){this.eventSource?.close()}dispose(){this.close(),this.clear()}},_=class{constructor(){this.fatalError=!1,this.pollingIntervalSeconds=60}clearPollingInterval(){clearInterval(this.pollingInterval)}schedulePollingInterval(e){clearInterval(this.pollingInterval),!(this.pollingIntervalSeconds<=0)&&(this.pollingInterval=setInterval(e,this.pollingIntervalSeconds*1e3))}async handleNonOkResponse(e){if(!e.ok)throw this.isStatusFatal(e.status)?(this.fatalError=!0,this.prepareFatalResponseStatusError(e.status,await e.text())):new o(`Connection failed with status: ${e.status}`,e.status)}handleFetchError(e){throw this.isFetchErrorFatal(e)?(this.fatalError=!0,this.close(),new o(`Connection failed with fatal error: ${e}. This is an unrecoverable error, retry attempts will be ignored.`)):e instanceof SyntaxError?new o(`Failed to parse the response from the server: ${e}`):new o(`Connection failed with error: ${e}.`)}prepareFatalResponseStatusError(e,t){let n=e??0;return new o(`${`Connection failed with status: ${e??`unknown`}`}${(t?.trim()?.length??0)>0?` (${t})`:``}. This is an unrecoverable error, retry attempts will be ignored.`,n)}isStatusFatal(e){return!!e&&e>=400&&e<500}isFetchErrorFatal(e){return e?.name===`NotAllowedError`?!0:e instanceof TypeError}get isConnected(){return this.pollingInterval!==void 0}close(){clearInterval(this.pollingInterval),this.pollingInterval=void 0}dispose(){this.close(),this.clear()}};const v=async(e,t,n,r)=>{let i=new AbortController,a=setTimeout(()=>{r.debug(`[fetchWithTimeout] Reached timeout, aborting request`),i.abort()},e);try{let e=await fetch(typeof t==`string`||t instanceof Request?t:t.toString(),{...n,signal:i.signal});return clearTimeout(a),e}catch(e){throw r.warn(`[fetchWithTimeout] Fetch error: `,e),clearTimeout(a),e}};var y=class extends _{constructor(e){super(),this.options=e,this.eventEmitter=new t,this.options=e,this.logger=e.logger,this.url=new URL(`server/polling/v1`,e.baseUrl),this.pollingIntervalSeconds=e.pollingInterval??60}async connect(e){return this.clearPollingInterval(),await this.fetchConfigs(e),this.schedulePollingInterval(()=>{this.fetchConfigs(e).catch(e=>{this.logger.error(`[PollingTransport] Error during polling:`,e)})}),this}async fetchConfigs(e){if(this.fatalError){this.logger.warn(`[PollingTransport] There was a prior unrecoverable error. Ignoring attempt to reconnect.`);return}try{let t=await v(e,this.url,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({metaContext:this.options.metaContext,serverSdkKey:this.options.serverSdkKey,lastUpdateTimestamp:this.lastUpdateTimestamp})},this.logger);if(await this.handleNonOkResponse(t),t.status===200){let e=JSON.parse(await t.text());this.lastUpdateTimestamp=e.timestamp,this.eventEmitter.emit(`configBundleReceived`,e)}}catch(e){this.handleFetchError(e)}}on(e,t){this.eventEmitter.on(e,t)}off(e,t){t?this.eventEmitter.off(e,t):this.eventEmitter.removeAllListeners(e)}clear(){this.eventEmitter.removeAllListeners()}},b=class extends y{constructor(e){super(e),this.pollingIntervalSeconds=0}};const ee=(e,t,n)=>{switch(t.toLowerCase()){case`=`:case`equals`:return e==n[0];case`!=`:case`does not equal`:return e!=n[0];case`is one of`:return n.includes(e);case`is not one of`:return!n.includes(e);case`starts with any of`:return n.findIndex(t=>e?.startsWith(t))>=0;case`does not start with any of`:return n.findIndex(t=>e?.startsWith(t))<0;case`ends with any of`:return n.findIndex(t=>e?.endsWith(t))>=0;case`does not end with any of`:return n.findIndex(t=>e?.endsWith(t))<0;case`matches regex`:return x(n[0],e);case`does not match regex`:return!x(n[0],e);default:return!1}},x=(e,t)=>{try{return new RegExp(e).test(t)}catch{return!1}},S=(e,t,n)=>{if(typeof e==`boolean`)return!1;let r=t.toLowerCase();if(e==null)return r==`!=`||r==`does not equal`;let{value:i,target:a}=C(e,n[0]);switch(r){case`=`:case`equals`:return i==a;case`!=`:case`does not equal`:return i!=a;case`<`:return i<a;case`<=`:return i<=a;case`>`:return i>a;case`>=`:return i>=a;default:return!1}},C=(e,t)=>typeof e==`number`?{value:e,target:w(t)}:typeof e==`bigint`?{value:e,target:BigInt(t)}:{value:w(e),target:w(t)},w=e=>{let t=Number.parseFloat(e.toString());if(isNaN(t)||!isFinite(t))throw TypeError(`Invalid numeric value: ${e}`);return t},T=(e,t,n)=>{if(e==null)return!1;try{let r=new Date(e),i=new Date(n[0]);switch(t){case`is after`:return r>i;case`is before`:return r<i;default:return!1}}catch{return!1}},E=(e,t,n)=>{let i=t.toLowerCase();if(e==null||e.trim().length==0)return i==`is not one of`;let a=r.coerce(e)??e,o=n.map(e=>r.coerce(e)??e);try{switch(i){case`=`:return D(a,o[0]);case`<`:return r.lt(a,o[0]);case`<=`:return r.lte(a,o[0]);case`>`:return r.gt(a,o[0]);case`>=`:return r.gte(a,o[0]);case`is one of`:return o.findIndex(e=>D(a,e))>=0;case`is not one of`:return o.findIndex(e=>D(a,e))<0;default:return!1}}catch{return!1}},D=(e,t)=>{try{return r.eq(e,t)}catch{return!1}},O=(e,t,n)=>{let r=t.toLowerCase();if(!Array.isArray(e))return r==`does not contain any of`;switch(r){case`contains any of`:return e.some(e=>n.includes(e));case`does not contain any of`:return!e.some(e=>n.includes(e));default:return!1}};var k=class{evaluate(e,t){if(!this.isConditionValidForComparison(e))return!1;switch(e.attribute){case`identifier`:return this.compare(t?.context?.id,e);case`name`:return this.compare(t?.context?.name,e);case`appName`:return this.compare(t?.metadata?.appName,e);case`appVersion`:return this.compare(t?.metadata?.appVersion,e);case`traits`:if(e.trait){let n=this.findTraitValue(t?.context?.traits,e.trait);return this.compare(n,e)}}return!1}compare(e,t){switch(t.targetType){case`text`:return ee(e?.toString(),t.operator,t.targetValues);case`number`:return S(e,t.operator,t.targetValues);case`datetime`:return T(e?.toString(),t.operator,t.targetValues);case`semver`:return E(e?.toString(),t.operator,t.targetValues);case`array`:return O(e,t.operator,t.targetValues)}return!1}findTraitValue(e,t){try{return n(t,e).val}catch{return}}isConditionValidForComparison(e){return Array.isArray(e.targetValues)&&e.targetValues.length>0}};const A=e=>{let t=`${e.contextIdentifier}-${e.configId}`;return Number(i(t,{seed:15426857351n})%1000n)/10};var j=class{constructor(e){if(this.logger=e,this.conditionEvaluator=new k,!e||!e.warn||!e.error||!e.info)throw TypeError(`The provided logger is not a valid 'ConfigDirectorLogger'`);this.logger=e}evaluate(e,t){return{id:e.id,key:e.key,type:e.type,value:this.getConfigValue(e,t)}}getConfigValue(e,t){let n=[...e.target?.rules??[]].sort((e,t)=>(e.order??2**53-1)-(t.order??2**53-1));for(let r of n){let n=this.evaluateRule(r,e,t);if(n.success)return n.value}return e.target?.defaultValue}evaluateRule(e,t,n){try{if(e.type==`percentage`)return this.evaluatePercentageRule(e,t,n);if(e.type==`conditional`)return this.evaluateConditionalRule(e,t,n)}catch(n){this.logger.warn(`There was an error while evaluating a targeting rule '${e?.id}' for '${t?.key}'. The rule will be disregarded.`,{error:n,configKey:t?.key,ruleId:e?.id})}return{success:!1}}evaluatePercentageRule(e,t,n){return this.evaluatePercentage(e.percentages??[],t,n)}evaluatePercentage(e,t,n){let r=A({configId:t.id,contextIdentifier:n?.context?.id??crypto.randomUUID()}),i=0,a=0,o;for(;!o&&i<e.length;)r<=e[i].percentage+a&&(o=e[i]),a+=e[i].percentage,i+=1;return o?.value==null?{success:!1}:{success:!0,value:o.value.toString()}}evaluateConditionalRule(e,t,n){let r=e.conditions??[],i=0,a;for(;!a&&i<r.length;)this.conditionEvaluator.evaluate(r[i],n)&&(a=r[i]),i+=1;return a&&e.target==`value`&&e.value!=null?{success:!0,value:e.value.toString()}:a&&e.target==`percentage`?this.evaluatePercentage(e.percentages??[],t,n):{success:!1}}};const M=e=>{let t=typeof e;if(t===`object`)try{return e.constructor?.name??t}catch{return t}else if(t===`function`)try{let n=e.name;return n?`function: ${n}`:t}catch{return t}return t},N=e=>e===`number`||e===`bigint`,P=(e,t)=>{let n=e.value,r=M(t);if(!n)return{parsedValue:t,parsedValueId:void 0,requestedType:r,usedDefault:!0,reason:`value-missing`};if(e.type===`json`)return R(n,t,r,e.valueId);if(typeof t==`string`)return{parsedValue:n,parsedValueId:e.valueId,requestedType:r,usedDefault:!1,reason:`found-match`};if(typeof t==`boolean`&&(e.type===`boolean`||e.type===`string`)){let i=F(n),a=typeof i==`boolean`;return{parsedValue:a?i:t,parsedValueId:a?e.valueId:void 0,requestedType:r,usedDefault:!a,reason:a?`found-match`:`invalid-boolean`}}if(N(typeof t)&&e.type===`integer`){let i=I(n),a=typeof i==`number`;return{parsedValue:a?i:t,parsedValueId:a?e.valueId:void 0,requestedType:r,usedDefault:!a,reason:a?`found-match`:`invalid-number`}}if(N(typeof t)&&(e.type===`float`||e.type===`enum`||e.type===`string`)){let i=L(n),a=typeof i==`number`;return{parsedValue:a?i:t,parsedValueId:a?e.valueId:void 0,requestedType:r,usedDefault:!a,reason:a?`found-match`:`invalid-number`}}return{parsedValue:n,parsedValueId:e.valueId,requestedType:r,usedDefault:!1,reason:`found-match`}},F=e=>{if(!e)return;let t=e.toLowerCase();if(!(t!=`true`&&t!=`false`))return t===`true`},I=e=>{if(!e)return;let t=Number.parseInt(e);if(!(isNaN(t)||!isFinite(t)))return t},L=e=>{if(!e)return;let t=Number.parseFloat(e);if(!(isNaN(t)||!isFinite(t)))return t},R=(e,t,n,r)=>{if(typeof t==`string`)return{parsedValue:e,parsedValueId:r,requestedType:n,usedDefault:!1,reason:`found-match`};try{return{parsedValue:JSON.parse(e),parsedValueId:r,requestedType:n,usedDefault:!1,reason:`found-match`}}catch{return{parsedValue:t,parsedValueId:void 0,requestedType:n,usedDefault:!0,reason:`invalid-json`}}},z={off:-1,error:0,warn:1,info:2,debug:3},B=()=>{let e={year:`numeric`,month:`2-digit`,day:`2-digit`,hour:`2-digit`,minute:`2-digit`,second:`2-digit`,timeZoneName:`short`};try{return new Intl.DateTimeFormat(void 0,{...e,fractionalSecondDigits:3})}catch{return new Intl.DateTimeFormat(void 0,e)}};var te=class{constructor(e,t){this.level=e,this.decorator=t,this.level=e,this.decorator=t,this.dateFormatter=B()}debug(e,...t){this.log(console.debug,`debug`,e,...t)}info(e,...t){this.log(console.info,`info`,e,...t)}warn(e,...t){this.log(console.warn,`warn`,e,...t)}error(e,...t){this.log(console.error,`error`,e,...t)}log(e,t,n,...r){z[this.level]>=z[t]&&e(`[${this.dateFormatter.format(new Date)}] ${this.decorator?.decorateMessage(n)}`,...r)}},V=class{decorateMessage(e){return`[ConfigDirector:js-server-sdk] ${e}`}};const H=(e,t)=>new te(e??`warn`,t??new V),U=(e,t)=>new URL(e,t?.toString()),W=async(e,t)=>e.valueId?{valueId:e.valueId}:e.value?e.value.length<=500&&e.type!=`json`?{value:e.value}:{valueId:await t(e.value)}:e,G=e=>{if(e.type===`json`||e.type==null&&typeof e.value==`object`){if(e.valueId)return{valueId:e.valueId,type:`json`};try{return{value:JSON.stringify(e.value),type:`json`}}catch{return{value:e.value.toString(),type:`json`}}}let t=e.value?.toString();return t&&t.length<=500?{value:t}:e.valueId?{valueId:e.valueId}:{value:t}},K=e=>{let t=Object.keys(e);if(t.length==0)return!0;for(let n of t)if((e[n]?.length??0)>0)return!1;return!0},q=e=>{if(!e)return!0;let t=Object.keys(e);if(t.length==0)return!0;for(let n of t)if((e[n]??0)>0)return!1;return!0};var J=class{constructor(e){this.executeRequests=!0,this.sdkKey=e.sdkKey,this.logger=e.logger,this.url=new URL(`server/telemetry/v1`,e.baseUrl.toString())}async report({discreteEvents:e,aggregatedEvents:t,droppedEvents:n}){if(!this.executeRequests)return{success:!1,fatalError:!0};let r={serverSdkKey:this.sdkKey,discreteEvents:e,aggregatedEvents:t,droppedEvents:n};if(this.isReportEmpty(r))return{success:!0,fatalError:!1};let i=await this.sendReport(r);return i.fatalError&&(this.executeRequests=!1),i}isReportEmpty(e){return K(e.discreteEvents)&&K(e.aggregatedEvents)&&q(e.droppedEvents)}async sendReport(e){try{let t=await v(5e3,this.url,{method:`POST`,body:JSON.stringify(e)},this.logger),n=this.isStatusFatal(t.status);return n&&this.logger.warn(`[EventReporter] Received a fatal status response (${t.status}) from the telemetry endpoint. No more telemetry data will be sent.`),{success:t.ok,fatalError:n}}catch(e){return this.logger.warn(`[EventReporter] Error attempting to send telemetry data: ${e}`,{error:e}),{success:!1,fatalError:!1}}}isStatusFatal(e){return!!e&&e>=400&&e<500}},Y=class{aggregate(e){if(e.events.length==0)return[];let t=new Map;for(let n of e.events){let e=JSON.stringify(n),r=t.get(e)?.count||0;t.set(e,{event:n,count:r+1})}return Array.from(t).map(([,t])=>({startTime:e.startTime,endTime:e.endTime,count:t.count,event:t.event}))}},X=class{constructor(e){this._events=[],this._droppedEventCount=0,this.limit=e??1e3}get events(){return this._events}get reachedLimit(){return this._events.length>=this.limit}get droppedEventCount(){return this._droppedEventCount}push(...e){this.startTime||(this.startTime=new Date);let t=Math.max(0,e.length-this.limit),n=e.slice(t,e.length);if(this._droppedEventCount+=t,this._events.length+n.length>this.limit){let e=this._events.length+n.length-this.limit;this._events.splice(0,e),this._droppedEventCount+=e}return this._events.push(...n)}takeSnapshot(){let e=new Date,t=this.startTime??e,n=this._events.splice(0),r=this._droppedEventCount;return this.startTime=void 0,this._droppedEventCount=0,{startTime:t,endTime:e,events:n,droppedCount:r}}clear(){this._events=[],this.startTime=void 0,this._droppedEventCount=0}},Z=class{constructor(e){this.aggregator=new Y,this.collectEvents=!0,this.logger=e.logger,this.evaluationEventQueue=new X(e.evaluationQueueLimit??1e3),this.flushIntervalDelay=e.flushIntervalDelay??3e4;let t=e.initialFlushIntervalDelay??5e3;this.flushTimeout=setTimeout(()=>this.flushAndScheduleNext(),t)}async flushAndScheduleNext(){(await this.flush()).fatalError?(this.collectEvents=!1,this.close(),this.logger.warn(`[TelemetryEventCollector] Received a fatal error from telemetry collection. No longer collecting events.`)):this.flushTimeout=setTimeout(()=>this.flushAndScheduleNext(),this.flushIntervalDelay)}cancelScheduledFlush(){clearTimeout(this.flushTimeout)}async close(){this.collectEvents=!1,clearTimeout(this.flushTimeout),await this.flush(),this.evaluationEventQueue.clear()}dispose(){this.close()}},Q=class extends Map{constructor(e,t){super(t),this._droppedCount=0,this.limit=e,this.enforceLimit()}set(e,t){return super.set(e,t),this.enforceLimit(),this}get droppedCount(){return this._droppedCount}clearAndReset(){this.clear(),this._droppedCount=0}enforceLimit(){for(;this.size>this.limit;){let e=this.keys().next().value;e!==void 0&&(this.delete(e),this._droppedCount+=1)}}},ne=class{constructor(e){this.valueIdGenerator=e,this.valueIdGenerator=e}async process(e){let t=await Promise.all(e.events.map(async e=>({...e,defaultValue:await W(e.defaultValue,this.valueIdGenerator),evaluatedValue:await W(e.evaluatedValue,this.valueIdGenerator)})));return{...e,events:t}}},re=class extends Z{constructor(e){super(e),this.reporter=new J(e),this.contexts=new Q(e.contextLimit??1e3),this.evaluationEventSnapshotPreprocessor=new ne(e.valueIdGenerator)}evaluatedConfig(e){if(!this.collectEvents)return;e.context?.id&&!e.context.anonymous&&this.contexts.set(e.context.id,e.context);let t={...e.evaluation,contextId:e.context?.anonymous?void 0:e.context?.id};this.evaluationEventQueue.push(this.sanitizeEvaluatedConfigEvent(t))}sanitizeEvaluatedConfigEvent(e){return{...e,defaultValue:G({value:e.defaultValue,type:e.type}),evaluatedValue:G({value:e.evaluatedValue,valueId:e.evaluatedValueId,type:e.type})}}snapshotContexts(){let e=Array.from(this.contexts.values()),t=this.contexts.droppedCount;return this.contexts.clearAndReset(),{contexts:e,droppedCount:t}}async flush(){if(!this.reporter)return{success:!1,fatalError:!1};let e=await this.evaluationEventSnapshotPreprocessor.process(this.evaluationEventQueue.takeSnapshot()),t=this.snapshotContexts();return await this.reporter?.report({context:this._context,discreteEvents:{capturedContexts:t.contexts},aggregatedEvents:{evaluatedConfig:this.aggregator.aggregate(e)},droppedEvents:{evaluatedConfig:e.droppedCount,capturedContexts:t.droppedCount}})}};const ie=(e,t)=>{let n=BigInt(0),r=BigInt(62),i=Array.from(e).map(e=>e.toString(16).padStart(2,`0`)).join(``),a=BigInt(`0x`+i),o=``;for(;a>n;)o=`0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz`[Number(a%r)]+o,a/=r;return o.padStart(t,`0`)},ae=Math.ceil(128/Math.log2(62)),oe=async e=>ie(a(`sha256`).update(e?.toString()??``).digest().subarray(0,16),ae),$=new URL(`https://server-sdk-api.configdirector.com`);var se=class{constructor(t,n,r){this.handlersMap=new Map,this.eventEmitter=new e,this.ready=!1,this.logger=r?.logger??H(),this.timeout=r?.connection?.timeout??3e3,this.metaContext=r?.metadata??{},this.validateSdkKeyPresence(t),this.configEvaluator=new j(this.logger);let i=this.parseUrl(r?.connection?.url)??$;this.connectionMode=r?.connection?.mode??`streaming`;let a=this.getTransportConstructor(this.connectionMode),o=r?.telemetry?.eventQueueLimit??5e3;this.usageEventCollector=new re({sdkKey:t,logger:this.logger,baseUrl:i,flushIntervalDelay:r?.telemetry?.flushInterval??3e4,evaluationQueueLimit:Math.ceil(o*.7),contextLimit:Math.floor(o*.3),urlFactory:U,valueIdGenerator:oe}),this.transport=new a({serverSdkKey:t,baseUrl:i,metaContext:{...r?.metadata,sdkName:n.sdkName,sdkVersion:n.sdkVersion},logger:this.logger,pollingInterval:r?.connection?.pollingInterval}),this.transport.on(`configBundleReceived`,e=>{this.readyResolve?.();let t=Object.keys(e.configs);!this.configSet||e.kind==`full`?(this.configSet=e,this.eventEmitter.emit(`configsUpdated`,{keys:t}),this.updateWatchers(e.configs)):(this.configSet.configs={...this.configSet.configs,...e.configs},this.eventEmitter.emit(`configsUpdated`,{keys:t}),this.updateWatchers(e.configs)),this.logger.debug(`[ConfigDirectorClient] ConfigSet updated from server with ${t.length} key(s)`,{keys:t})}),this.registerHandler(`clientReady`,r?.hooks?.clientReady),this.registerHandler(`configEvaluated`,r?.hooks?.configEvaluated),this.registerHandler(`configsUpdated`,r?.hooks?.configsUpdated)}registerHandler(e,t){if(!t)return;let n=Array.isArray(t)?t:[t];for(let t of n)typeof t==`function`&&this.on(e,t)}async initialize(){try{this.ready=!1,this.readyPromise=new Promise(e=>{this.readyResolve=e}).then(()=>{this.ready=!0,this.eventEmitter.emit(`clientReady`),this.logger.debug(`[ConfigDirectorClient] Received initial payload from the server, client is ready`)});let e=new Date().getTime();await this.transport.connect(this.timeout);let t=new Date().getTime()-e,n=this.timeout-t;if(n>0&&await Promise.race([this.readyPromise,new Promise(e=>{setTimeout(()=>e(),n)})]),!this.ready){let e=this.connectionMode===`streaming`?`The client will continue to retry since there were no fatal errors detected. Configs will return the default value until the connection succeeds.`:`Since the client was configured without streaming, configs may not update and always return the default value.`;this.logger.warn(`[ConfigDirectorClient] Timed out waiting for initialization after ${this.timeout}ms. ${e}`)}}catch(e){this.logger.error(`[ConfigDirectorClient] An error occurred during initialization: `,e)}}getTransportConstructor(e){switch(e){case`one-time`:return b;case`polling`:return y;default:return g}}updateWatchers(e){Object.values(e).forEach(e=>this.updateWatchersForConfig(e))}updateWatchersForConfig(e){this.handlersMap.get(e.key)?.forEach(t=>{let n=this.getValueFromConfigDefinition(e.key,e,t.defaultValue,t.context);t.handler(n)})}watch(e,t,n,r){this.validateDefaultValue(t);let i=this.handlersMap.get(e),a={handler:n,defaultValue:t,requestedType:typeof t,context:r};return i?i.push(a):this.handlersMap.set(e,[a]),()=>this.unwatch(e,n)}unwatch(e,t){let n=this.handlersMap.get(e);if(n)if(t){let e=n.findIndex(e=>e.handler==t);e>=0&&n?.splice(e,1)}else n.splice(0)}getValue(e,t,n){this.validateDefaultValue(t);let r=this.configSet?.configs[e];return this.getValueFromConfigDefinition(e,r,t,n)}getValueFromConfigDefinition(e,t,n,r){if(t){let i=this.configEvaluator.evaluate(t,{context:r,metadata:this.metaContext}),a=P(i,n);return this.usageEventCollector.evaluatedConfig({context:r,evaluation:{key:e,type:i.type,defaultValue:n,requestedType:a.requestedType,evaluatedValue:a.parsedValue,evaluatedValueId:a.parsedValueId,usedDefault:a.usedDefault,evaluationReason:a.reason}}),this.dispatchEvaluationEvent({key:e,value:a.parsedValue,valueId:a.parsedValueId??void 0,isDefaultValue:a.usedDefault,reason:a.reason,context:r}),this.logger.debug(`[ConfigDirectorClient] Evaluated '${e}' to '${a.parsedValue}'`),a.parsedValue}else{this.logger.debug(`[ConfigDirectorClient] No config state found for '${e}', returning default value '${n}'`);let t=this.isReady?`config-state-missing`:`client-not-ready`;return this.usageEventCollector.evaluatedConfig({context:r,evaluation:{key:e,defaultValue:n,requestedType:M(n),evaluatedValue:n,usedDefault:!0,evaluationReason:t}}),this.dispatchEvaluationEvent({key:e,value:n,isDefaultValue:!0,reason:t,context:r}),n}}dispatchEvaluationEvent(e){setTimeout(()=>this.eventEmitter.emit(`configEvaluated`,{evaluation:e}),0)}parseUrl(e){if(e)try{return new URL(e)}catch(t){throw new s(`Invalid base URL '${e}'. Parsing failed: ${t}`)}}validateSdkKeyPresence(e){if(!e||e.trim().length==0)throw new s(`No server SDK key was provided, the client cannot be instantiated without a valid server SDK key`)}validateDefaultValue(e){if(e==null)throw new s(`Invalid default value. The default value for a config must be defined and non-null.`);if(typeof e==`function`)throw new s(`Invalid default value. The default value for a config cannot be a function.`)}get isReady(){return this.ready}on(e,t){this.eventEmitter.on(e,t)}off(e,t){t?this.eventEmitter.off(e,t):this.eventEmitter.removeAllListeners(e)}removeAllObservers(){this.logger.debug(`[ConfigDirectorClient] removeAllObservers() has been called, removing all observers`),this.eventEmitter.removeAllListeners(),this.handlersMap.clear()}unwatchAll(){this.handlersMap.clear()}getAllConfigs(e){if(!this.configSet)return{};let t=Object.entries(this.configSet.configs),n=e?.configKeys?t.filter(([t])=>e.configKeys?.includes(t)):t;return Object.fromEntries(n.map(([t,n])=>[t,this.configEvaluator.evaluate(n,{context:e?.context,metadata:this.metaContext})]))}closeConnection(){this.logger.debug(`[ConfigDirectorClient] closeConnection() has been called, closing connection to server`),this.transport.close(),this.ready=!1}dispose(){this.removeAllObservers(),this.closeConnection()}};const ce=(e,t)=>new se(e,{sdkName:`js-server-sdk`,sdkVersion:`0.9.2`},t),le=(e,t)=>H(e,t);export{ce as createClient,le as createConsoleLogger};
|
|
6
6
|
//# sourceMappingURL=configdirector-server.mjs.map
|
package/package.json
CHANGED