@configdirector/nuxt-sdk 1.5.0 → 1.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/module.json CHANGED
@@ -4,5 +4,5 @@
4
4
  "compatibility": {
5
5
  "nuxt": ">=4.0.0"
6
6
  },
7
- "version": "1.5.0"
7
+ "version": "1.6.1"
8
8
  }
@@ -6938,9 +6938,6 @@ const equals = (value, target) => value !== null && target !== null && semver.eq
6938
6938
  const ordered = (value, target, compare) => value !== null && target !== null && compare(value, target);
6939
6939
  //#endregion
6940
6940
  //#region ../config-evaluator/src/render.ts
6941
- /**
6942
- * Rendering a resolved context value to text.
6943
- */
6944
6941
  /** A value the context does not carry. Compared as "", so a negative operator can still match. */
6945
6942
  const ABSENT = Symbol("absent");
6946
6943
  /** An attribute this SDK version does not know about. Not compared at all. */
@@ -6962,6 +6959,12 @@ const renderScalar = (value) => {
6962
6959
  };
6963
6960
  /** The raw value, for comparisons that inspect its JSON type rather than its text. */
6964
6961
  const unwrap = (value) => value === ABSENT ? void 0 : value;
6962
+ const resolvedTypeOf = (value) => {
6963
+ if (value === UNKNOWN_ATTRIBUTE) return "unknown-attribute";
6964
+ if (value === ABSENT) return "absent";
6965
+ if (isScalar(value)) return "scalar";
6966
+ return Array.isArray(value) ? "array" : "object";
6967
+ };
6965
6968
  //#endregion
6966
6969
  //#region ../config-evaluator/src/array-comparison.ts
6967
6970
  /**
@@ -6992,8 +6995,28 @@ var ConditionEvaluator = class {
6992
6995
  * a condition can still match or not match on its own terms.
6993
6996
  */
6994
6997
  evaluate(condition, context) {
6998
+ return this.explain(condition, context).matched;
6999
+ }
7000
+ /**
7001
+ * Decide whether a condition holds for a context, and say what the condition was compared
7002
+ * against: the raw resolved value and its shape.
7003
+ */
7004
+ explain(condition, context) {
6995
7005
  const value = this.resolve(condition, context);
6996
- if (value === UNKNOWN_ATTRIBUTE) return false;
7006
+ const resolvedType = resolvedTypeOf(value);
7007
+ const resolvedValue = value === ABSENT || value === UNKNOWN_ATTRIBUTE ? void 0 : value;
7008
+ if (value === UNKNOWN_ATTRIBUTE) return {
7009
+ matched: false,
7010
+ resolvedValue,
7011
+ resolvedType
7012
+ };
7013
+ return {
7014
+ matched: this.compare(condition, value),
7015
+ resolvedValue,
7016
+ resolvedType
7017
+ };
7018
+ }
7019
+ compare(condition, value) {
6997
7020
  const targetValues = condition.targetValues ?? [];
6998
7021
  switch (condition.targetType) {
6999
7022
  case "text": return compareText(render(value), condition.operator, targetValues);
@@ -7047,61 +7070,130 @@ var ConfigEvaluator = class {
7047
7070
  id: config.id,
7048
7071
  key: config.key,
7049
7072
  type: config.type,
7050
- value: this.getConfigValue(config, context)
7073
+ value: this.explain(config, context).value
7051
7074
  };
7052
7075
  }
7053
- getConfigValue(config, context) {
7076
+ /**
7077
+ * Evaluate a config for a context and record how the value was reached: every rule in the
7078
+ * order it was walked with its outcome, the conditions that were checked with what they
7079
+ * resolved to, and the share a rollout assigned the context to. `evaluate` is this walk with
7080
+ * only the value kept, so the two cannot disagree.
7081
+ */
7082
+ explain(config, context) {
7054
7083
  const rules = [...config.target?.rules ?? []].sort((a, b) => (a.order ?? Number.MAX_SAFE_INTEGER) - (b.order ?? Number.MAX_SAFE_INTEGER));
7084
+ const explanations = [];
7085
+ let served;
7055
7086
  for (const rule of rules) {
7056
- const result = this.evaluateRule(rule, config, context);
7057
- if (result.success) return result.value;
7087
+ if (served) {
7088
+ explanations.push({
7089
+ ruleId: rule.id,
7090
+ outcome: "not-evaluated",
7091
+ conditions: [],
7092
+ bucket: void 0
7093
+ });
7094
+ continue;
7095
+ }
7096
+ const result = this.explainRule(rule, config, context);
7097
+ explanations.push(result.explanation);
7098
+ if (result.value !== void 0) served = {
7099
+ ruleId: rule.id,
7100
+ value: result.value
7101
+ };
7058
7102
  }
7059
- return config.target?.defaultValue;
7103
+ return {
7104
+ value: served?.value ?? config.target?.defaultValue,
7105
+ servedBy: served ? {
7106
+ kind: "rule",
7107
+ ruleId: served.ruleId
7108
+ } : { kind: "default" },
7109
+ rules: explanations
7110
+ };
7060
7111
  }
7061
- evaluateRule(rule, config, context) {
7112
+ explainRule(rule, config, context) {
7113
+ const explanation = {
7114
+ ruleId: rule.id,
7115
+ outcome: "not-matched",
7116
+ conditions: [],
7117
+ bucket: void 0
7118
+ };
7062
7119
  try {
7063
- if (rule.type == "percentage") return this.evaluatePercentageRule(rule, config, context);
7064
- else if (rule.type == "conditional") return this.evaluateConditionalRule(rule, config, context);
7120
+ let value;
7121
+ if (rule.type == "percentage") value = this.explainPercentage(rule.percentages ?? [], config, context, explanation);
7122
+ else if (rule.type == "conditional") value = this.explainConditionalRule(rule, config, context, explanation);
7123
+ if (value !== void 0) explanation.outcome = "matched";
7124
+ return {
7125
+ explanation,
7126
+ value
7127
+ };
7065
7128
  } catch (error) {
7066
7129
  this.logger.warn(`There was an error while evaluating a targeting rule '${rule?.id}' for '${config?.key}'. The rule will be disregarded.`, {
7067
7130
  error,
7068
7131
  configKey: config?.key,
7069
7132
  ruleId: rule?.id
7070
7133
  });
7134
+ return {
7135
+ explanation: {
7136
+ ruleId: rule.id,
7137
+ outcome: "errored",
7138
+ conditions: [],
7139
+ bucket: void 0
7140
+ },
7141
+ value: void 0
7142
+ };
7071
7143
  }
7072
- return { success: false };
7073
- }
7074
- evaluatePercentageRule(rule, config, context) {
7075
- return this.evaluatePercentage(rule.percentages ?? [], config, context);
7076
7144
  }
7077
- evaluatePercentage(percentages, config, context) {
7145
+ explainPercentage(percentages, config, context, explanation) {
7146
+ const contextIdentifier = context?.context?.id;
7147
+ const identifier = contextIdentifier ?? crypto.randomUUID();
7078
7148
  const assignedPercentage = assignPercentage({
7079
7149
  configId: config.id,
7080
- contextIdentifier: context?.context?.id ?? crypto.randomUUID()
7150
+ contextIdentifier: identifier
7081
7151
  });
7152
+ const shares = [];
7082
7153
  let sum = 0;
7083
- let bucket = void 0;
7154
+ let selected = void 0;
7084
7155
  for (const percentage of percentages) {
7085
- if (assignedPercentage < percentage.percentage + sum) {
7086
- bucket = percentage;
7087
- break;
7088
- }
7089
- sum += percentage.percentage;
7156
+ const to = sum + percentage.percentage;
7157
+ shares.push({
7158
+ percentageId: percentage.id,
7159
+ from: sum,
7160
+ to,
7161
+ value: percentage.value?.toString()
7162
+ });
7163
+ if (selected === void 0 && assignedPercentage < to) selected = percentage;
7164
+ sum = to;
7090
7165
  }
7091
- if (bucket?.value != null) return {
7092
- success: true,
7093
- value: bucket.value.toString()
7094
- };
7095
- return { success: false };
7096
- }
7097
- evaluateConditionalRule(rule, config, context) {
7098
- const conditionsMet = (rule.conditions ?? []).every((candidate) => this.conditionEvaluator.evaluate(candidate, context));
7099
- if (conditionsMet && rule.target == "value" && rule.value != null) return {
7100
- success: true,
7101
- value: rule.value.toString()
7166
+ explanation.bucket = {
7167
+ identifier,
7168
+ identifierWasGenerated: contextIdentifier == null,
7169
+ assignedPercentage,
7170
+ shares,
7171
+ selectedPercentageId: selected?.id
7102
7172
  };
7103
- else if (conditionsMet && rule.target == "percentage") return this.evaluatePercentage(rule.percentages ?? [], config, context);
7104
- return { success: false };
7173
+ return selected?.value != null ? selected.value.toString() : void 0;
7174
+ }
7175
+ explainConditionalRule(rule, config, context, explanation) {
7176
+ let failed = false;
7177
+ for (const condition of rule.conditions ?? []) {
7178
+ if (failed) {
7179
+ explanation.conditions.push({
7180
+ conditionId: condition.id,
7181
+ outcome: "not-evaluated"
7182
+ });
7183
+ continue;
7184
+ }
7185
+ const check = this.conditionEvaluator.explain(condition, context);
7186
+ explanation.conditions.push({
7187
+ conditionId: condition.id,
7188
+ outcome: check.matched ? "matched" : "not-matched",
7189
+ resolvedValue: check.resolvedValue,
7190
+ resolvedType: check.resolvedType
7191
+ });
7192
+ failed = !check.matched;
7193
+ }
7194
+ if (failed) return;
7195
+ if (rule.target == "value") return rule.value != null ? rule.value.toString() : void 0;
7196
+ if (rule.target == "percentage") return this.explainPercentage(rule.percentages ?? [], config, context, explanation);
7105
7197
  }
7106
7198
  };
7107
7199
  //#endregion
@@ -7124,6 +7216,11 @@ const getRequestedType = (defaultValue) => {
7124
7216
  const isNumericNativeType = (requestedType) => {
7125
7217
  return requestedType === "number" || requestedType === "bigint";
7126
7218
  };
7219
+ const configTypesNotReadableAsString = new Set([
7220
+ "boolean",
7221
+ "integer",
7222
+ "float"
7223
+ ]);
7127
7224
  const parseConfigValue = (configState, defaultValue) => {
7128
7225
  const value = configState.value;
7129
7226
  const requestedType = getRequestedType(defaultValue);
@@ -7135,13 +7232,22 @@ const parseConfigValue = (configState, defaultValue) => {
7135
7232
  reason: "value-missing"
7136
7233
  };
7137
7234
  if (configState.type === "json") return parseJson(value, defaultValue, requestedType, configState.valueId);
7138
- if (typeof defaultValue === "string") return {
7139
- parsedValue: value,
7140
- parsedValueId: configState.valueId,
7141
- requestedType,
7142
- usedDefault: false,
7143
- reason: "found-match"
7144
- };
7235
+ if (typeof defaultValue === "string") {
7236
+ if (configTypesNotReadableAsString.has(configState.type)) return {
7237
+ parsedValue: defaultValue,
7238
+ parsedValueId: void 0,
7239
+ requestedType,
7240
+ usedDefault: true,
7241
+ reason: "type-mismatch"
7242
+ };
7243
+ return {
7244
+ parsedValue: value,
7245
+ parsedValueId: configState.valueId,
7246
+ requestedType,
7247
+ usedDefault: false,
7248
+ reason: "found-match"
7249
+ };
7250
+ }
7145
7251
  if (typeof defaultValue === "boolean" && (configState.type === "boolean" || configState.type === "string" || configState.type === "enum")) {
7146
7252
  const boolValue = parseConfigBoolean(value);
7147
7253
  const hasBoolean = typeof boolValue === "boolean";
@@ -7363,6 +7469,7 @@ const isDroppedEventsEmpty = (droppedEvents) => {
7363
7469
  var ServerEventReporter = class {
7364
7470
  sdkKey;
7365
7471
  sdkIdentity;
7472
+ metaContext;
7366
7473
  logger;
7367
7474
  url;
7368
7475
  executeRequests = true;
@@ -7370,6 +7477,7 @@ var ServerEventReporter = class {
7370
7477
  this.sdkKey = options.sdkKey;
7371
7478
  this.sdkIdentity = options.sdkIdentity;
7372
7479
  this.logger = options.logger;
7480
+ this.metaContext = options.metaContext;
7373
7481
  this.url = new URL("server/telemetry/v1", options.baseUrl.toString());
7374
7482
  }
7375
7483
  async report({ discreteEvents, aggregatedEvents, droppedEvents }) {
@@ -7381,7 +7489,9 @@ var ServerEventReporter = class {
7381
7489
  serverSdkKey: this.sdkKey,
7382
7490
  metaContext: {
7383
7491
  sdkName: this.sdkIdentity.sdkName,
7384
- sdkVersion: this.sdkIdentity.sdkVersion
7492
+ sdkVersion: this.sdkIdentity.sdkVersion,
7493
+ appName: this.metaContext.appName,
7494
+ appVersion: this.metaContext.appVersion
7385
7495
  },
7386
7496
  discreteEvents,
7387
7497
  aggregatedEvents,
@@ -7721,6 +7831,7 @@ var DefaultConfigDirectorClient = class {
7721
7831
  this.usageEventCollector = new ServerTelemetryEventCollector({
7722
7832
  sdkKey: serverSdkKey,
7723
7833
  sdkIdentity: sdkOptions,
7834
+ metaContext: clientOptions?.metadata ?? {},
7724
7835
  logger: this.logger,
7725
7836
  baseUrl,
7726
7837
  flushIntervalDelay: clientOptions?.telemetry?.flushInterval ?? DEFAULT_FLUSH_INTERVAL,
@@ -7970,7 +8081,7 @@ var plugin_default = defineNitroPlugin((nitroApp) => {
7970
8081
  const { serverSdkKey, baseUrl, connection } = runtimeConfig.configdirector;
7971
8082
  const client = new DefaultConfigDirectorClient(serverSdkKey, {
7972
8083
  sdkName: "nuxt-sdk",
7973
- sdkVersion: "1.5.0"
8084
+ sdkVersion: "1.6.1"
7974
8085
  }, {
7975
8086
  logger,
7976
8087
  connection: {
@@ -504,6 +504,11 @@ const getRequestedType = (defaultValue) => {
504
504
  const isNumericNativeType = (requestedType) => {
505
505
  return requestedType === "number" || requestedType === "bigint";
506
506
  };
507
+ const configTypesNotReadableAsString = new Set([
508
+ "boolean",
509
+ "integer",
510
+ "float"
511
+ ]);
507
512
  const parseConfigValue = (configState, defaultValue) => {
508
513
  const value = configState.value;
509
514
  const requestedType = getRequestedType(defaultValue);
@@ -515,13 +520,22 @@ const parseConfigValue = (configState, defaultValue) => {
515
520
  reason: "value-missing"
516
521
  };
517
522
  if (configState.type === "json") return parseJson(value, defaultValue, requestedType, configState.valueId);
518
- if (typeof defaultValue === "string") return {
519
- parsedValue: value,
520
- parsedValueId: configState.valueId,
521
- requestedType,
522
- usedDefault: false,
523
- reason: "found-match"
524
- };
523
+ if (typeof defaultValue === "string") {
524
+ if (configTypesNotReadableAsString.has(configState.type)) return {
525
+ parsedValue: defaultValue,
526
+ parsedValueId: void 0,
527
+ requestedType,
528
+ usedDefault: true,
529
+ reason: "type-mismatch"
530
+ };
531
+ return {
532
+ parsedValue: value,
533
+ parsedValueId: configState.valueId,
534
+ requestedType,
535
+ usedDefault: false,
536
+ reason: "found-match"
537
+ };
538
+ }
525
539
  if (typeof defaultValue === "boolean" && (configState.type === "boolean" || configState.type === "string" || configState.type === "enum")) {
526
540
  const boolValue = parseConfigBoolean(value);
527
541
  const hasBoolean = typeof boolValue === "boolean";
@@ -1266,13 +1280,14 @@ var WebWorkerTelemetryClient = class {
1266
1280
  payload: {
1267
1281
  sdkKey: options.sdkKey,
1268
1282
  sdkIdentity: options.sdkIdentity,
1283
+ metaContext: options.metaContext,
1269
1284
  baseUrl: options.baseUrl.toString(),
1270
1285
  evaluationQueueLimit: options.evaluationQueueLimit ?? 1e3,
1271
1286
  initialFlushIntervalDelay: options.initialFlushIntervalDelay ?? 5e3,
1272
1287
  flushIntervalDelay: options.flushIntervalDelay ?? 3e4
1273
1288
  }
1274
1289
  };
1275
- this.worker = new Worker(URL.createObjectURL(new Blob(["(function(){let e=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,t=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},n=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},r=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}},i=e=>e?.name===`NotAllowedError`?!0:e instanceof TypeError;var a=class{constructor(e){this.executeRequests=!0,this.sdkKey=e.sdkKey,this.logger=e.logger,this.sdkIdentity=e.sdkIdentity,this.url=e.urlFactory(`client/telemetry/v1`,e.baseUrl)}async report({context:e,discreteEvents:t,aggregatedEvents:n,droppedEvents:r}){if(!this.executeRequests)return{success:!1,fatalError:!0};let i={clientSdkKey:this.sdkKey,metaContext:{sdkName:this.sdkIdentity.sdkName,sdkVersion:this.sdkIdentity.sdkVersion},context:e,discreteEvents:t,aggregatedEvents:n,droppedEvents:r};if(this.isReportEmpty(i))return{success:!0,fatalError:!1};let a=await this.sendReport(i);return a.fatalError&&(this.executeRequests=!1),a}isReportEmpty(e){return t(e.discreteEvents)&&t(e.aggregatedEvents)&&n(e.droppedEvents)}async sendReport(e){try{let t=await r(5e3,this.url,{method:`POST`,body:JSON.stringify(e),keepalive:!0},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){let t=i(e);return this.logger.warn(`[EventReporter] Error attempting to send telemetry data: ${e}`,{error:e}),{success:!1,fatalError:t}}}isStatusFatal(e){return!!e&&e>=400&&e<500&&e!=429}},o=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}))}},s=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}},c=class{constructor(e){this.aggregator=new o,this.collectEvents=!0,this.logger=e.logger,this.evaluationEventQueue=new s(e.evaluationQueueLimit??1e3),this.flushIntervalDelay=e.flushIntervalDelay??3e4;let t=e.initialFlushIntervalDelay??5e3;this.flushTimeout=setTimeout(()=>this.flushAndScheduleNext(),t)}async flushAndScheduleNext(){if(!this.collectEvents)return;let e;try{e=await this.flush()}catch(t){this.logger.warn(`[TelemetryEventCollector] Error flushing telemetry events: `,t),e={success:!1,fatalError:!1}}e.fatalError?(this.collectEvents=!1,this.close(),this.logger.warn(`[TelemetryEventCollector] Received a fatal error from telemetry collection. No longer collecting events.`)):this.collectEvents&&(this.cancelScheduledFlush(),this.flushTimeout=setTimeout(()=>this.flushAndScheduleNext(),this.flushIntervalDelay))}cancelScheduledFlush(){clearTimeout(this.flushTimeout)}async close(){this.collectEvents=!1,clearTimeout(this.flushTimeout);try{await this.flush()}catch(e){this.logger.warn(`[TelemetryEventCollector] Error flushing telemetry events during close: `,e)}this.evaluationEventQueue.clear()}dispose(){this.close()}},l=class{constructor(e){this.valueIdGenerator=e,this.valueIdGenerator=e}async process(t){let n=await Promise.all(t.events.map(async t=>({...t,defaultValue:await e(t.defaultValue,this.valueIdGenerator),evaluatedValue:await e(t.evaluatedValue,this.valueIdGenerator)})));return{...t,events:n}}},u=class extends c{constructor(e){super(e),this.reporter=new a(e),this.evaluationEventSnapshotPreprocessor=new l(e.valueIdGenerator)}async updateContext(e){this.cancelScheduledFlush(),await this.flush(),this._context=e,await this.flushAndScheduleNext()}evaluatedConfig(e){this.collectEvents&&this.evaluationEventQueue.push(e)}async forceFlush(){this.cancelScheduledFlush(),await this.flushAndScheduleNext()}async flush(){if(!this.reporter)return{success:!1,fatalError:!1};let e=await this.evaluationEventSnapshotPreprocessor.process(this.evaluationEventQueue.takeSnapshot());return await this.reporter?.report({context:this._context,discreteEvents:{},aggregatedEvents:{evaluatedConfig:this.aggregator.aggregate(e)},droppedEvents:{evaluatedConfig:e.droppedCount}})}};let d=(e,t)=>new URL(e,t?.toString()),f=(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`)},p=Math.ceil(128/Math.log2(62)),m=new TextEncoder,h=async e=>{if(typeof crypto>`u`||crypto.subtle==null)return;let t=await crypto.subtle.digest(`SHA-256`,m.encode(e?.toString()??``));return f(new Uint8Array(t,0,16),p)},g,_=(e,t,...n)=>({type:`Log`,payload:{level:e,message:t,args:n}}),v=e=>{try{self.postMessage(e)}catch{self.postMessage(_(e.payload.level,e.payload.message))}},y={debug:function(e,...t){v(_(`debug`,e,t))},info:function(e,...t){v(_(`info`,e,t))},warn:function(e,...t){v(_(`warn`,e,t))},error:function(e,...t){v(_(`error`,e,t))}};addEventListener(`message`,e=>{let t=e.data;if(t.type!=`Initialize`&&!g){y.info(`[TelemetryWorker] Ignoring ${t.type} because no collector is configured`);return}switch(y.debug(`[TelemetryWorker] Received '${t.type}' event:`,t),t.type){case`Initialize`:if(g){y.warn(`[TelemetryWorker] Received a duplicate Initialize event, ignoring it`);break}g=new u({...t.payload,baseUrl:d(t.payload.baseUrl),urlFactory:d,valueIdGenerator:h,logger:y});break;case`EvaluatedConfigEvent`:g?.evaluatedConfig(t.payload);break;case`UpdateContext`:g?.updateContext(t.payload.context);break;case`Flush`:g?.forceFlush();break;case`Close`:(g?.close()??Promise.resolve()).then(()=>{self.postMessage({type:`Closed`}),self.close()});break}})})();"], { type: "application/javascript" })));
1290
+ this.worker = new Worker(URL.createObjectURL(new Blob(["(function(){let e=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,t=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},n=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},r=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}},i=e=>e?.name===`NotAllowedError`?!0:e instanceof TypeError;var a=class{constructor(e){this.executeRequests=!0,this.sdkKey=e.sdkKey,this.logger=e.logger,this.sdkIdentity=e.sdkIdentity,this.metaContext=e.metaContext,this.url=e.urlFactory(`client/telemetry/v1`,e.baseUrl)}async report({context:e,discreteEvents:t,aggregatedEvents:n,droppedEvents:r}){if(!this.executeRequests)return{success:!1,fatalError:!0};let i={clientSdkKey:this.sdkKey,metaContext:{sdkName:this.sdkIdentity.sdkName,sdkVersion:this.sdkIdentity.sdkVersion,appName:this.metaContext.appName,appVersion:this.metaContext.appVersion},context:e,discreteEvents:t,aggregatedEvents:n,droppedEvents:r};if(this.isReportEmpty(i))return{success:!0,fatalError:!1};let a=await this.sendReport(i);return a.fatalError&&(this.executeRequests=!1),a}isReportEmpty(e){return t(e.discreteEvents)&&t(e.aggregatedEvents)&&n(e.droppedEvents)}async sendReport(e){try{let t=await r(5e3,this.url,{method:`POST`,body:JSON.stringify(e),keepalive:!0},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){let t=i(e);return this.logger.warn(`[EventReporter] Error attempting to send telemetry data: ${e}`,{error:e}),{success:!1,fatalError:t}}}isStatusFatal(e){return!!e&&e>=400&&e<500&&e!=429}},o=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}))}},s=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}},c=class{constructor(e){this.aggregator=new o,this.collectEvents=!0,this.logger=e.logger,this.evaluationEventQueue=new s(e.evaluationQueueLimit??1e3),this.flushIntervalDelay=e.flushIntervalDelay??3e4;let t=e.initialFlushIntervalDelay??5e3;this.flushTimeout=setTimeout(()=>this.flushAndScheduleNext(),t)}async flushAndScheduleNext(){if(!this.collectEvents)return;let e;try{e=await this.flush()}catch(t){this.logger.warn(`[TelemetryEventCollector] Error flushing telemetry events: `,t),e={success:!1,fatalError:!1}}e.fatalError?(this.collectEvents=!1,this.close(),this.logger.warn(`[TelemetryEventCollector] Received a fatal error from telemetry collection. No longer collecting events.`)):this.collectEvents&&(this.cancelScheduledFlush(),this.flushTimeout=setTimeout(()=>this.flushAndScheduleNext(),this.flushIntervalDelay))}cancelScheduledFlush(){clearTimeout(this.flushTimeout)}async close(){this.collectEvents=!1,clearTimeout(this.flushTimeout);try{await this.flush()}catch(e){this.logger.warn(`[TelemetryEventCollector] Error flushing telemetry events during close: `,e)}this.evaluationEventQueue.clear()}dispose(){this.close()}},l=class{constructor(e){this.valueIdGenerator=e,this.valueIdGenerator=e}async process(t){let n=await Promise.all(t.events.map(async t=>({...t,defaultValue:await e(t.defaultValue,this.valueIdGenerator),evaluatedValue:await e(t.evaluatedValue,this.valueIdGenerator)})));return{...t,events:n}}},u=class extends c{constructor(e){super(e),this.reporter=new a(e),this.evaluationEventSnapshotPreprocessor=new l(e.valueIdGenerator)}async updateContext(e){this.cancelScheduledFlush(),await this.flush(),this._context=e,await this.flushAndScheduleNext()}evaluatedConfig(e){this.collectEvents&&this.evaluationEventQueue.push(e)}async forceFlush(){this.cancelScheduledFlush(),await this.flushAndScheduleNext()}async flush(){if(!this.reporter)return{success:!1,fatalError:!1};let e=await this.evaluationEventSnapshotPreprocessor.process(this.evaluationEventQueue.takeSnapshot());return await this.reporter?.report({context:this._context,discreteEvents:{},aggregatedEvents:{evaluatedConfig:this.aggregator.aggregate(e)},droppedEvents:{evaluatedConfig:e.droppedCount}})}};let d=(e,t)=>new URL(e,t?.toString()),f=(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`)},p=Math.ceil(128/Math.log2(62)),m=new TextEncoder,h=async e=>{if(typeof crypto>`u`||crypto.subtle==null)return;let t=await crypto.subtle.digest(`SHA-256`,m.encode(e?.toString()??``));return f(new Uint8Array(t,0,16),p)},g,_=(e,t,...n)=>({type:`Log`,payload:{level:e,message:t,args:n}}),v=e=>{try{self.postMessage(e)}catch{self.postMessage(_(e.payload.level,e.payload.message))}},y={debug:function(e,...t){v(_(`debug`,e,t))},info:function(e,...t){v(_(`info`,e,t))},warn:function(e,...t){v(_(`warn`,e,t))},error:function(e,...t){v(_(`error`,e,t))}};addEventListener(`message`,e=>{let t=e.data;if(t.type!=`Initialize`&&!g){y.info(`[TelemetryWorker] Ignoring ${t.type} because no collector is configured`);return}switch(y.debug(`[TelemetryWorker] Received '${t.type}' event:`,t),t.type){case`Initialize`:if(g){y.warn(`[TelemetryWorker] Received a duplicate Initialize event, ignoring it`);break}g=new u({...t.payload,baseUrl:d(t.payload.baseUrl),urlFactory:d,valueIdGenerator:h,logger:y});break;case`EvaluatedConfigEvent`:g?.evaluatedConfig(t.payload);break;case`UpdateContext`:g?.updateContext(t.payload.context);break;case`Flush`:g?.forceFlush();break;case`Close`:(g?.close()??Promise.resolve()).then(()=>{self.postMessage({type:`Closed`}),self.close()});break}})})();"], { type: "application/javascript" })));
1276
1291
  this.worker.onmessage = (message) => {
1277
1292
  this.handleWorkerEvent(message.data);
1278
1293
  };
@@ -1401,6 +1416,10 @@ const createBrowserClient = (clientSdkKey, sdkOptions, clientOptions) => {
1401
1416
  sdkName: sdkOptions.sdkName,
1402
1417
  sdkVersion: sdkOptions.sdkVersion
1403
1418
  },
1419
+ metaContext: {
1420
+ appName: clientOptions?.metadata?.appName,
1421
+ appVersion: clientOptions?.metadata?.appVersion
1422
+ },
1404
1423
  logger,
1405
1424
  baseUrl,
1406
1425
  urlFactory: defaultUrlFactory,
@@ -1463,7 +1482,7 @@ var plugin_client_default = defineNuxtPlugin((nuxtApp) => {
1463
1482
  const { clientSdkKey, appName, appVersion, baseUrl } = runtimeConfig.public.configdirector;
1464
1483
  const client = createBrowserClient(clientSdkKey, {
1465
1484
  sdkName: "nuxt-sdk",
1466
- sdkVersion: "1.5.0"
1485
+ sdkVersion: "1.6.1"
1467
1486
  }, {
1468
1487
  metadata: {
1469
1488
  appName,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@configdirector/nuxt-sdk",
3
- "version": "1.5.0",
3
+ "version": "1.6.1",
4
4
  "description": "Nuxt SDK for ConfigDirector. ConfigDirector is a remote configuration and feature flag service.",
5
5
  "license": "MIT",
6
6
  "author": "ConfigDirector",