@configdirector/nuxt-sdk 1.2.0 → 1.4.0

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.d.mts CHANGED
@@ -1,6 +1,9 @@
1
1
  import * as _$_nuxt_schema0 from "@nuxt/schema";
2
2
  import { LogLevel } from "consola";
3
3
 
4
+ //#region ../shared/src/types.d.ts
5
+ type ConnectionMode = "streaming" | "polling";
6
+ //#endregion
4
7
  //#region src/module.d.ts
5
8
  interface ConfigDirectorRuntimeConfig {
6
9
  /**
@@ -23,6 +26,40 @@ interface ConfigDirectorRuntimeConfig {
23
26
  * Can be set via the NUXT_CONFIGDIRECTOR_BASE_URL environment variable.
24
27
  */
25
28
  baseUrl?: string;
29
+ /**
30
+ * Connection options for the server SDK client used during SSR and in Nitro handlers.
31
+ */
32
+ connection?: {
33
+ /**
34
+ * The connection mode, one of `streaming` (default) or `polling`. In `streaming` mode the
35
+ * connection stays open and receives config updates as they happen. In `polling` mode configs
36
+ * are fetched once during initialization and then again on every `pollingInterval`.
37
+ * Can be set via the NUXT_CONFIGDIRECTOR_CONNECTION_MODE environment variable.
38
+ */
39
+ mode?: ConnectionMode;
40
+ /**
41
+ * The polling interval in seconds when `mode` is `polling`. Has no effect in `streaming` mode.
42
+ * When omitted (or `0`), the server SDK default is used.
43
+ * Can be set via the NUXT_CONFIGDIRECTOR_CONNECTION_POLLING_INTERVAL environment variable.
44
+ */
45
+ pollingInterval?: number;
46
+ /**
47
+ * How long, in milliseconds, the server SDK client waits for its initial config payload. Requests
48
+ * that arrive before the payload is received are held until it arrives or this timeout elapses,
49
+ * after which they render with default values. When omitted (or `0`), the server SDK default is used.
50
+ * Can be set via the NUXT_CONFIGDIRECTOR_CONNECTION_TIMEOUT environment variable.
51
+ */
52
+ timeout?: number;
53
+ };
54
+ /**
55
+ * Whether requests that arrive before the server SDK client received its initial config payload
56
+ * (for example right after the server starts) wait for it, for up to the connection `timeout`, before
57
+ * being handled. When disabled, such requests are handled immediately and evaluate configs to their
58
+ * default values until the payload arrives.
59
+ *
60
+ * Defaults to `true`. Can be set via the NUXT_CONFIGDIRECTOR_WAIT_FOR_INITIALIZATION environment variable.
61
+ */
62
+ waitForInitialization?: boolean;
26
63
  }
27
64
  interface ConfigDirectorPublicRuntimeConfig {
28
65
  /**
package/dist/module.json CHANGED
@@ -4,5 +4,5 @@
4
4
  "compatibility": {
5
5
  "nuxt": ">=4.0.0"
6
6
  },
7
- "version": "1.2.0"
7
+ "version": "1.4.0"
8
8
  }
package/dist/module.mjs CHANGED
@@ -11,14 +11,20 @@ var module_default = defineNuxtModule({
11
11
  setup(options, nuxt) {
12
12
  const logger = useLogger("configdirector");
13
13
  const resolver = createResolver(import.meta.url);
14
- nuxt.options.runtimeConfig.configdirector = defu(nuxt.options.runtimeConfig.configdirector, options.logLevel !== void 0 ? {
15
- serverSdkKey: "",
16
- logLevel: options.logLevel,
17
- baseUrl: ""
18
- } : {
14
+ const serverDefaults = {
19
15
  serverSdkKey: "",
20
- baseUrl: ""
21
- });
16
+ baseUrl: "",
17
+ connection: {
18
+ mode: "streaming",
19
+ pollingInterval: 0,
20
+ timeout: 0
21
+ },
22
+ waitForInitialization: true
23
+ };
24
+ nuxt.options.runtimeConfig.configdirector = defu(nuxt.options.runtimeConfig.configdirector, options.logLevel !== void 0 ? {
25
+ ...serverDefaults,
26
+ logLevel: options.logLevel
27
+ } : serverDefaults);
22
28
  nuxt.options.runtimeConfig.public.configdirector = defu(nuxt.options.runtimeConfig.public.configdirector, options.logLevel !== void 0 ? {
23
29
  clientSdkKey: "",
24
30
  logLevel: options.logLevel,
@@ -2,7 +2,7 @@ import * as _$h3 from "h3";
2
2
  import { ConfigDirectorClient } from "@configdirector/server-sdk";
3
3
 
4
4
  //#region src/runtime/nitro/middleware.d.ts
5
- declare const _default: _$h3.EventHandler<_$h3.EventHandlerRequest, void>;
5
+ declare const _default: _$h3.EventHandler<_$h3.EventHandlerRequest, Promise<void>>;
6
6
  declare module "h3" {
7
7
  interface H3EventContext {
8
8
  configDirectorClient: ConfigDirectorClient;
@@ -2,7 +2,7 @@ import * as _$h3 from "h3";
2
2
  import { ConfigDirectorClient } from "@configdirector/server-sdk";
3
3
 
4
4
  //#region src/runtime/nitro/middleware.d.ts
5
- declare const _default: _$h3.EventHandler<_$h3.EventHandlerRequest, void>;
5
+ declare const _default: _$h3.EventHandler<_$h3.EventHandlerRequest, Promise<void>>;
6
6
  declare module "h3" {
7
7
  interface H3EventContext {
8
8
  configDirectorClient: ConfigDirectorClient;
@@ -1,8 +1,10 @@
1
- import { useNitroApp } from "nitropack/runtime";
1
+ import { useNitroApp, useRuntimeConfig } from "nitropack/runtime";
2
2
  import { defineEventHandler } from "h3";
3
3
  //#region src/runtime/nitro/middleware.ts
4
- var middleware_default = defineEventHandler((event) => {
5
- event.context.configDirectorClient = useNitroApp().configDirectorClient;
4
+ var middleware_default = defineEventHandler(async (event) => {
5
+ const nitroApp = useNitroApp();
6
+ if (useRuntimeConfig(event).configdirector.waitForInitialization) await nitroApp.configDirectorInitialization;
7
+ event.context.configDirectorClient = nitroApp.configDirectorClient;
6
8
  });
7
9
  //#endregion
8
10
  export { middleware_default as default };
@@ -6,6 +6,7 @@ declare const _default: _$nitropack.NitroAppPlugin;
6
6
  declare module "nitropack" {
7
7
  interface NitroApp {
8
8
  configDirectorClient: ConfigDirectorClient;
9
+ configDirectorInitialization: Promise<void>;
9
10
  }
10
11
  }
11
12
  //#endregion
@@ -6,6 +6,7 @@ declare const _default: _$nitropack.NitroAppPlugin;
6
6
  declare module "nitropack" {
7
7
  interface NitroApp {
8
8
  configDirectorClient: ConfigDirectorClient;
9
+ configDirectorInitialization: Promise<void>;
9
10
  }
10
11
  }
11
12
  //#endregion
@@ -476,7 +476,7 @@ var StreamingTransport = class {
476
476
  return new ConfigDirectorConnectionError(`${`Connection failed with status: ${responseStatus ?? "unknown"}.`}${error ? ` Error: ${error}.` : ""} This is an unrecoverable error, will not attempt to reconnect.`, status);
477
477
  }
478
478
  isStatusFatal(status) {
479
- return !!status && status >= 400 && status < 500;
479
+ return !!status && status >= 400 && status < 500 && status != 429;
480
480
  }
481
481
  get isConnected() {
482
482
  return this.eventSource?.readyState == "open";
@@ -535,7 +535,7 @@ var AbstractPollingTransport = class {
535
535
  return new ConfigDirectorConnectionError(`${`Connection failed with status: ${responseStatus ?? "unknown"}`}${(errorBody?.trim()?.length ?? 0) > 0 ? ` (${errorBody})` : ""}. This is an unrecoverable error, retry attempts will be ignored.`, status);
536
536
  }
537
537
  isStatusFatal(status) {
538
- return !!status && status >= 400 && status < 500;
538
+ return !!status && status >= 400 && status < 500 && status != 429;
539
539
  }
540
540
  isFetchErrorFatal(fetchError) {
541
541
  return fetchError?.name === "NotAllowedError";
@@ -7419,7 +7419,7 @@ var ServerEventReporter = class {
7419
7419
  }
7420
7420
  }
7421
7421
  isStatusFatal(status) {
7422
- return !!status && status >= 400 && status < 500;
7422
+ return !!status && status >= 400 && status < 500 && status != 429;
7423
7423
  }
7424
7424
  };
7425
7425
  //#endregion
@@ -7967,16 +7967,21 @@ var plugin_default = defineNitroPlugin((nitroApp) => {
7967
7967
  throw new ConfigDirectorInitializationError(message);
7968
7968
  }
7969
7969
  logger.debug("Initializing ConfigDirector Nitro plugin");
7970
- const { serverSdkKey, baseUrl } = runtimeConfig.configdirector;
7970
+ const { serverSdkKey, baseUrl, connection } = runtimeConfig.configdirector;
7971
7971
  const client = new DefaultConfigDirectorClient(serverSdkKey, {
7972
7972
  sdkName: "nuxt-sdk",
7973
- sdkVersion: "1.2.0"
7973
+ sdkVersion: "1.4.0"
7974
7974
  }, {
7975
7975
  logger,
7976
- ...baseUrl ? { connection: { url: baseUrl } } : {}
7976
+ connection: {
7977
+ url: baseUrl || void 0,
7978
+ mode: connection?.mode || void 0,
7979
+ pollingInterval: connection?.pollingInterval || void 0,
7980
+ timeout: connection?.timeout || void 0
7981
+ }
7977
7982
  });
7978
7983
  nitroApp.configDirectorClient = client;
7979
- client.initialize();
7984
+ nitroApp.configDirectorInitialization = client.initialize();
7980
7985
  });
7981
7986
  //#endregion
7982
7987
  export { plugin_default as default };
@@ -464,7 +464,7 @@ var StreamingTransport = class {
464
464
  return new ConfigDirectorConnectionError(`${`Connection failed with status: ${responseStatus ?? "unknown"}.`}${error ? ` Error: ${error}` : ""}. This is an unrecoverable error, will not attempt to reconnect.`, status);
465
465
  }
466
466
  isStatusFatal(status) {
467
- return !!status && status >= 400 && status < 500;
467
+ return !!status && status >= 400 && status < 500 && status != 429;
468
468
  }
469
469
  on(name, handler) {
470
470
  this.eventEmitter.on(name, handler);
@@ -710,6 +710,91 @@ const generateInstanceId = () => {
710
710
  });
711
711
  };
712
712
  //#endregion
713
+ //#region ../js-client-core/src/instance-id.ts
714
+ const STORAGE_KEY = "configdirector-sdk:instance-id";
715
+ const RECORD_VERSION = 1;
716
+ const MAX_AGE_MS = 360 * 60 * 1e3;
717
+ const CHECKSUM_SALT = "configdirector.instanceId.v1";
718
+ const UUID_V4_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
719
+ const fnv1a = (input, offsetBasis) => {
720
+ let hash = offsetBasis;
721
+ for (let i = 0; i < input.length; i++) {
722
+ hash ^= input.charCodeAt(i);
723
+ hash = Math.imul(hash, 16777619);
724
+ }
725
+ return hash >>> 0;
726
+ };
727
+ const checksumFor = (id, createdAt) => {
728
+ const payload = `${CHECKSUM_SALT}|${id}|${createdAt}`;
729
+ const low = fnv1a(payload, 2166136261);
730
+ return fnv1a(`${low}|${payload}`, 16777619).toString(16).padStart(8, "0") + low.toString(16).padStart(8, "0");
731
+ };
732
+ const getLocalStorage = () => {
733
+ try {
734
+ return globalThis.localStorage ?? void 0;
735
+ } catch {
736
+ return;
737
+ }
738
+ };
739
+ const isUsableRecord = (record) => {
740
+ if (!record || typeof record !== "object") return false;
741
+ const { v, id, createdAt, checksum } = record;
742
+ if (v !== RECORD_VERSION || typeof id !== "string" || typeof checksum !== "string") return false;
743
+ if (typeof createdAt !== "number" || !Number.isInteger(createdAt) || createdAt <= 0) return false;
744
+ if (!UUID_V4_PATTERN.test(id)) return false;
745
+ return checksum === checksumFor(id, createdAt);
746
+ };
747
+ const readStoredInstanceId = (storage, logger) => {
748
+ let raw;
749
+ try {
750
+ raw = storage.getItem(STORAGE_KEY);
751
+ } catch (error) {
752
+ logger?.debug("[ConfigDirectorClient] Could not read the stored instance id, generating a new one", error);
753
+ return;
754
+ }
755
+ if (!raw) return;
756
+ let record;
757
+ try {
758
+ record = JSON.parse(raw);
759
+ } catch {
760
+ logger?.debug("[ConfigDirectorClient] The stored instance id is not valid JSON, generating a new one");
761
+ return;
762
+ }
763
+ if (!isUsableRecord(record)) {
764
+ logger?.debug("[ConfigDirectorClient] The stored instance id failed validation, generating a new one");
765
+ return;
766
+ }
767
+ const age = Date.now() - record.createdAt;
768
+ if (age < 0 || age >= MAX_AGE_MS) {
769
+ logger?.debug("[ConfigDirectorClient] The stored instance id has expired, generating a new one");
770
+ return;
771
+ }
772
+ return record.id;
773
+ };
774
+ const storeInstanceId = (storage, id, logger) => {
775
+ const createdAt = Date.now();
776
+ const record = {
777
+ v: RECORD_VERSION,
778
+ id,
779
+ createdAt,
780
+ checksum: checksumFor(id, createdAt)
781
+ };
782
+ try {
783
+ storage.setItem(STORAGE_KEY, JSON.stringify(record));
784
+ } catch (error) {
785
+ logger?.debug("[ConfigDirectorClient] Could not persist the instance id", error);
786
+ }
787
+ };
788
+ const resolveInstanceId = (logger) => {
789
+ const storage = getLocalStorage();
790
+ if (!storage) return generateInstanceId();
791
+ const storedInstanceId = readStoredInstanceId(storage, logger);
792
+ if (storedInstanceId) return storedInstanceId;
793
+ const instanceId = generateInstanceId();
794
+ storeInstanceId(storage, instanceId, logger);
795
+ return instanceId;
796
+ };
797
+ //#endregion
713
798
  //#region ../shared/src/transport/AbstractPollingTransport.ts
714
799
  var AbstractPollingTransport = class {
715
800
  fatalError = false;
@@ -743,7 +828,7 @@ var AbstractPollingTransport = class {
743
828
  return new ConfigDirectorConnectionError(`${`Connection failed with status: ${responseStatus ?? "unknown"}`}${(errorBody?.trim()?.length ?? 0) > 0 ? ` (${errorBody})` : ""}. This is an unrecoverable error, retry attempts will be ignored.`, status);
744
829
  }
745
830
  isStatusFatal(status) {
746
- return !!status && status >= 400 && status < 500;
831
+ return !!status && status >= 400 && status < 500 && status != 429;
747
832
  }
748
833
  isFetchErrorFatal(fetchError) {
749
834
  return fetchError?.name === "NotAllowedError";
@@ -794,7 +879,9 @@ var PollingTransport = class extends AbstractPollingTransport {
794
879
  this.options = options;
795
880
  this.logger = options.logger;
796
881
  this.url = options.resolveUrl("client/polling/v1", options.baseUrl);
797
- this.pollingIntervalSeconds = options.pollingInterval ?? 60;
882
+ const requestedInterval = options.pollingInterval ?? 300;
883
+ if (requestedInterval < 60) this.logger.warn(`[PollingTransport] pollingInterval of ${requestedInterval} seconds is below the minimum of 60 seconds. Using 60 seconds.`);
884
+ this.pollingIntervalSeconds = Math.max(requestedInterval, 60);
798
885
  }
799
886
  async connect(context, timeout) {
800
887
  this.clearPollingInterval();
@@ -852,6 +939,17 @@ var PollingTransport = class extends AbstractPollingTransport {
852
939
  }
853
940
  };
854
941
  //#endregion
942
+ //#region ../js-client-core/src/browser-globals.ts
943
+ const read = (readValue) => {
944
+ try {
945
+ return readValue(globalThis);
946
+ } catch {
947
+ return;
948
+ }
949
+ };
950
+ const readUserAgent = () => read((globals) => globals.navigator?.userAgent);
951
+ const readHost = () => read((globals) => globals.location?.host);
952
+ //#endregion
855
953
  //#region ../js-client-core/src/DefaultConfigDirectorClient.ts
856
954
  const MAX_EXPONENTIAL_DELAY = 9;
857
955
  var DefaultConfigDirectorClient = class {
@@ -871,8 +969,8 @@ var DefaultConfigDirectorClient = class {
871
969
  connectionMode;
872
970
  instanceId;
873
971
  constructor(telemetryClient, clientSdkKey, sdkOptions, clientOptions, internalClientOptions) {
874
- this.instanceId = generateInstanceId();
875
972
  this.logger = clientOptions?.logger ?? createDefaultLogger$1();
973
+ this.instanceId = resolveInstanceId(this.logger);
876
974
  this.timeout = clientOptions?.connection?.timeout ?? 3e3;
877
975
  this.validateSdkKeyPresence(clientSdkKey);
878
976
  const urlFactory = internalClientOptions?.urlFactory ?? defaultUrlFactory;
@@ -889,7 +987,8 @@ var DefaultConfigDirectorClient = class {
889
987
  ...clientOptions?.metadata,
890
988
  sdkName: sdkOptions.sdkName,
891
989
  sdkVersion: sdkOptions.sdkVersion,
892
- userAgent: navigator?.userAgent
990
+ userAgent: readUserAgent(),
991
+ host: readHost()
893
992
  },
894
993
  logger: this.logger,
895
994
  fetch: internalClientOptions?.fetch,
@@ -1173,7 +1272,7 @@ var WebWorkerTelemetryClient = class {
1173
1272
  flushIntervalDelay: options.flushIntervalDelay ?? 3e4
1174
1273
  }
1175
1274
  };
1176
- 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}},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" })));
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" })));
1177
1276
  this.worker.onmessage = (message) => {
1178
1277
  this.handleWorkerEvent(message.data);
1179
1278
  };
@@ -1364,7 +1463,7 @@ var plugin_client_default = defineNuxtPlugin((nuxtApp) => {
1364
1463
  const { clientSdkKey, appName, appVersion, baseUrl } = runtimeConfig.public.configdirector;
1365
1464
  const client = createBrowserClient(clientSdkKey, {
1366
1465
  sdkName: "nuxt-sdk",
1367
- sdkVersion: "1.2.0"
1466
+ sdkVersion: "1.4.0"
1368
1467
  }, {
1369
1468
  metadata: {
1370
1469
  appName,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@configdirector/nuxt-sdk",
3
- "version": "1.2.0",
3
+ "version": "1.4.0",
4
4
  "description": "Nuxt SDK for ConfigDirector. ConfigDirector is a remote configuration and feature flag service.",
5
5
  "license": "MIT",
6
6
  "author": "ConfigDirector",