@configdirector/nuxt-sdk 0.1.1 → 0.5.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/README.md +4 -142
- package/dist/module.json +1 -1
- package/dist/module.mjs +25 -23
- package/dist/runtime/app/composables/useConfigDirectorClient.d.mts +1 -1
- package/dist/runtime/app/composables/useConfigDirectorClient.d.ts +1 -1
- package/dist/runtime/app/composables/useConfigDirectorContext.d.mts +1 -1
- package/dist/runtime/app/composables/useConfigDirectorContext.d.ts +1 -1
- package/dist/runtime/app/composables/useConfigDirectorValue.d.mts +1 -1
- package/dist/runtime/app/composables/useConfigDirectorValue.d.ts +1 -1
- package/dist/runtime/nitro/middleware.d.mts +1 -119
- package/dist/runtime/nitro/middleware.d.ts +1 -119
- package/dist/runtime/nitro/plugin.d.mts +1 -1
- package/dist/runtime/nitro/plugin.d.ts +1 -1
- package/dist/runtime/nitro/plugin.mjs +200 -34
- package/dist/runtime/plugin.client.mjs +134 -53
- package/dist/runtime/server/composables/useConfigDirectorClient.d.mts +1 -1
- package/dist/runtime/server/composables/useConfigDirectorClient.d.ts +1 -1
- package/dist/runtime/{types-CQWNx-gI.d.mts → types-BWxsGnk2.d.mts} +9 -1
- package/dist/runtime/{types-KSnINlzh.d.mts → types-D9IS8RoC.d.mts} +1 -1
- package/dist/runtime/{types-D9MFb76Q.d.mts → types-DGQ1B6mZ.d.mts} +12 -1
- package/package.json +7 -7
|
@@ -60,13 +60,6 @@ var ConfigDirectorInitializationError = class ConfigDirectorInitializationError
|
|
|
60
60
|
}
|
|
61
61
|
};
|
|
62
62
|
//#endregion
|
|
63
|
-
//#region ../js-client-core/src/errors.ts
|
|
64
|
-
const isFetchErrorFatal = (fetchError) => {
|
|
65
|
-
if (fetchError?.name === "NotAllowedError") return true;
|
|
66
|
-
else if (fetchError instanceof TypeError) return true;
|
|
67
|
-
return false;
|
|
68
|
-
};
|
|
69
|
-
//#endregion
|
|
70
63
|
//#region ../eventsource/src/errors.ts
|
|
71
64
|
var StreamClosedError = class StreamClosedError extends Error {
|
|
72
65
|
name = "StreamClosedError";
|
|
@@ -420,7 +413,7 @@ var StreamingTransport = class {
|
|
|
420
413
|
this.dispatchMessage(data);
|
|
421
414
|
},
|
|
422
415
|
onConnect: () => {
|
|
423
|
-
this.logger.debug("[
|
|
416
|
+
this.logger.debug("[StreamingTransport] Connected");
|
|
424
417
|
resolve(this);
|
|
425
418
|
},
|
|
426
419
|
shouldReconnect: (state) => {
|
|
@@ -430,14 +423,16 @@ var StreamingTransport = class {
|
|
|
430
423
|
},
|
|
431
424
|
calculateReconnectDelay: (state) => {
|
|
432
425
|
const delay = this.options.connectionRetryDelay(state.attempt);
|
|
433
|
-
|
|
426
|
+
const logMessage = `[StreamingTransport] Scheduling reconnect attempt #${state.attempt} in ${delay}ms.`;
|
|
427
|
+
if (state.attempt <= 5) this.logger.info(logMessage);
|
|
428
|
+
else this.logger.warn(logMessage);
|
|
434
429
|
return delay;
|
|
435
430
|
},
|
|
436
431
|
onError: (error) => {
|
|
437
|
-
this.logger.debug("[
|
|
432
|
+
this.logger.debug("[StreamingTransport] Error: ", error);
|
|
438
433
|
},
|
|
439
434
|
onDisconnect: () => {
|
|
440
|
-
this.logger.debug("[
|
|
435
|
+
this.logger.debug("[StreamingTransport] Disconnected");
|
|
441
436
|
}
|
|
442
437
|
});
|
|
443
438
|
this.eventSource.connect();
|
|
@@ -453,7 +448,7 @@ var StreamingTransport = class {
|
|
|
453
448
|
const json = JSON.parse(data);
|
|
454
449
|
this.eventEmitter.emit("configSetReceived", json);
|
|
455
450
|
} catch (error) {
|
|
456
|
-
this.logger.error("[
|
|
451
|
+
this.logger.error("[StreamingTransport] Error parsing and dispatching config data update: ", error);
|
|
457
452
|
}
|
|
458
453
|
}
|
|
459
454
|
prepareFatalError(responseStatus, error) {
|
|
@@ -510,6 +505,7 @@ const parseConfigValue = (configState, defaultValue) => {
|
|
|
510
505
|
usedDefault: true,
|
|
511
506
|
reason: "value-missing"
|
|
512
507
|
};
|
|
508
|
+
if (configState.type === "json") return parseJson(value, defaultValue, requestedType);
|
|
513
509
|
if (typeof defaultValue === "string") return {
|
|
514
510
|
parsedValue: value,
|
|
515
511
|
requestedType,
|
|
@@ -571,6 +567,29 @@ const parseConfigFloat = (value) => {
|
|
|
571
567
|
if (isNaN(num) || !isFinite(num)) return;
|
|
572
568
|
return num;
|
|
573
569
|
};
|
|
570
|
+
const parseJson = (jsonString, defaultValue, requestedType) => {
|
|
571
|
+
if (typeof defaultValue === "string") return {
|
|
572
|
+
parsedValue: jsonString,
|
|
573
|
+
requestedType,
|
|
574
|
+
usedDefault: false,
|
|
575
|
+
reason: "found-match"
|
|
576
|
+
};
|
|
577
|
+
try {
|
|
578
|
+
return {
|
|
579
|
+
parsedValue: JSON.parse(jsonString),
|
|
580
|
+
requestedType,
|
|
581
|
+
usedDefault: false,
|
|
582
|
+
reason: "found-match"
|
|
583
|
+
};
|
|
584
|
+
} catch {
|
|
585
|
+
return {
|
|
586
|
+
parsedValue: defaultValue,
|
|
587
|
+
requestedType,
|
|
588
|
+
usedDefault: true,
|
|
589
|
+
reason: "invalid-json"
|
|
590
|
+
};
|
|
591
|
+
}
|
|
592
|
+
};
|
|
574
593
|
//#endregion
|
|
575
594
|
//#region ../shared/src/logger.ts
|
|
576
595
|
const LEVELS = {
|
|
@@ -635,6 +654,58 @@ const createDefaultLogger$1 = (level, messageDecorator) => {
|
|
|
635
654
|
return new DefaultConsoleLogger(level ?? "warn", messageDecorator ?? new LogMessageDecorator());
|
|
636
655
|
};
|
|
637
656
|
//#endregion
|
|
657
|
+
//#region ../shared/src/transport/AbstractPollingTransport.ts
|
|
658
|
+
var AbstractPollingTransport = class {
|
|
659
|
+
fatalError = false;
|
|
660
|
+
pollingInterval;
|
|
661
|
+
pollingIntervalSeconds = 60;
|
|
662
|
+
clearPollingInterval() {
|
|
663
|
+
clearInterval(this.pollingInterval);
|
|
664
|
+
}
|
|
665
|
+
schedulePollingInterval(callback) {
|
|
666
|
+
clearInterval(this.pollingInterval);
|
|
667
|
+
if (this.pollingIntervalSeconds <= 0) return;
|
|
668
|
+
this.pollingInterval = setInterval(callback, this.pollingIntervalSeconds * 1e3);
|
|
669
|
+
}
|
|
670
|
+
async handleNonOkResponse(response) {
|
|
671
|
+
if (!response.ok) if (this.isStatusFatal(response.status)) {
|
|
672
|
+
this.fatalError = true;
|
|
673
|
+
throw this.prepareFatalResponseStatusError(response.status, await response.text());
|
|
674
|
+
} else throw new ConfigDirectorConnectionError(`Connection failed with status: ${response.status}`, response.status);
|
|
675
|
+
}
|
|
676
|
+
handleFetchError(fetchError) {
|
|
677
|
+
if (this.isFetchErrorFatal(fetchError)) {
|
|
678
|
+
this.fatalError = true;
|
|
679
|
+
this.close();
|
|
680
|
+
throw new ConfigDirectorConnectionError(`Connection failed with fatal error: ${fetchError}. This is an unrecoverable error, retry attempts will be ignored.`);
|
|
681
|
+
} else if (fetchError instanceof SyntaxError) throw new ConfigDirectorConnectionError(`Failed to parse the response from the server: ${fetchError}`);
|
|
682
|
+
else throw new ConfigDirectorConnectionError(`Connection failed with error: ${fetchError}.`);
|
|
683
|
+
}
|
|
684
|
+
prepareFatalResponseStatusError(responseStatus, errorBody) {
|
|
685
|
+
const status = responseStatus ?? 0;
|
|
686
|
+
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);
|
|
687
|
+
}
|
|
688
|
+
isStatusFatal(status) {
|
|
689
|
+
return !!status && status >= 400 && status < 500;
|
|
690
|
+
}
|
|
691
|
+
isFetchErrorFatal(fetchError) {
|
|
692
|
+
if (fetchError?.name === "NotAllowedError") return true;
|
|
693
|
+
else if (fetchError instanceof TypeError) return true;
|
|
694
|
+
return false;
|
|
695
|
+
}
|
|
696
|
+
get isConnected() {
|
|
697
|
+
return this.pollingInterval !== void 0;
|
|
698
|
+
}
|
|
699
|
+
close() {
|
|
700
|
+
clearInterval(this.pollingInterval);
|
|
701
|
+
this.pollingInterval = void 0;
|
|
702
|
+
}
|
|
703
|
+
dispose() {
|
|
704
|
+
this.close();
|
|
705
|
+
this.clear();
|
|
706
|
+
}
|
|
707
|
+
};
|
|
708
|
+
//#endregion
|
|
638
709
|
//#region ../shared/src/fetchWithTimeout.ts
|
|
639
710
|
const fetchWithTimeout = async (timeout, resource, options, logger) => {
|
|
640
711
|
const abortController = new AbortController();
|
|
@@ -656,22 +727,34 @@ const fetchWithTimeout = async (timeout, resource, options, logger) => {
|
|
|
656
727
|
}
|
|
657
728
|
};
|
|
658
729
|
//#endregion
|
|
659
|
-
//#region ../js-client-core/src/
|
|
660
|
-
var
|
|
730
|
+
//#region ../js-client-core/src/PollingTransport.ts
|
|
731
|
+
var PollingTransport = class extends AbstractPollingTransport {
|
|
661
732
|
logger;
|
|
662
733
|
eventEmitter = new Emitter();
|
|
663
734
|
url;
|
|
664
|
-
|
|
735
|
+
lastUpdateTimestamp;
|
|
665
736
|
constructor(options) {
|
|
737
|
+
super();
|
|
666
738
|
this.options = options;
|
|
667
739
|
this.options = options;
|
|
668
740
|
this.logger = options.logger;
|
|
669
|
-
this.url = options.resolveUrl("client/
|
|
741
|
+
this.url = options.resolveUrl("client/polling/v1", options.baseUrl);
|
|
742
|
+
this.pollingIntervalSeconds = options.pollingInterval ?? 60;
|
|
670
743
|
}
|
|
671
744
|
async connect(context, timeout) {
|
|
745
|
+
this.clearPollingInterval();
|
|
746
|
+
await this.fetchConfigs(context, timeout);
|
|
747
|
+
this.schedulePollingInterval(() => {
|
|
748
|
+
this.fetchConfigs(context, timeout).catch((error) => {
|
|
749
|
+
this.logger.error("[PollingTransport] Error during polling:", error);
|
|
750
|
+
});
|
|
751
|
+
});
|
|
752
|
+
return this;
|
|
753
|
+
}
|
|
754
|
+
async fetchConfigs(context, timeout) {
|
|
672
755
|
if (this.fatalError) {
|
|
673
|
-
this.logger.warn("[
|
|
674
|
-
return
|
|
756
|
+
this.logger.warn("[PollingTransport] There was a prior unrecoverable error. Ignoring attempt to reconnect.");
|
|
757
|
+
return;
|
|
675
758
|
}
|
|
676
759
|
try {
|
|
677
760
|
const response = await fetchWithTimeout(timeout, this.url, {
|
|
@@ -680,31 +763,20 @@ var PullTransport = class {
|
|
|
680
763
|
body: JSON.stringify({
|
|
681
764
|
givenContext: context,
|
|
682
765
|
metaContext: this.options.metaContext,
|
|
683
|
-
clientSdkKey: this.options.clientSdkKey
|
|
766
|
+
clientSdkKey: this.options.clientSdkKey,
|
|
767
|
+
lastUpdateTimestamp: this.lastUpdateTimestamp
|
|
684
768
|
})
|
|
685
769
|
}, this.logger);
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
return this;
|
|
770
|
+
await this.handleNonOkResponse(response);
|
|
771
|
+
if (response.status == 200) {
|
|
772
|
+
const json = JSON.parse(await response.text());
|
|
773
|
+
this.lastUpdateTimestamp = json.timestamp;
|
|
774
|
+
this.eventEmitter.emit("configSetReceived", json);
|
|
775
|
+
}
|
|
693
776
|
} catch (fetchError) {
|
|
694
|
-
|
|
695
|
-
this.fatalError = true;
|
|
696
|
-
throw new ConfigDirectorConnectionError(`Connection failed with fatal error: ${fetchError}. This is an unrecoverable error, retry attempts will be ignored.`);
|
|
697
|
-
} else if (fetchError instanceof SyntaxError) throw new ConfigDirectorConnectionError(`Failed to parse the response from the server: ${fetchError}`);
|
|
698
|
-
else throw new ConfigDirectorConnectionError(`Connection failed with error: ${fetchError}.`);
|
|
777
|
+
this.handleFetchError(fetchError);
|
|
699
778
|
}
|
|
700
779
|
}
|
|
701
|
-
prepareFatalResponseStatusError(responseStatus, errorBody) {
|
|
702
|
-
const status = responseStatus ?? 0;
|
|
703
|
-
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);
|
|
704
|
-
}
|
|
705
|
-
isStatusFatal(status) {
|
|
706
|
-
return !!status && status >= 400 && status < 500;
|
|
707
|
-
}
|
|
708
780
|
on(name, handler) {
|
|
709
781
|
this.eventEmitter.on(name, handler);
|
|
710
782
|
}
|
|
@@ -714,10 +786,13 @@ var PullTransport = class {
|
|
|
714
786
|
clear() {
|
|
715
787
|
this.eventEmitter.clear();
|
|
716
788
|
}
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
789
|
+
};
|
|
790
|
+
//#endregion
|
|
791
|
+
//#region ../js-client-core/src/OneTimeTransport.ts
|
|
792
|
+
var OneTimeTransport = class extends PollingTransport {
|
|
793
|
+
constructor(options) {
|
|
794
|
+
super(options);
|
|
795
|
+
this.pollingIntervalSeconds = 0;
|
|
721
796
|
}
|
|
722
797
|
};
|
|
723
798
|
//#endregion
|
|
@@ -748,14 +823,14 @@ var DefaultConfigDirectorClient = class {
|
|
|
748
823
|
readyPromise;
|
|
749
824
|
readyResolve;
|
|
750
825
|
currentContext;
|
|
751
|
-
|
|
826
|
+
connectionMode;
|
|
752
827
|
constructor(telemetryClient, clientSdkKey, sdkOptions, clientOptions, internalClientOptions) {
|
|
753
828
|
this.logger = clientOptions?.logger ?? createDefaultLogger$1();
|
|
754
829
|
this.timeout = clientOptions?.connection?.timeout ?? 3e3;
|
|
755
830
|
const urlFactory = internalClientOptions?.urlFactory ?? defaultUrlFactory;
|
|
756
831
|
const baseUrl = this.parseUrl(clientOptions?.connection?.url, urlFactory) ?? "https://client-sdk-api.configdirector.com";
|
|
757
|
-
this.
|
|
758
|
-
const transportConstructor = this.
|
|
832
|
+
this.connectionMode = clientOptions?.connection?.mode ?? "streaming";
|
|
833
|
+
const transportConstructor = this.getTransportConstructor(this.connectionMode);
|
|
759
834
|
this.telemetryClient = telemetryClient;
|
|
760
835
|
this.transport = new transportConstructor({
|
|
761
836
|
clientSdkKey,
|
|
@@ -771,7 +846,8 @@ var DefaultConfigDirectorClient = class {
|
|
|
771
846
|
fetch: internalClientOptions?.fetch,
|
|
772
847
|
connectionRetryDelay: internalClientOptions?.connectionRetryDelay ?? ((attempt) => {
|
|
773
848
|
return Math.pow(2, Math.min(attempt, MAX_EXPONENTIAL_DELAY)) * 1e3;
|
|
774
|
-
})
|
|
849
|
+
}),
|
|
850
|
+
pollingInterval: clientOptions?.connection?.pollingInterval
|
|
775
851
|
});
|
|
776
852
|
this.transport.on("configSetReceived", (configSet) => {
|
|
777
853
|
this.readyResolve?.();
|
|
@@ -791,6 +867,13 @@ var DefaultConfigDirectorClient = class {
|
|
|
791
867
|
this.logger.debug("[ConfigDirectorClient] ConfigSet updated from server:", { keys: configKeys });
|
|
792
868
|
});
|
|
793
869
|
}
|
|
870
|
+
getTransportConstructor(connectionMode) {
|
|
871
|
+
switch (connectionMode) {
|
|
872
|
+
case "one-time": return OneTimeTransport;
|
|
873
|
+
case "polling": return PollingTransport;
|
|
874
|
+
default: return StreamingTransport;
|
|
875
|
+
}
|
|
876
|
+
}
|
|
794
877
|
async initialize(context) {
|
|
795
878
|
this.initializing = true;
|
|
796
879
|
await this.connectToTransport(context, "initialization");
|
|
@@ -824,10 +907,7 @@ var DefaultConfigDirectorClient = class {
|
|
|
824
907
|
if (remainingTimeout > 0) await Promise.race([this.readyPromise, new Promise((resolve) => {
|
|
825
908
|
this.timeoutTimer = setTimeout(() => resolve(), remainingTimeout);
|
|
826
909
|
})]).finally(() => clearTimeout(this.timeoutTimer));
|
|
827
|
-
if (!this.ready) {
|
|
828
|
-
const warningDetails = this.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.";
|
|
829
|
-
this.logger.warn(`[ConfigDirectorClient] Timed out waiting for ${caller} after ${this.timeout}ms. ${warningDetails}`);
|
|
830
|
-
}
|
|
910
|
+
if (!this.ready) this.logger.warn(`[ConfigDirectorClient] Timed out waiting for ${caller} after ${this.timeout}ms. The client will continue to retry since there were no fatal errors detected. Configs will return the default value until the connection succeeds.`);
|
|
831
911
|
} catch (error) {
|
|
832
912
|
this.logger.error(`[ConfigDirectorClient] An error occurred during ${caller}: `, error);
|
|
833
913
|
}
|
|
@@ -883,6 +963,7 @@ var DefaultConfigDirectorClient = class {
|
|
|
883
963
|
const parseResult = parseConfigValue(configState, defaultValue);
|
|
884
964
|
this.telemetryClient.evaluatedConfig({
|
|
885
965
|
contextId: this.currentContext?.id,
|
|
966
|
+
type: configState.type,
|
|
886
967
|
key: configKey,
|
|
887
968
|
defaultValue,
|
|
888
969
|
requestedType: parseResult.requestedType,
|
|
@@ -952,7 +1033,7 @@ var DefaultConfigDirectorClient = class {
|
|
|
952
1033
|
//#region ../shared/src/telemetry/utils.ts
|
|
953
1034
|
const CONFIG_VALUE_MAX_LENGTH = 500;
|
|
954
1035
|
const sanitizeValue = (value, type) => {
|
|
955
|
-
if (type === "json") try {
|
|
1036
|
+
if (type === "json" || type == null && typeof value === "object") try {
|
|
956
1037
|
return djb2Hash(JSON.stringify(value));
|
|
957
1038
|
} catch {
|
|
958
1039
|
return value.toString().slice(0, CONFIG_VALUE_MAX_LENGTH);
|
|
@@ -990,7 +1071,7 @@ var WebWorkerTelemetryClient = class {
|
|
|
990
1071
|
flushIntervalDelay: options.flushIntervalDelay ?? 3e4
|
|
991
1072
|
}
|
|
992
1073
|
};
|
|
993
|
-
this.worker = new Worker(URL.createObjectURL(new Blob(["(function(){let e=(e,n)=>{if(n===`json`)try{return t(JSON.stringify(e))}catch{return e.toString().slice(0,500)}return e.toString().slice(0,500)},t=e=>n(new TextEncoder().encode(e)).toString(16).padStart(8,`0`),n=e=>{let t=5381;for(let n=0;n<e.length;n++)t=(t<<5)+t+(e[n]??0),t>>>=0;return t},r=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},i=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},a=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}},o=e=>e?.name===`NotAllowedError`?!0:e instanceof TypeError;var s=class{constructor(e){this.executeRequests=!0,this.sdkKey=e.sdkKey,this.logger=e.logger,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,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 r(e.discreteEvents)&&r(e.aggregatedEvents)&&i(e.droppedEvents)}async sendReport(e){try{let t=await a(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=o(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}},c=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}))}},l=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}},u=class{constructor(e){this.aggregator=new c,this.collectEvents=!0,this.logger=e.logger,this.evaluationEventQueue=new l(e.evaluationQueueLimit??1e3),this.flushIntervalDelay=e.flushIntervalDelay??3e4;let t=e.initialFlushIntervalDelay??5e3;this.flushTimeout=setTimeout(()=>this.flushAndScheduleNext(),t)}sanitizeValue(t,n){return e(t,n)}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)}async flush(){if(!this.reporter)return{success:!1,fatalError:!1};let e=this.evaluationEventQueue.takeSnapshot();return await this.reporter?.report({context:this._context,discreteEvents:{},aggregatedEvents:{evaluatedConfig:this.aggregator.aggregate(e)},droppedEvents:{evaluatedConfig:e.droppedCount}})}cancelScheduledFlush(){clearTimeout(this.flushTimeout)}async close(){this.collectEvents=!1,clearTimeout(this.flushTimeout),await this.flush(),this.evaluationEventQueue.clear()}dispose(){this.close()}},d=class extends u{constructor(e){super(e),this.reporter=new s(e)}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()}};let f=(e,t)=>new URL(e,t?.toString()),p,m=(e,t,...n)=>({type:`Log`,payload:{level:e,message:t,args:n}}),h=e=>{try{self.postMessage(e)}catch{self.postMessage(m(e.payload.level,e.payload.message))}},g={debug:function(e,...t){h(m(`debug`,e,t))},info:function(e,...t){h(m(`info`,e,t))},warn:function(e,...t){h(m(`warn`,e,t))},error:function(e,...t){h(m(`error`,e,t))}};addEventListener(`message`,e=>{let t=e.data;if(t.type!=`Initialize`&&!p){g.info(`[TelemetryWorker] Ignoring ${t.type} because no collector is configured`);return}switch(g.debug(`[TelemetryWorker] Received '${t.type}' event:`,t),t.type){case`Initialize`:p=new d({...t.payload,baseUrl:f(t.payload.baseUrl),urlFactory:f,logger:g});break;case`EvaluatedConfigEvent`:p?.evaluatedConfig(t.payload);break;case`UpdateContext`:p?.updateContext(t.payload.context);break;case`Flush`:p?.forceFlush();break;case`Close`:(p?.close()??Promise.resolve()).then(()=>{self.postMessage({type:`Closed`}),self.close()});break}})})();"], { type: "application/javascript" })));
|
|
1074
|
+
this.worker = new Worker(URL.createObjectURL(new Blob(["(function(){let e=(e,n)=>{if(n===`json`||n==null&&typeof e==`object`)try{return t(JSON.stringify(e))}catch{return e.toString().slice(0,500)}return e.toString().slice(0,500)},t=e=>n(new TextEncoder().encode(e)).toString(16).padStart(8,`0`),n=e=>{let t=5381;for(let n=0;n<e.length;n++)t=(t<<5)+t+(e[n]??0),t>>>=0;return t},r=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},i=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},a=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}},o=e=>e?.name===`NotAllowedError`?!0:e instanceof TypeError;var s=class{constructor(e){this.executeRequests=!0,this.sdkKey=e.sdkKey,this.logger=e.logger,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,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 r(e.discreteEvents)&&r(e.aggregatedEvents)&&i(e.droppedEvents)}async sendReport(e){try{let t=await a(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=o(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}},c=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}))}},l=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}},u=class{constructor(e){this.aggregator=new c,this.collectEvents=!0,this.logger=e.logger,this.evaluationEventQueue=new l(e.evaluationQueueLimit??1e3),this.flushIntervalDelay=e.flushIntervalDelay??3e4;let t=e.initialFlushIntervalDelay??5e3;this.flushTimeout=setTimeout(()=>this.flushAndScheduleNext(),t)}sanitizeValue(t,n){return e(t,n)}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)}async flush(){if(!this.reporter)return{success:!1,fatalError:!1};let e=this.evaluationEventQueue.takeSnapshot();return await this.reporter?.report({context:this._context,discreteEvents:{},aggregatedEvents:{evaluatedConfig:this.aggregator.aggregate(e)},droppedEvents:{evaluatedConfig:e.droppedCount}})}cancelScheduledFlush(){clearTimeout(this.flushTimeout)}async close(){this.collectEvents=!1,clearTimeout(this.flushTimeout),await this.flush(),this.evaluationEventQueue.clear()}dispose(){this.close()}},d=class extends u{constructor(e){super(e),this.reporter=new s(e)}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()}};let f=(e,t)=>new URL(e,t?.toString()),p,m=(e,t,...n)=>({type:`Log`,payload:{level:e,message:t,args:n}}),h=e=>{try{self.postMessage(e)}catch{self.postMessage(m(e.payload.level,e.payload.message))}},g={debug:function(e,...t){h(m(`debug`,e,t))},info:function(e,...t){h(m(`info`,e,t))},warn:function(e,...t){h(m(`warn`,e,t))},error:function(e,...t){h(m(`error`,e,t))}};addEventListener(`message`,e=>{let t=e.data;if(t.type!=`Initialize`&&!p){g.info(`[TelemetryWorker] Ignoring ${t.type} because no collector is configured`);return}switch(g.debug(`[TelemetryWorker] Received '${t.type}' event:`,t),t.type){case`Initialize`:p=new d({...t.payload,baseUrl:f(t.payload.baseUrl),urlFactory:f,logger:g});break;case`EvaluatedConfigEvent`:p?.evaluatedConfig(t.payload);break;case`UpdateContext`:p?.updateContext(t.payload.context);break;case`Flush`:p?.forceFlush();break;case`Close`:(p?.close()??Promise.resolve()).then(()=>{self.postMessage({type:`Closed`}),self.close()});break}})})();"], { type: "application/javascript" })));
|
|
994
1075
|
this.worker.onmessage = (message) => {
|
|
995
1076
|
this.handleWorkerEvent(message.data);
|
|
996
1077
|
};
|
|
@@ -1130,7 +1211,7 @@ var plugin_client_default = defineNuxtPlugin((nuxtApp) => {
|
|
|
1130
1211
|
const { clientSdkKey, appName, appVersion, baseUrl } = runtimeConfig.public.configdirector;
|
|
1131
1212
|
const client = createBrowserClient(clientSdkKey, {
|
|
1132
1213
|
sdkName: "nuxt-sdk",
|
|
1133
|
-
sdkVersion: "0.
|
|
1214
|
+
sdkVersion: "0.5.0"
|
|
1134
1215
|
}, {
|
|
1135
1216
|
metadata: {
|
|
1136
1217
|
appName,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as ConfigDirectorClient } from "../../types-
|
|
1
|
+
import { t as ConfigDirectorClient } from "../../types-DGQ1B6mZ.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/runtime/server/composables/useConfigDirectorClient.d.ts
|
|
4
4
|
declare const useConfigDirectorClient: () => ConfigDirectorClient;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as ConfigDirectorClient } from "../../types-
|
|
1
|
+
import { t as ConfigDirectorClient } from "../../types-DGQ1B6mZ.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/runtime/server/composables/useConfigDirectorClient.d.ts
|
|
4
4
|
declare const useConfigDirectorClient: () => ConfigDirectorClient;
|
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
//#region ../shared/src/types.d.ts
|
|
2
|
+
declare const ConfigTypeList: readonly ["custom", "boolean", "string", "integer", "float", "enum", "url", "json"];
|
|
3
|
+
type ConfigType = (typeof ConfigTypeList)[number];
|
|
2
4
|
type ConfigEnumLikeType = {
|
|
3
5
|
[key: string]: string | number;
|
|
4
6
|
};
|
|
5
7
|
type ConfigValueType = string | number | boolean | object | ConfigEnumLikeType;
|
|
8
|
+
type ConfigState = {
|
|
9
|
+
id: string;
|
|
10
|
+
key: string;
|
|
11
|
+
type: ConfigType;
|
|
12
|
+
value: string | undefined | null;
|
|
13
|
+
};
|
|
6
14
|
/**
|
|
7
15
|
* The user's context to be sent to ConfigDirector. This context will be used for targeting
|
|
8
16
|
* rules evaluation.
|
|
@@ -31,4 +39,4 @@ type ConfigDirectorContext = {
|
|
|
31
39
|
};
|
|
32
40
|
};
|
|
33
41
|
//#endregion
|
|
34
|
-
export {
|
|
42
|
+
export { ConfigState as n, ConfigValueType as r, ConfigDirectorContext as t };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as ConfigValueType, t as ConfigDirectorContext } from "./types-
|
|
1
|
+
import { n as ConfigState, r as ConfigValueType, t as ConfigDirectorContext } from "./types-BWxsGnk2.mjs";
|
|
2
2
|
|
|
3
3
|
//#region ../js-server-sdk/src/types.d.ts
|
|
4
4
|
type ClientEvents = {
|
|
@@ -71,6 +71,17 @@ interface ConfigDirectorClient {
|
|
|
71
71
|
* Removes all subscribers from all config keys
|
|
72
72
|
*/
|
|
73
73
|
unwatchAll(): void;
|
|
74
|
+
/**
|
|
75
|
+
* Returns the evaluated {@link ConfigState} for every known config key, or only the keys
|
|
76
|
+
* listed in `configKeys` when provided.
|
|
77
|
+
*
|
|
78
|
+
* Intended for SSR hydration — does NOT record telemetry events. Returns an empty object
|
|
79
|
+
* when the client is not yet ready.
|
|
80
|
+
*/
|
|
81
|
+
getAllConfigs(options?: {
|
|
82
|
+
context?: ConfigDirectorContext;
|
|
83
|
+
configKeys?: string[];
|
|
84
|
+
}): Record<string, ConfigState>;
|
|
74
85
|
on<T extends keyof ClientEvents>(eventName: T, handler: (args: ClientEvents[T]) => void): void;
|
|
75
86
|
off<T extends keyof ClientEvents>(eventName: T, handler?: (args: ClientEvents[T]) => void): void;
|
|
76
87
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@configdirector/nuxt-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.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",
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"homepage": "https://www.configdirector.com",
|
|
19
19
|
"repository": {
|
|
20
20
|
"type": "git",
|
|
21
|
-
"url": "https://github.com/ConfigDirector/js-sdks"
|
|
21
|
+
"url": "git+https://github.com/ConfigDirector/js-sdks.git"
|
|
22
22
|
},
|
|
23
23
|
"type": "module",
|
|
24
24
|
"exports": {
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"scripts": {
|
|
42
42
|
"build": "nuxt prepare && tsdown",
|
|
43
43
|
"prepack": "nuxt prepare && tsdown",
|
|
44
|
-
"release": "yarn lint && yarn ci:test && yarn build && npm publish --access public",
|
|
44
|
+
"release": "nuxt prepare && yarn lint && yarn ci:test && yarn build && npm publish --access public",
|
|
45
45
|
"lint": "eslint .",
|
|
46
46
|
"ci:test": "vitest run",
|
|
47
47
|
"test": "vitest watch",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
51
|
"@jsonjoy.com/json-pointer": "^18.1.0",
|
|
52
|
-
"@nuxt/kit": "^
|
|
52
|
+
"@nuxt/kit": "^3.7.0 || ^4.0.0",
|
|
53
53
|
"consola": "^3.4.2",
|
|
54
54
|
"defu": "^6.1.7",
|
|
55
55
|
"rapidhash-js": "^3.0.0",
|
|
@@ -63,14 +63,14 @@
|
|
|
63
63
|
"@types/node": "latest",
|
|
64
64
|
"changelogen": "^0.6.2",
|
|
65
65
|
"eslint": "^10.2.1",
|
|
66
|
-
"nuxt": "^4.4.
|
|
67
|
-
"playwright": "^1.
|
|
66
|
+
"nuxt": "^4.4.6",
|
|
67
|
+
"playwright": "^1.60.0",
|
|
68
68
|
"rimraf": "^6.1.3",
|
|
69
69
|
"tsdown": "^0.21.7",
|
|
70
70
|
"tslib": "^2.8.1",
|
|
71
71
|
"typescript": "~6.0.3",
|
|
72
72
|
"vite": "^8.0.10",
|
|
73
|
-
"vitest": "^4.1.
|
|
73
|
+
"vitest": "^4.1.8",
|
|
74
74
|
"vue-tsc": "^3.2.7"
|
|
75
75
|
}
|
|
76
76
|
}
|