@configdirector/nuxt-sdk 0.8.0 → 0.9.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.json +1 -1
- package/dist/module.mjs +9 -0
- 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/useConfigDirectorClientHooks.d.mts +6 -0
- package/dist/runtime/app/composables/useConfigDirectorClientHooks.d.ts +6 -0
- package/dist/runtime/app/composables/useConfigDirectorClientHooks.mjs +22 -0
- 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/plugin.d.mts +1 -1
- package/dist/runtime/nitro/plugin.d.ts +1 -1
- package/dist/runtime/nitro/plugin.mjs +35 -13
- package/dist/runtime/plugin.client.mjs +69 -10
- package/dist/runtime/server/composables/useConfigDirectorClient.d.mts +1 -1
- package/dist/runtime/server/composables/useConfigDirectorClient.d.ts +1 -1
- package/dist/runtime/server/composables/useConfigDirectorServerHooks.d.mts +6 -0
- package/dist/runtime/server/composables/useConfigDirectorServerHooks.d.ts +6 -0
- package/dist/runtime/server/composables/useConfigDirectorServerHooks.mjs +14 -0
- package/dist/runtime/{types-DE44nrVT.d.mts → types-B4EK1B6g.d.mts} +12 -2
- package/dist/runtime/{types-BqdrorBY.d.mts → types-CHmsCChN.d.mts} +10 -1
- package/dist/runtime/{types-Ce8HdKnT.d.mts → types-CzmBIfqo.d.mts} +11 -2
- package/package.json +1 -1
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -37,6 +37,10 @@ var module_default = defineNuxtModule({
|
|
|
37
37
|
name: "useConfigDirectorClient",
|
|
38
38
|
as: "useConfigDirectorClient",
|
|
39
39
|
from: resolver.resolve("./runtime/server/composables/useConfigDirectorClient")
|
|
40
|
+
}, {
|
|
41
|
+
name: "useConfigDirectorServerHooks",
|
|
42
|
+
as: "useConfigDirectorServerHooks",
|
|
43
|
+
from: resolver.resolve("./runtime/server/composables/useConfigDirectorServerHooks")
|
|
40
44
|
}]);
|
|
41
45
|
addPlugin(resolver.resolve("./runtime/plugin.server"));
|
|
42
46
|
addPlugin(resolver.resolve("./runtime/plugin.client"));
|
|
@@ -60,6 +64,11 @@ var module_default = defineNuxtModule({
|
|
|
60
64
|
name: "useConfigDirectorValue",
|
|
61
65
|
as: "useConfigDirectorValue",
|
|
62
66
|
from: resolver.resolve("./runtime/app/composables/useConfigDirectorValue")
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
name: "useConfigDirectorClientHooks",
|
|
70
|
+
as: "useConfigDirectorClientHooks",
|
|
71
|
+
from: resolver.resolve("./runtime/app/composables/useConfigDirectorClientHooks")
|
|
63
72
|
}
|
|
64
73
|
]);
|
|
65
74
|
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { t as ClientHooks } from "../../types-B4EK1B6g.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/runtime/app/composables/useConfigDirectorClientHooks.d.ts
|
|
4
|
+
declare const useConfigDirectorClientHooks: (hooks: ClientHooks) => void;
|
|
5
|
+
//#endregion
|
|
6
|
+
export { useConfigDirectorClientHooks };
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { t as ClientHooks } from "../../types-B4EK1B6g.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/runtime/app/composables/useConfigDirectorClientHooks.d.ts
|
|
4
|
+
declare const useConfigDirectorClientHooks: (hooks: ClientHooks) => void;
|
|
5
|
+
//#endregion
|
|
6
|
+
export { useConfigDirectorClientHooks };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { useNuxtApp } from "#app";
|
|
2
|
+
import { getCurrentInstance, onUnmounted } from "vue";
|
|
3
|
+
//#region src/runtime/app/composables/useConfigDirectorClientHooks.ts
|
|
4
|
+
const useConfigDirectorClientHooks = (hooks) => {
|
|
5
|
+
const { $configDirectorClient } = useNuxtApp();
|
|
6
|
+
const client = $configDirectorClient;
|
|
7
|
+
const hookEntries = Object.entries(hooks);
|
|
8
|
+
for (const [event, handler] of hookEntries) {
|
|
9
|
+
if (!handler) continue;
|
|
10
|
+
const handlers = Array.isArray(handler) ? handler : [handler];
|
|
11
|
+
for (const h of handlers) client.on(event, h);
|
|
12
|
+
}
|
|
13
|
+
if (getCurrentInstance()) onUnmounted(() => {
|
|
14
|
+
for (const [event, handler] of hookEntries) {
|
|
15
|
+
if (!handler) continue;
|
|
16
|
+
const handlers = Array.isArray(handler) ? handler : [handler];
|
|
17
|
+
for (const h of handlers) client.off(event, h);
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
};
|
|
21
|
+
//#endregion
|
|
22
|
+
export { useConfigDirectorClientHooks };
|
|
@@ -2,6 +2,7 @@ import EventEmitter, { EventEmitter as EventEmitter$1 } from "node:events";
|
|
|
2
2
|
import { findByPointer } from "@jsonjoy.com/json-pointer";
|
|
3
3
|
import * as semver from "semver";
|
|
4
4
|
import { rapidhash } from "rapidhash-js";
|
|
5
|
+
import { createHash } from "node:crypto";
|
|
5
6
|
import { defineNitroPlugin, useRuntimeConfig } from "nitropack/runtime";
|
|
6
7
|
import { consola } from "consola";
|
|
7
8
|
//#region ../shared/src/errors.ts
|
|
@@ -862,7 +863,7 @@ const parseConfigValue = (configState, defaultValue) => {
|
|
|
862
863
|
usedDefault: false,
|
|
863
864
|
reason: "found-match"
|
|
864
865
|
};
|
|
865
|
-
if (typeof defaultValue === "boolean" && configState.type === "boolean") {
|
|
866
|
+
if (typeof defaultValue === "boolean" && (configState.type === "boolean" || configState.type === "string")) {
|
|
866
867
|
const boolValue = parseConfigBoolean(value);
|
|
867
868
|
const hasBoolean = typeof boolValue === "boolean";
|
|
868
869
|
return {
|
|
@@ -884,7 +885,7 @@ const parseConfigValue = (configState, defaultValue) => {
|
|
|
884
885
|
reason: hasNumber ? "found-match" : "invalid-number"
|
|
885
886
|
};
|
|
886
887
|
}
|
|
887
|
-
if (isNumericNativeType(typeof defaultValue) && (configState.type === "float" || configState.type === "enum")) {
|
|
888
|
+
if (isNumericNativeType(typeof defaultValue) && (configState.type === "float" || configState.type === "enum" || configState.type === "string")) {
|
|
888
889
|
const numValue = parseConfigFloat(value);
|
|
889
890
|
const hasNumber = typeof numValue === "number";
|
|
890
891
|
return {
|
|
@@ -1360,17 +1361,11 @@ const toBase62 = (buf, length) => {
|
|
|
1360
1361
|
}
|
|
1361
1362
|
return result.padStart(length, "0");
|
|
1362
1363
|
};
|
|
1364
|
+
const BASE62_LENGTH = Math.ceil(128 / Math.log2(62));
|
|
1363
1365
|
//#endregion
|
|
1364
|
-
//#region ../
|
|
1365
|
-
const DIGEST_BYTES = 16;
|
|
1366
|
-
const BASE62_LENGTH = Math.ceil(DIGEST_BYTES * 8 / Math.log2(62));
|
|
1367
|
-
const sha256 = async (value) => {
|
|
1368
|
-
const encoded = new TextEncoder().encode(value);
|
|
1369
|
-
const hashBuffer = await globalThis.crypto.subtle.digest("SHA-256", encoded);
|
|
1370
|
-
return new Uint8Array(hashBuffer).subarray(0, DIGEST_BYTES);
|
|
1371
|
-
};
|
|
1366
|
+
//#region ../js-server-sdk/src/telemetry/value-id-generator.ts
|
|
1372
1367
|
const generateValueId = async (v) => {
|
|
1373
|
-
return toBase62(
|
|
1368
|
+
return toBase62(createHash("sha256").update(v?.toString() ?? "").digest().subarray(0, 16), BASE62_LENGTH);
|
|
1374
1369
|
};
|
|
1375
1370
|
//#endregion
|
|
1376
1371
|
//#region ../js-server-sdk/src/DefaultConfigDirectorClient.ts
|
|
@@ -1438,6 +1433,14 @@ var DefaultConfigDirectorClient = class {
|
|
|
1438
1433
|
}
|
|
1439
1434
|
this.logger.debug(`[ConfigDirectorClient] ConfigSet updated from server with ${configKeys.length} key(s)`, { keys: configKeys });
|
|
1440
1435
|
});
|
|
1436
|
+
this.registerHandler("clientReady", clientOptions?.hooks?.clientReady);
|
|
1437
|
+
this.registerHandler("configEvaluated", clientOptions?.hooks?.configEvaluated);
|
|
1438
|
+
this.registerHandler("configsUpdated", clientOptions?.hooks?.configsUpdated);
|
|
1439
|
+
}
|
|
1440
|
+
registerHandler(event, handler) {
|
|
1441
|
+
if (!handler) return;
|
|
1442
|
+
const handlers = Array.isArray(handler) ? handler : [handler];
|
|
1443
|
+
for (const h of handlers) if (typeof h === "function") this.on(event, h);
|
|
1441
1444
|
}
|
|
1442
1445
|
async initialize() {
|
|
1443
1446
|
try {
|
|
@@ -1526,10 +1529,19 @@ var DefaultConfigDirectorClient = class {
|
|
|
1526
1529
|
evaluationReason: parseResult.reason
|
|
1527
1530
|
}
|
|
1528
1531
|
});
|
|
1532
|
+
this.dispatchEvaluationEvent({
|
|
1533
|
+
key: configKey,
|
|
1534
|
+
value: parseResult.parsedValue,
|
|
1535
|
+
valueId: parseResult.parsedValueId ?? void 0,
|
|
1536
|
+
isDefaultValue: parseResult.usedDefault,
|
|
1537
|
+
reason: parseResult.reason,
|
|
1538
|
+
context
|
|
1539
|
+
});
|
|
1529
1540
|
this.logger.debug(`[ConfigDirectorClient] Evaluated '${configKey}' to '${parseResult.parsedValue}'`);
|
|
1530
1541
|
return parseResult.parsedValue;
|
|
1531
1542
|
} else {
|
|
1532
1543
|
this.logger.debug(`[ConfigDirectorClient] No config state found for '${configKey}', returning default value '${defaultValue}'`);
|
|
1544
|
+
const reason = this.isReady ? "config-state-missing" : "client-not-ready";
|
|
1533
1545
|
this.usageEventCollector.evaluatedConfig({
|
|
1534
1546
|
context,
|
|
1535
1547
|
evaluation: {
|
|
@@ -1538,12 +1550,22 @@ var DefaultConfigDirectorClient = class {
|
|
|
1538
1550
|
requestedType: getRequestedType(defaultValue),
|
|
1539
1551
|
evaluatedValue: defaultValue,
|
|
1540
1552
|
usedDefault: true,
|
|
1541
|
-
evaluationReason:
|
|
1553
|
+
evaluationReason: reason
|
|
1542
1554
|
}
|
|
1543
1555
|
});
|
|
1556
|
+
this.dispatchEvaluationEvent({
|
|
1557
|
+
key: configKey,
|
|
1558
|
+
value: defaultValue,
|
|
1559
|
+
isDefaultValue: true,
|
|
1560
|
+
reason,
|
|
1561
|
+
context
|
|
1562
|
+
});
|
|
1544
1563
|
return defaultValue;
|
|
1545
1564
|
}
|
|
1546
1565
|
}
|
|
1566
|
+
dispatchEvaluationEvent(event) {
|
|
1567
|
+
setTimeout(() => this.eventEmitter.emit("configEvaluated", { evaluation: event }), 0);
|
|
1568
|
+
}
|
|
1547
1569
|
parseUrl(url) {
|
|
1548
1570
|
if (!url) return;
|
|
1549
1571
|
try {
|
|
@@ -1614,7 +1636,7 @@ var plugin_default = defineNitroPlugin((nitroApp) => {
|
|
|
1614
1636
|
const { serverSdkKey, baseUrl } = runtimeConfig.configdirector;
|
|
1615
1637
|
const client = new DefaultConfigDirectorClient(serverSdkKey, {
|
|
1616
1638
|
sdkName: "nuxt-sdk",
|
|
1617
|
-
sdkVersion: "0.
|
|
1639
|
+
sdkVersion: "0.9.0"
|
|
1618
1640
|
}, {
|
|
1619
1641
|
logger,
|
|
1620
1642
|
...baseUrl ? { connection: { url: baseUrl } } : {}
|
|
@@ -515,7 +515,7 @@ const parseConfigValue = (configState, defaultValue) => {
|
|
|
515
515
|
usedDefault: false,
|
|
516
516
|
reason: "found-match"
|
|
517
517
|
};
|
|
518
|
-
if (typeof defaultValue === "boolean" && configState.type === "boolean") {
|
|
518
|
+
if (typeof defaultValue === "boolean" && (configState.type === "boolean" || configState.type === "string")) {
|
|
519
519
|
const boolValue = parseConfigBoolean(value);
|
|
520
520
|
const hasBoolean = typeof boolValue === "boolean";
|
|
521
521
|
return {
|
|
@@ -537,7 +537,7 @@ const parseConfigValue = (configState, defaultValue) => {
|
|
|
537
537
|
reason: hasNumber ? "found-match" : "invalid-number"
|
|
538
538
|
};
|
|
539
539
|
}
|
|
540
|
-
if (isNumericNativeType(typeof defaultValue) && (configState.type === "float" || configState.type === "enum")) {
|
|
540
|
+
if (isNumericNativeType(typeof defaultValue) && (configState.type === "float" || configState.type === "enum" || configState.type === "string")) {
|
|
541
541
|
const numValue = parseConfigFloat(value);
|
|
542
542
|
const hasNumber = typeof numValue === "number";
|
|
543
543
|
return {
|
|
@@ -868,18 +868,27 @@ var DefaultConfigDirectorClient = class {
|
|
|
868
868
|
const configKeys = Object.keys(configSet.configs);
|
|
869
869
|
if (!this.configSet || configSet.kind == "full") {
|
|
870
870
|
this.configSet = configSet;
|
|
871
|
-
this.
|
|
871
|
+
this.emit("configsUpdated", { keys: configKeys });
|
|
872
872
|
this.updateWatchers(configSet.configs);
|
|
873
873
|
} else {
|
|
874
874
|
this.configSet.configs = {
|
|
875
875
|
...this.configSet.configs,
|
|
876
876
|
...configSet.configs
|
|
877
877
|
};
|
|
878
|
-
this.
|
|
878
|
+
this.emit("configsUpdated", { keys: configKeys });
|
|
879
879
|
this.updateWatchers(configSet.configs);
|
|
880
880
|
}
|
|
881
881
|
this.logger.debug("[ConfigDirectorClient] ConfigSet updated from server:", { keys: configKeys });
|
|
882
882
|
});
|
|
883
|
+
this.registerHandler("clientReady", clientOptions?.hooks?.clientReady);
|
|
884
|
+
this.registerHandler("configEvaluated", clientOptions?.hooks?.configEvaluated);
|
|
885
|
+
this.registerHandler("configsUpdated", clientOptions?.hooks?.configsUpdated);
|
|
886
|
+
this.registerHandler("contextUpdated", clientOptions?.hooks?.contextUpdated);
|
|
887
|
+
}
|
|
888
|
+
registerHandler(event, handler) {
|
|
889
|
+
if (!handler) return;
|
|
890
|
+
const handlers = Array.isArray(handler) ? handler : [handler];
|
|
891
|
+
for (const h of handlers) if (typeof h === "function") this.on(event, h);
|
|
883
892
|
}
|
|
884
893
|
getTransportConstructor(connectionMode) {
|
|
885
894
|
switch (connectionMode) {
|
|
@@ -906,14 +915,14 @@ var DefaultConfigDirectorClient = class {
|
|
|
906
915
|
}).then(() => {
|
|
907
916
|
this.ready = true;
|
|
908
917
|
this.initializing = false;
|
|
909
|
-
this.
|
|
918
|
+
this.emit("clientReady", { action: caller });
|
|
910
919
|
this.logger.debug("[ConfigDirectorClient] Received initial payload from the server, client is ready");
|
|
911
920
|
});
|
|
912
921
|
const startTime = (/* @__PURE__ */ new Date()).getTime();
|
|
913
922
|
await this.transport.connect(context ?? {}, this.timeout);
|
|
914
923
|
this.currentContext = context;
|
|
915
924
|
this.telemetryClient.updateContext(context);
|
|
916
|
-
this.
|
|
925
|
+
this.emit("contextUpdated", { context });
|
|
917
926
|
const elapsedTime = (/* @__PURE__ */ new Date()).getTime() - startTime;
|
|
918
927
|
const remainingTimeout = this.timeout - elapsedTime;
|
|
919
928
|
clearTimeout(this.timeoutTimer);
|
|
@@ -963,6 +972,7 @@ var DefaultConfigDirectorClient = class {
|
|
|
963
972
|
getValueFromConfigState(configKey, configState, defaultValue) {
|
|
964
973
|
if (!configState) {
|
|
965
974
|
this.logger.debug(`[ConfigDirectorClient] No config state found for '${configKey}', returning default value '${defaultValue}'`);
|
|
975
|
+
const reason = this.isReady ? "config-state-missing" : "client-not-ready";
|
|
966
976
|
this.telemetryClient.evaluatedConfig({
|
|
967
977
|
contextId: this.currentContext?.id,
|
|
968
978
|
key: configKey,
|
|
@@ -970,7 +980,14 @@ var DefaultConfigDirectorClient = class {
|
|
|
970
980
|
requestedType: getRequestedType(defaultValue),
|
|
971
981
|
evaluatedValue: defaultValue,
|
|
972
982
|
usedDefault: true,
|
|
973
|
-
evaluationReason:
|
|
983
|
+
evaluationReason: reason
|
|
984
|
+
});
|
|
985
|
+
this.dispatchEvaluationEvent({
|
|
986
|
+
key: configKey,
|
|
987
|
+
value: defaultValue,
|
|
988
|
+
isDefaultValue: true,
|
|
989
|
+
reason,
|
|
990
|
+
context: this.context
|
|
974
991
|
});
|
|
975
992
|
return defaultValue;
|
|
976
993
|
}
|
|
@@ -986,9 +1003,27 @@ var DefaultConfigDirectorClient = class {
|
|
|
986
1003
|
usedDefault: parseResult.usedDefault,
|
|
987
1004
|
evaluationReason: parseResult.reason
|
|
988
1005
|
});
|
|
1006
|
+
this.dispatchEvaluationEvent({
|
|
1007
|
+
key: configKey,
|
|
1008
|
+
value: parseResult.parsedValue,
|
|
1009
|
+
valueId: parseResult.parsedValueId ?? void 0,
|
|
1010
|
+
isDefaultValue: parseResult.usedDefault,
|
|
1011
|
+
reason: parseResult.reason,
|
|
1012
|
+
context: this.context
|
|
1013
|
+
});
|
|
989
1014
|
this.logger.debug(`[ConfigDirectorClient] Evaluated '${configKey}' to '${parseResult.parsedValue}'`);
|
|
990
1015
|
return parseResult.parsedValue;
|
|
991
1016
|
}
|
|
1017
|
+
dispatchEvaluationEvent(event) {
|
|
1018
|
+
setTimeout(() => this.emit("configEvaluated", { evaluation: event }), 0);
|
|
1019
|
+
}
|
|
1020
|
+
emit(name, payload) {
|
|
1021
|
+
try {
|
|
1022
|
+
this.eventEmitter.emit(name, payload);
|
|
1023
|
+
} catch (error) {
|
|
1024
|
+
this.logger.error(`[ConfigDirectorClient] Error executing event handlers for '${name}'`, error);
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
992
1027
|
parseUrl(url, urlFactory) {
|
|
993
1028
|
if (!url) return;
|
|
994
1029
|
try {
|
|
@@ -1089,7 +1124,7 @@ var WebWorkerTelemetryClient = class {
|
|
|
1089
1124
|
flushIntervalDelay: options.flushIntervalDelay ?? 3e4
|
|
1090
1125
|
}
|
|
1091
1126
|
};
|
|
1092
|
-
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.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 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(){(await this.flush()).fatalError?(this.collectEvents=!1,this.close(),this.logger.warn(`[TelemetryEventCollector] Received a fatal error from telemetry collection. No longer collecting events.`)):this.flushTimeout=setTimeout(()=>this.flushAndScheduleNext(),this.flushIntervalDelay)}cancelScheduledFlush(){clearTimeout(this.flushTimeout)}async close(){this.collectEvents=!1,clearTimeout(this.flushTimeout),await this.flush(),this.evaluationEventQueue.clear()}dispose(){this.close()}},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=async e=>{let t=
|
|
1127
|
+
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.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 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(){(await this.flush()).fatalError?(this.collectEvents=!1,this.close(),this.logger.warn(`[TelemetryEventCollector] Received a fatal error from telemetry collection. No longer collecting events.`)):this.flushTimeout=setTimeout(()=>this.flushAndScheduleNext(),this.flushIntervalDelay)}cancelScheduledFlush(){clearTimeout(this.flushTimeout)}async close(){this.collectEvents=!1,clearTimeout(this.flushTimeout),await this.flush(),this.evaluationEventQueue.clear()}dispose(){this.close()}},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=>{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`: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" })));
|
|
1093
1128
|
this.worker.onmessage = (message) => {
|
|
1094
1129
|
this.handleWorkerEvent(message.data);
|
|
1095
1130
|
};
|
|
@@ -1170,6 +1205,29 @@ var WebWorkerTelemetryClient = class {
|
|
|
1170
1205
|
}
|
|
1171
1206
|
};
|
|
1172
1207
|
//#endregion
|
|
1208
|
+
//#region ../shared/src/base62-encoder.ts
|
|
1209
|
+
const BASE62 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
|
1210
|
+
const toBase62 = (buf, length) => {
|
|
1211
|
+
const ZERO = BigInt(0);
|
|
1212
|
+
const BASE = BigInt(62);
|
|
1213
|
+
const hex = Array.from(buf).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
1214
|
+
let n = BigInt("0x" + hex);
|
|
1215
|
+
let result = "";
|
|
1216
|
+
while (n > ZERO) {
|
|
1217
|
+
result = BASE62[Number(n % BASE)] + result;
|
|
1218
|
+
n /= BASE;
|
|
1219
|
+
}
|
|
1220
|
+
return result.padStart(length, "0");
|
|
1221
|
+
};
|
|
1222
|
+
const BASE62_LENGTH = Math.ceil(128 / Math.log2(62));
|
|
1223
|
+
//#endregion
|
|
1224
|
+
//#region ../js-browser-client/src/telemetry/value-id-generator.ts
|
|
1225
|
+
const encoder = new TextEncoder();
|
|
1226
|
+
const generateValueId = async (v) => {
|
|
1227
|
+
const hashBuffer = await crypto.subtle.digest("SHA-256", encoder.encode(v?.toString() ?? ""));
|
|
1228
|
+
return toBase62(new Uint8Array(hashBuffer, 0, 16), BASE62_LENGTH);
|
|
1229
|
+
};
|
|
1230
|
+
//#endregion
|
|
1173
1231
|
//#region ../js-browser-client/src/BrowserConfigDirectorClient.ts
|
|
1174
1232
|
const createBrowserClient = (clientSdkKey, sdkOptions, clientOptions) => {
|
|
1175
1233
|
const logger = clientOptions?.logger ?? createDefaultLogger$1();
|
|
@@ -1177,7 +1235,8 @@ const createBrowserClient = (clientSdkKey, sdkOptions, clientOptions) => {
|
|
|
1177
1235
|
sdkKey: clientSdkKey,
|
|
1178
1236
|
logger,
|
|
1179
1237
|
baseUrl: parseUrl(clientOptions?.connection?.url) ?? "https://client-sdk-api.configdirector.com",
|
|
1180
|
-
urlFactory: defaultUrlFactory
|
|
1238
|
+
urlFactory: defaultUrlFactory,
|
|
1239
|
+
valueIdGenerator: generateValueId
|
|
1181
1240
|
}), clientSdkKey, sdkOptions, {
|
|
1182
1241
|
...clientOptions,
|
|
1183
1242
|
logger
|
|
@@ -1236,7 +1295,7 @@ var plugin_client_default = defineNuxtPlugin((nuxtApp) => {
|
|
|
1236
1295
|
const { clientSdkKey, appName, appVersion, baseUrl } = runtimeConfig.public.configdirector;
|
|
1237
1296
|
const client = createBrowserClient(clientSdkKey, {
|
|
1238
1297
|
sdkName: "nuxt-sdk",
|
|
1239
|
-
sdkVersion: "0.
|
|
1298
|
+
sdkVersion: "0.9.0"
|
|
1240
1299
|
}, {
|
|
1241
1300
|
metadata: {
|
|
1242
1301
|
appName,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { n as ConfigDirectorClient } from "../../types-CzmBIfqo.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 {
|
|
1
|
+
import { n as ConfigDirectorClient } from "../../types-CzmBIfqo.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/runtime/server/composables/useConfigDirectorClient.d.ts
|
|
4
4
|
declare const useConfigDirectorClient: () => ConfigDirectorClient;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { t as ClientHooks } from "../../types-CzmBIfqo.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/runtime/server/composables/useConfigDirectorServerHooks.d.ts
|
|
4
|
+
declare const useConfigDirectorServerHooks: (hooks: ClientHooks) => void;
|
|
5
|
+
//#endregion
|
|
6
|
+
export { useConfigDirectorServerHooks };
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { t as ClientHooks } from "../../types-CzmBIfqo.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/runtime/server/composables/useConfigDirectorServerHooks.d.ts
|
|
4
|
+
declare const useConfigDirectorServerHooks: (hooks: ClientHooks) => void;
|
|
5
|
+
//#endregion
|
|
6
|
+
export { useConfigDirectorServerHooks };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { useNitroApp } from "nitropack/runtime";
|
|
2
|
+
//#region src/runtime/server/composables/useConfigDirectorServerHooks.ts
|
|
3
|
+
const useConfigDirectorServerHooks = (hooks) => {
|
|
4
|
+
const client = useNitroApp().configDirectorClient;
|
|
5
|
+
if (!client) throw new Error("The ConfigDirector Nitro plugin was not initialized");
|
|
6
|
+
const entries = Object.entries(hooks);
|
|
7
|
+
for (const [event, handler] of entries) {
|
|
8
|
+
if (!handler) continue;
|
|
9
|
+
const handlers = Array.isArray(handler) ? handler : [handler];
|
|
10
|
+
for (const h of handlers) client.on(event, h);
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
//#endregion
|
|
14
|
+
export { useConfigDirectorServerHooks };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { i as ConfigValueType, n as ConfigEvaluation, t as ConfigDirectorContext } from "./types-CHmsCChN.mjs";
|
|
2
2
|
|
|
3
3
|
//#region ../js-client-core/src/Emitter.d.ts
|
|
4
4
|
type EventType = string | symbol;
|
|
@@ -11,7 +11,14 @@ interface EventProvider<TEventsRecord extends EventsRecord> {
|
|
|
11
11
|
}
|
|
12
12
|
//#endregion
|
|
13
13
|
//#region ../js-client-core/src/types.d.ts
|
|
14
|
+
type ClientHooks = {
|
|
15
|
+
clientReady?: HookHandler<"clientReady"> | HookHandler<"clientReady">[];
|
|
16
|
+
configsUpdated?: HookHandler<"configsUpdated"> | HookHandler<"configsUpdated">[];
|
|
17
|
+
contextUpdated?: HookHandler<"contextUpdated"> | HookHandler<"contextUpdated">[];
|
|
18
|
+
configEvaluated?: HookHandler<"configEvaluated"> | HookHandler<"configEvaluated">[];
|
|
19
|
+
};
|
|
14
20
|
type ClientConnectAction = "initialization" | "context update" | "network resume";
|
|
21
|
+
type HookHandler<TEvent extends keyof ClientEvents> = (payload: ClientEvents[TEvent]) => void;
|
|
15
22
|
type ClientEvents = {
|
|
16
23
|
configsUpdated: {
|
|
17
24
|
keys: string[];
|
|
@@ -22,6 +29,9 @@ type ClientEvents = {
|
|
|
22
29
|
contextUpdated: {
|
|
23
30
|
context: ConfigDirectorContext | undefined;
|
|
24
31
|
};
|
|
32
|
+
configEvaluated: {
|
|
33
|
+
evaluation: ConfigEvaluation;
|
|
34
|
+
};
|
|
25
35
|
};
|
|
26
36
|
type WatchHandler<T extends ConfigValueType> = (message: T) => void;
|
|
27
37
|
/**
|
|
@@ -132,4 +142,4 @@ interface ConfigDirectorClient extends EventProvider<ClientEvents> {
|
|
|
132
142
|
dispose(): void;
|
|
133
143
|
}
|
|
134
144
|
//#endregion
|
|
135
|
-
export { ConfigDirectorClient as t };
|
|
145
|
+
export { ConfigDirectorClient as n, ClientHooks as t };
|
|
@@ -12,6 +12,15 @@ type ConfigState = {
|
|
|
12
12
|
value: string | undefined | null;
|
|
13
13
|
valueId?: string | undefined | null;
|
|
14
14
|
};
|
|
15
|
+
type EvaluationReason = "found-match" | "config-state-missing" | "client-not-ready" | "type-mismatch" | "value-missing" | "invalid-number" | "invalid-json" | "invalid-boolean";
|
|
16
|
+
type ConfigEvaluation = {
|
|
17
|
+
key: string;
|
|
18
|
+
value: ConfigValueType;
|
|
19
|
+
valueId?: string;
|
|
20
|
+
isDefaultValue: boolean;
|
|
21
|
+
reason: EvaluationReason;
|
|
22
|
+
context?: ConfigDirectorContext;
|
|
23
|
+
};
|
|
15
24
|
/**
|
|
16
25
|
* The user's context to be sent to ConfigDirector. This context will be used for targeting
|
|
17
26
|
* rules evaluation.
|
|
@@ -48,4 +57,4 @@ type ConfigDirectorContext = {
|
|
|
48
57
|
anonymous?: boolean;
|
|
49
58
|
};
|
|
50
59
|
//#endregion
|
|
51
|
-
export {
|
|
60
|
+
export { ConfigValueType as i, ConfigEvaluation as n, ConfigState as r, ConfigDirectorContext as t };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as
|
|
1
|
+
import { i as ConfigValueType, n as ConfigEvaluation, r as ConfigState, t as ConfigDirectorContext } from "./types-CHmsCChN.mjs";
|
|
2
2
|
|
|
3
3
|
//#region ../js-server-sdk/src/types.d.ts
|
|
4
4
|
type ClientEvents = {
|
|
@@ -6,7 +6,16 @@ type ClientEvents = {
|
|
|
6
6
|
keys: string[];
|
|
7
7
|
};
|
|
8
8
|
clientReady: undefined;
|
|
9
|
+
configEvaluated: {
|
|
10
|
+
evaluation: ConfigEvaluation;
|
|
11
|
+
};
|
|
12
|
+
};
|
|
13
|
+
type ClientHooks = {
|
|
14
|
+
clientReady?: HookHandler<"clientReady"> | HookHandler<"clientReady">[];
|
|
15
|
+
configsUpdated?: HookHandler<"configsUpdated"> | HookHandler<"configsUpdated">[];
|
|
16
|
+
configEvaluated?: HookHandler<"configEvaluated"> | HookHandler<"configEvaluated">[];
|
|
9
17
|
};
|
|
18
|
+
type HookHandler<TEvent extends keyof ClientEvents> = (payload: ClientEvents[TEvent]) => void;
|
|
10
19
|
type WatchHandler<T extends ConfigValueType> = (message: T) => void;
|
|
11
20
|
/**
|
|
12
21
|
* The ConfigDirector SDK client object.
|
|
@@ -93,4 +102,4 @@ interface ConfigDirectorClient {
|
|
|
93
102
|
dispose(): void;
|
|
94
103
|
}
|
|
95
104
|
//#endregion
|
|
96
|
-
export { ConfigDirectorClient as t };
|
|
105
|
+
export { ConfigDirectorClient as n, ClientHooks as t };
|
package/package.json
CHANGED