@configdirector/nuxt-sdk 1.12.0 → 1.13.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 +75 -1
- package/dist/module.json +1 -1
- 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 +1 -1
- package/dist/runtime/app/composables/useConfigDirectorClientHooks.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/{native-values-ChQdi7c7.d.mts → native-values-CfhP0yLS.d.mts} +17 -6
- package/dist/runtime/nitro/offline-values.d.mts +1 -1
- package/dist/runtime/nitro/offline-values.d.ts +1 -1
- package/dist/runtime/nitro/plugin.d.mts +2 -2
- package/dist/runtime/nitro/plugin.d.ts +2 -2
- package/dist/runtime/nitro/plugin.mjs +54 -30
- package/dist/runtime/plugin.client.mjs +99 -55
- package/dist/runtime/plugin.server.mjs +3 -0
- package/dist/runtime/server/composables/useConfigDirectorServerClient.d.mts +1 -1
- package/dist/runtime/server/composables/useConfigDirectorServerClient.d.ts +1 -1
- package/dist/runtime/server/composables/useConfigDirectorServerHooks.d.mts +1 -1
- package/dist/runtime/server/composables/useConfigDirectorServerHooks.d.ts +1 -1
- package/dist/runtime/{types-CuzrFq9I.d.mts → types-BvqduMQP.d.mts} +11 -1
- package/dist/runtime/{types-BoDJiQsr.d.mts → types-qDEc6g8R.d.mts} +12 -1
- package/dist/testing.d.mts +27 -6
- package/dist/testing.mjs +66 -42
- package/package.json +2 -2
package/dist/module.d.mts
CHANGED
|
@@ -2,6 +2,80 @@ import * as _$_nuxt_schema0 from "@nuxt/schema";
|
|
|
2
2
|
import { LogLevel } from "consola";
|
|
3
3
|
|
|
4
4
|
//#region ../shared/src/types.d.ts
|
|
5
|
+
type ConfigEnumLikeType = {
|
|
6
|
+
[key: string]: string | number;
|
|
7
|
+
};
|
|
8
|
+
type ConfigValueType = string | number | boolean | object | ConfigEnumLikeType;
|
|
9
|
+
/**
|
|
10
|
+
* Why an evaluation returned the value it did. `found-match` returns a value from ConfigDirector,
|
|
11
|
+
* and `cached` a value kept on the device while a client SDK waits for live values. `value-missing`
|
|
12
|
+
* returns the in-code default value because the config has no value. Every other reason returns the
|
|
13
|
+
* in-code default value because of an error: the config does not exist (`config-state-missing`), no
|
|
14
|
+
* values have arrived yet (`client-not-ready`), or the value cannot be read as the type of the in-code
|
|
15
|
+
* default value.
|
|
16
|
+
*/
|
|
17
|
+
type EvaluationReason = "found-match" | "config-state-missing" | "client-not-ready" | "type-mismatch" | "value-missing" | "invalid-number" | "invalid-json" | "invalid-boolean" | "cached";
|
|
18
|
+
/**
|
|
19
|
+
* One evaluation of a config: the value it returned, why, and the context it was evaluated for.
|
|
20
|
+
*/
|
|
21
|
+
type ConfigEvaluation<T extends ConfigValueType = ConfigValueType> = {
|
|
22
|
+
/** The config key that was evaluated. */key: string; /** The value returned, which is the in-code default value when {@link isDefaultValue} is `true`. */
|
|
23
|
+
value: T; /** The ID ConfigDirector assigned to the value returned, when ConfigDirector sent one with it. */
|
|
24
|
+
valueId?: string; /** Whether the evaluation returned the in-code default value. */
|
|
25
|
+
isDefaultValue: boolean; /** Why the evaluation returned this value. */
|
|
26
|
+
reason: EvaluationReason; /** The context the config was evaluated for, if any. */
|
|
27
|
+
context?: ConfigDirectorContext;
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* The context to be sent to ConfigDirector: the user or other entity, such as a device or an
|
|
31
|
+
* organization, that configs are evaluated for. This context will be used for targeting rules
|
|
32
|
+
* evaluation.
|
|
33
|
+
*/
|
|
34
|
+
type ConfigDirectorContext = {
|
|
35
|
+
/**
|
|
36
|
+
* The context's identifier. This should be a value that uniquely identifies the user or other
|
|
37
|
+
* entity among contexts of the same {@link kind}.
|
|
38
|
+
* In the case of anonymous users, you could generate a UUID or alternatively not provide
|
|
39
|
+
* the {@link id}. In a client SDK, ConfigDirector then derives an identifier from an ID the SDK
|
|
40
|
+
* stores for the app install or the website, so it stays the same until the app is uninstalled
|
|
41
|
+
* or the site's data is cleared. However, keep in mind that this value is used for segmenting
|
|
42
|
+
* contexts in percentage rollouts, and changes to the {@link id} could result in the context
|
|
43
|
+
* being assigned to a different percentile.
|
|
44
|
+
*/
|
|
45
|
+
id?: string;
|
|
46
|
+
/**
|
|
47
|
+
* The kind of entity this context represents, such as `user`, `device`, `organization`,
|
|
48
|
+
* `tenant` or `listing`. You can use any other kind that fits your application. A kind has 1
|
|
49
|
+
* to 64 characters, using only lowercase letters (`a`-`z`), digits, `-` and `_`. It is used
|
|
50
|
+
* exactly as given, without trimming or changing its case.
|
|
51
|
+
*
|
|
52
|
+
* A context with an invalid kind is ignored as a whole: the SDK logs an error and evaluates
|
|
53
|
+
* as if no context had been provided.
|
|
54
|
+
*
|
|
55
|
+
* @default "user"
|
|
56
|
+
*/
|
|
57
|
+
kind?: string;
|
|
58
|
+
/**
|
|
59
|
+
* The context's display name. This will be shown in the ConfigDirector dashboard and may be
|
|
60
|
+
* used for targeting rules.
|
|
61
|
+
*/
|
|
62
|
+
name?: string;
|
|
63
|
+
/**
|
|
64
|
+
* Any arbitrary traits for the context. They will be shown in the ConfigDirector dashboard
|
|
65
|
+
* and may be used for targeting rules.
|
|
66
|
+
*/
|
|
67
|
+
traits?: {
|
|
68
|
+
[key: string]: unknown;
|
|
69
|
+
};
|
|
70
|
+
/**
|
|
71
|
+
* Whether or not to treat the context as anonymous during evaluation. When `true`, the
|
|
72
|
+
* context values will be used for targeting rules evaluation but the context will not be
|
|
73
|
+
* persisted and will not appear in the dashboard.
|
|
74
|
+
*
|
|
75
|
+
* @default false
|
|
76
|
+
*/
|
|
77
|
+
anonymous?: boolean;
|
|
78
|
+
};
|
|
5
79
|
type ConnectionMode = "streaming" | "polling" | "offline";
|
|
6
80
|
//#endregion
|
|
7
81
|
//#region ../shared/src/native-values.d.ts
|
|
@@ -219,4 +293,4 @@ interface ModuleOptions {
|
|
|
219
293
|
}
|
|
220
294
|
declare const _default: _$_nuxt_schema0.NuxtModule<ModuleOptions, ModuleOptions, false>;
|
|
221
295
|
//#endregion
|
|
222
|
-
export { ModuleOptions, type OfflineValue, type OfflineValues, _default as default };
|
|
296
|
+
export { type ConfigEvaluation, type EvaluationReason, ModuleOptions, type OfflineValue, type OfflineValues, _default as default };
|
package/dist/module.json
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as ClientHooks } from "../../types-
|
|
1
|
+
import { t as ClientHooks } from "../../types-BvqduMQP.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/runtime/app/composables/useConfigDirectorClientHooks.d.ts
|
|
4
4
|
declare const useConfigDirectorClientHooks: (hooks: ClientHooks) => void;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as ClientHooks } from "../../types-
|
|
1
|
+
import { t as ClientHooks } from "../../types-BvqduMQP.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/runtime/app/composables/useConfigDirectorClientHooks.d.ts
|
|
4
4
|
declare const useConfigDirectorClientHooks: (hooks: ClientHooks) => void;
|
|
@@ -12,13 +12,24 @@ type ConfigState = {
|
|
|
12
12
|
value: string | undefined | null;
|
|
13
13
|
valueId?: string | undefined | null;
|
|
14
14
|
};
|
|
15
|
+
/**
|
|
16
|
+
* Why an evaluation returned the value it did. `found-match` returns a value from ConfigDirector,
|
|
17
|
+
* and `cached` a value kept on the device while a client SDK waits for live values. `value-missing`
|
|
18
|
+
* returns the in-code default value because the config has no value. Every other reason returns the
|
|
19
|
+
* in-code default value because of an error: the config does not exist (`config-state-missing`), no
|
|
20
|
+
* values have arrived yet (`client-not-ready`), or the value cannot be read as the type of the in-code
|
|
21
|
+
* default value.
|
|
22
|
+
*/
|
|
15
23
|
type EvaluationReason = "found-match" | "config-state-missing" | "client-not-ready" | "type-mismatch" | "value-missing" | "invalid-number" | "invalid-json" | "invalid-boolean" | "cached";
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
24
|
+
/**
|
|
25
|
+
* One evaluation of a config: the value it returned, why, and the context it was evaluated for.
|
|
26
|
+
*/
|
|
27
|
+
type ConfigEvaluation<T extends ConfigValueType = ConfigValueType> = {
|
|
28
|
+
/** The config key that was evaluated. */key: string; /** The value returned, which is the in-code default value when {@link isDefaultValue} is `true`. */
|
|
29
|
+
value: T; /** The ID ConfigDirector assigned to the value returned, when ConfigDirector sent one with it. */
|
|
30
|
+
valueId?: string; /** Whether the evaluation returned the in-code default value. */
|
|
31
|
+
isDefaultValue: boolean; /** Why the evaluation returned this value. */
|
|
32
|
+
reason: EvaluationReason; /** The context the config was evaluated for, if any. */
|
|
22
33
|
context?: ConfigDirectorContext;
|
|
23
34
|
};
|
|
24
35
|
/**
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { o as ConnectionMode } from "../native-values-
|
|
2
|
-
import { n as ConfigDirectorClient } from "../types-
|
|
1
|
+
import { o as ConnectionMode } from "../native-values-CfhP0yLS.mjs";
|
|
2
|
+
import { n as ConfigDirectorClient } from "../types-qDEc6g8R.mjs";
|
|
3
3
|
import * as _$nitropack from "nitropack";
|
|
4
4
|
|
|
5
5
|
//#region src/runtime/nitro/plugin.d.ts
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { o as ConnectionMode } from "../native-values-
|
|
2
|
-
import { n as ConfigDirectorClient } from "../types-
|
|
1
|
+
import { o as ConnectionMode } from "../native-values-CfhP0yLS.mjs";
|
|
2
|
+
import { n as ConfigDirectorClient } from "../types-qDEc6g8R.mjs";
|
|
3
3
|
import * as _$nitropack from "nitropack";
|
|
4
4
|
|
|
5
5
|
//#region src/runtime/nitro/plugin.d.ts
|
|
@@ -7252,19 +7252,22 @@ var ConfigEvaluator = class {
|
|
|
7252
7252
|
this.logger = logger;
|
|
7253
7253
|
}
|
|
7254
7254
|
evaluate(config, context, segments) {
|
|
7255
|
+
const explanation = this.explain(config, context, segments);
|
|
7255
7256
|
return {
|
|
7256
7257
|
id: config.id,
|
|
7257
7258
|
key: config.key,
|
|
7258
7259
|
type: config.type,
|
|
7259
|
-
value:
|
|
7260
|
+
value: explanation.value,
|
|
7261
|
+
valueId: explanation.valueId
|
|
7260
7262
|
};
|
|
7261
7263
|
}
|
|
7262
7264
|
/**
|
|
7263
7265
|
* Evaluate a config for a context and record how the value was reached: every rule in the
|
|
7264
7266
|
* order it was walked with its outcome, the conditions that were checked with what they
|
|
7265
7267
|
* resolved to, and the share a rollout assigned the context to. `evaluate` is this walk with
|
|
7266
|
-
* only the value kept, so the two cannot disagree. `segments` is the payload's
|
|
7267
|
-
* which a segment condition looks its segment up in; a segment it does not hold
|
|
7268
|
+
* only the value and its value ID kept, so the two cannot disagree. `segments` is the payload's
|
|
7269
|
+
* segments map, which a segment condition looks its segment up in; a segment it does not hold
|
|
7270
|
+
* never matches.
|
|
7268
7271
|
*/
|
|
7269
7272
|
explain(config, context, segments) {
|
|
7270
7273
|
const rules = [...config.target?.rules ?? []].sort((a, b) => (a.order ?? Number.MAX_SAFE_INTEGER) - (b.order ?? Number.MAX_SAFE_INTEGER));
|
|
@@ -7282,17 +7285,24 @@ var ConfigEvaluator = class {
|
|
|
7282
7285
|
}
|
|
7283
7286
|
const result = this.explainRule(rule, config, context, segments);
|
|
7284
7287
|
explanations.push(result.explanation);
|
|
7285
|
-
if (result.
|
|
7288
|
+
if (result.served !== void 0) served = {
|
|
7286
7289
|
ruleId: rule.id,
|
|
7287
|
-
|
|
7290
|
+
...result.served
|
|
7288
7291
|
};
|
|
7289
7292
|
}
|
|
7290
|
-
return {
|
|
7291
|
-
value: served
|
|
7292
|
-
|
|
7293
|
+
if (served) return {
|
|
7294
|
+
value: served.value,
|
|
7295
|
+
valueId: served.valueId,
|
|
7296
|
+
servedBy: {
|
|
7293
7297
|
kind: "rule",
|
|
7294
7298
|
ruleId: served.ruleId
|
|
7295
|
-
}
|
|
7299
|
+
},
|
|
7300
|
+
rules: explanations
|
|
7301
|
+
};
|
|
7302
|
+
return {
|
|
7303
|
+
value: config.target?.defaultValue,
|
|
7304
|
+
valueId: config.target?.defaultValueId,
|
|
7305
|
+
servedBy: { kind: "default" },
|
|
7296
7306
|
rules: explanations
|
|
7297
7307
|
};
|
|
7298
7308
|
}
|
|
@@ -7304,13 +7314,13 @@ var ConfigEvaluator = class {
|
|
|
7304
7314
|
bucket: void 0
|
|
7305
7315
|
};
|
|
7306
7316
|
try {
|
|
7307
|
-
let
|
|
7308
|
-
if (rule.type == "percentage")
|
|
7309
|
-
else if (rule.type == "conditional")
|
|
7310
|
-
if (
|
|
7317
|
+
let served;
|
|
7318
|
+
if (rule.type == "percentage") served = this.explainPercentage(rule.percentages ?? [], config, context, explanation);
|
|
7319
|
+
else if (rule.type == "conditional") served = this.explainConditionalRule(rule, config, context, segments, explanation);
|
|
7320
|
+
if (served !== void 0) explanation.outcome = "matched";
|
|
7311
7321
|
return {
|
|
7312
7322
|
explanation,
|
|
7313
|
-
|
|
7323
|
+
served
|
|
7314
7324
|
};
|
|
7315
7325
|
} catch (error) {
|
|
7316
7326
|
this.logger.warn(`There was an error while evaluating a targeting rule '${rule?.id}' for '${config?.key}'. The rule will be disregarded.`, {
|
|
@@ -7325,7 +7335,7 @@ var ConfigEvaluator = class {
|
|
|
7325
7335
|
conditions: [],
|
|
7326
7336
|
bucket: void 0
|
|
7327
7337
|
},
|
|
7328
|
-
|
|
7338
|
+
served: void 0
|
|
7329
7339
|
};
|
|
7330
7340
|
}
|
|
7331
7341
|
}
|
|
@@ -7357,7 +7367,10 @@ var ConfigEvaluator = class {
|
|
|
7357
7367
|
shares,
|
|
7358
7368
|
selectedPercentageId: selected?.id
|
|
7359
7369
|
};
|
|
7360
|
-
return selected?.value != null ?
|
|
7370
|
+
return selected?.value != null ? {
|
|
7371
|
+
value: selected.value.toString(),
|
|
7372
|
+
valueId: selected.valueId
|
|
7373
|
+
} : void 0;
|
|
7361
7374
|
}
|
|
7362
7375
|
explainConditionalRule(rule, config, context, segments, explanation) {
|
|
7363
7376
|
let failed = false;
|
|
@@ -7374,7 +7387,10 @@ var ConfigEvaluator = class {
|
|
|
7374
7387
|
failed = checked.outcome !== "matched";
|
|
7375
7388
|
}
|
|
7376
7389
|
if (failed) return;
|
|
7377
|
-
if (rule.target == "value") return rule.value != null ?
|
|
7390
|
+
if (rule.target == "value") return rule.value != null ? {
|
|
7391
|
+
value: rule.value.toString(),
|
|
7392
|
+
valueId: rule.valueId
|
|
7393
|
+
} : void 0;
|
|
7378
7394
|
if (rule.target == "percentage") return this.explainPercentage(rule.percentages ?? [], config, context, explanation);
|
|
7379
7395
|
}
|
|
7380
7396
|
explainCondition(condition, context, segments) {
|
|
@@ -7551,9 +7567,12 @@ const parseJson = (jsonString, defaultValue, requestedType, valueId) => {
|
|
|
7551
7567
|
};
|
|
7552
7568
|
const isJsonTypeCompatible = (parsed, defaultValue) => {
|
|
7553
7569
|
if (parsed === null) return false;
|
|
7554
|
-
|
|
7555
|
-
|
|
7556
|
-
|
|
7570
|
+
return jsonShapeOf(parsed) === jsonShapeOf(defaultValue);
|
|
7571
|
+
};
|
|
7572
|
+
const jsonShapeOf = (value) => {
|
|
7573
|
+
if (Array.isArray(value)) return "array";
|
|
7574
|
+
const nativeType = typeof value;
|
|
7575
|
+
return nativeType === "bigint" ? "number" : nativeType;
|
|
7557
7576
|
};
|
|
7558
7577
|
//#endregion
|
|
7559
7578
|
//#region ../shared/src/logger.ts
|
|
@@ -8282,7 +8301,7 @@ var DefaultConfigDirectorClient = class DefaultConfigDirectorClient {
|
|
|
8282
8301
|
updateWatchersForConfig(configKey) {
|
|
8283
8302
|
const configDefinition = this.configSet?.configs[configKey];
|
|
8284
8303
|
this.handlersMap.get(configKey)?.forEach((h) => {
|
|
8285
|
-
const value = this.
|
|
8304
|
+
const value = this.evaluationOf(configKey, configDefinition, h.defaultValue, h.context).value;
|
|
8286
8305
|
h.handler(value);
|
|
8287
8306
|
});
|
|
8288
8307
|
}
|
|
@@ -8308,11 +8327,14 @@ var DefaultConfigDirectorClient = class DefaultConfigDirectorClient {
|
|
|
8308
8327
|
} else handlers.splice(0);
|
|
8309
8328
|
}
|
|
8310
8329
|
getValue(configKey, defaultValue, context) {
|
|
8330
|
+
return this.evaluate(configKey, defaultValue, context).value;
|
|
8331
|
+
}
|
|
8332
|
+
evaluate(configKey, defaultValue, context) {
|
|
8311
8333
|
this.validateDefaultValue(defaultValue);
|
|
8312
8334
|
const configDefinition = this.configSet?.configs[configKey];
|
|
8313
|
-
return this.
|
|
8335
|
+
return this.evaluationOf(configKey, configDefinition, defaultValue, context);
|
|
8314
8336
|
}
|
|
8315
|
-
|
|
8337
|
+
evaluationOf(configKey, configDefinition, defaultValue, suppliedContext) {
|
|
8316
8338
|
const context = this.contextToEvaluate(suppliedContext);
|
|
8317
8339
|
if (configDefinition) {
|
|
8318
8340
|
const configState = this.configEvaluator.evaluate(configDefinition, {
|
|
@@ -8333,16 +8355,17 @@ var DefaultConfigDirectorClient = class DefaultConfigDirectorClient {
|
|
|
8333
8355
|
evaluationReason: parseResult.reason
|
|
8334
8356
|
}
|
|
8335
8357
|
});
|
|
8336
|
-
|
|
8358
|
+
const evaluation = {
|
|
8337
8359
|
key: configKey,
|
|
8338
8360
|
value: parseResult.parsedValue,
|
|
8339
8361
|
valueId: parseResult.parsedValueId ?? void 0,
|
|
8340
8362
|
isDefaultValue: parseResult.usedDefault,
|
|
8341
8363
|
reason: parseResult.reason,
|
|
8342
8364
|
context
|
|
8343
|
-
}
|
|
8365
|
+
};
|
|
8366
|
+
this.dispatchEvaluationEvent(evaluation);
|
|
8344
8367
|
this.logger.debug(`[ConfigDirectorClient] Evaluated '${configKey}' to '${parseResult.parsedValue}'`);
|
|
8345
|
-
return
|
|
8368
|
+
return evaluation;
|
|
8346
8369
|
} else {
|
|
8347
8370
|
this.logger.debug(`[ConfigDirectorClient] No config state found for '${configKey}', returning default value '${defaultValue}'`);
|
|
8348
8371
|
const reason = this.isReady ? "config-state-missing" : "client-not-ready";
|
|
@@ -8357,14 +8380,15 @@ var DefaultConfigDirectorClient = class DefaultConfigDirectorClient {
|
|
|
8357
8380
|
evaluationReason: reason
|
|
8358
8381
|
}
|
|
8359
8382
|
});
|
|
8360
|
-
|
|
8383
|
+
const evaluation = {
|
|
8361
8384
|
key: configKey,
|
|
8362
8385
|
value: defaultValue,
|
|
8363
8386
|
isDefaultValue: true,
|
|
8364
8387
|
reason,
|
|
8365
8388
|
context
|
|
8366
|
-
}
|
|
8367
|
-
|
|
8389
|
+
};
|
|
8390
|
+
this.dispatchEvaluationEvent(evaluation);
|
|
8391
|
+
return evaluation;
|
|
8368
8392
|
}
|
|
8369
8393
|
}
|
|
8370
8394
|
contextToEvaluate(suppliedContext) {
|
|
@@ -8452,7 +8476,7 @@ var plugin_default = defineNitroPlugin((nitroApp) => {
|
|
|
8452
8476
|
logger.debug("Initializing ConfigDirector Nitro plugin");
|
|
8453
8477
|
const client = new DefaultConfigDirectorClient(serverSdkKey || void 0, {
|
|
8454
8478
|
sdkName: "nuxt-sdk",
|
|
8455
|
-
sdkVersion: "1.
|
|
8479
|
+
sdkVersion: "1.13.0"
|
|
8456
8480
|
}, {
|
|
8457
8481
|
logger,
|
|
8458
8482
|
connection: {
|
|
@@ -819,9 +819,12 @@ const parseJson = (jsonString, defaultValue, requestedType, valueId) => {
|
|
|
819
819
|
};
|
|
820
820
|
const isJsonTypeCompatible = (parsed, defaultValue) => {
|
|
821
821
|
if (parsed === null) return false;
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
822
|
+
return jsonShapeOf(parsed) === jsonShapeOf(defaultValue);
|
|
823
|
+
};
|
|
824
|
+
const jsonShapeOf = (value) => {
|
|
825
|
+
if (Array.isArray(value)) return "array";
|
|
826
|
+
const nativeType = typeof value;
|
|
827
|
+
return nativeType === "bigint" ? "number" : nativeType;
|
|
825
828
|
};
|
|
826
829
|
//#endregion
|
|
827
830
|
//#region ../shared/src/logger.ts
|
|
@@ -1265,11 +1268,12 @@ const utf8ByteLength = (text) => {
|
|
|
1265
1268
|
};
|
|
1266
1269
|
//#endregion
|
|
1267
1270
|
//#region ../js-client-core/src/cached-values.ts
|
|
1268
|
-
const ENTRY_FORMAT_VERSION =
|
|
1271
|
+
const ENTRY_FORMAT_VERSION = 2;
|
|
1272
|
+
const FIRST_ENTRY_FORMAT_VERSION = 1;
|
|
1269
1273
|
const DEFAULT_CONTEXT_KIND = "user";
|
|
1270
1274
|
const CACHED_VALUES_STORAGE_KEY_PREFIX = "configdirector-sdk:cached-values:";
|
|
1271
1275
|
const DEFAULT_CACHING_SIZE_LIMIT_BYTES = 256 * 1024;
|
|
1272
|
-
const planCaching = (connectionPlan, caching,
|
|
1276
|
+
const planCaching = (connectionPlan, caching, sha256HexDigest, platformCachingProvider) => {
|
|
1273
1277
|
if (connectionPlan.kind === "stay-offline") return {
|
|
1274
1278
|
kind: "no cache",
|
|
1275
1279
|
reason: "offline mode"
|
|
@@ -1278,7 +1282,12 @@ const planCaching = (connectionPlan, caching, platformProvider) => {
|
|
|
1278
1282
|
kind: "no cache",
|
|
1279
1283
|
reason: "turned off"
|
|
1280
1284
|
};
|
|
1281
|
-
|
|
1285
|
+
if (!sha256HexDigest) return {
|
|
1286
|
+
kind: "no cache",
|
|
1287
|
+
reason: "no SHA-256 available"
|
|
1288
|
+
};
|
|
1289
|
+
const clientSdkKeyDigest = sha256HexDigest(connectionPlan.clientSdkKey);
|
|
1290
|
+
const provider = caching?.provider ?? platformCachingProvider?.(clientSdkKeyDigest);
|
|
1282
1291
|
if (!provider) return {
|
|
1283
1292
|
kind: "no cache",
|
|
1284
1293
|
reason: "no provider available"
|
|
@@ -1286,7 +1295,7 @@ const planCaching = (connectionPlan, caching, platformProvider) => {
|
|
|
1286
1295
|
return {
|
|
1287
1296
|
kind: "cache",
|
|
1288
1297
|
provider,
|
|
1289
|
-
|
|
1298
|
+
clientSdkKeyDigest
|
|
1290
1299
|
};
|
|
1291
1300
|
};
|
|
1292
1301
|
const contextTagFor = (context, instanceId) => {
|
|
@@ -1321,19 +1330,34 @@ const isConfigStateMap = (candidate) => {
|
|
|
1321
1330
|
if (!candidate || typeof candidate !== "object" || Array.isArray(candidate)) return false;
|
|
1322
1331
|
return Object.values(candidate).every((config) => config && typeof config === "object" && typeof config.id === "string" && typeof config.key === "string");
|
|
1323
1332
|
};
|
|
1324
|
-
const isEntryFor = (
|
|
1333
|
+
const isEntryFor = (clientSdkKeyDigest, candidate) => {
|
|
1325
1334
|
if (!candidate || typeof candidate !== "object") return false;
|
|
1326
1335
|
const entry = candidate;
|
|
1327
|
-
return entry.formatVersion === ENTRY_FORMAT_VERSION && entry.
|
|
1336
|
+
return entry.formatVersion === ENTRY_FORMAT_VERSION && entry.clientSdkKeyDigest === clientSdkKeyDigest && isContextTag(entry.contextTag) && typeof entry.environmentId === "string" && typeof entry.projectId === "string" && isConfigStateMap(entry.configs) && typeof entry.receivedAt === "number";
|
|
1337
|
+
};
|
|
1338
|
+
const hasFirstFormatVersion = (candidate) => !!candidate && typeof candidate === "object" && candidate.formatVersion === FIRST_ENTRY_FORMAT_VERSION;
|
|
1339
|
+
const parseEntry = (raw, clientSdkKeyDigest) => {
|
|
1340
|
+
let candidate;
|
|
1341
|
+
try {
|
|
1342
|
+
candidate = JSON.parse(raw);
|
|
1343
|
+
} catch {
|
|
1344
|
+
return { outcome: "unreadable" };
|
|
1345
|
+
}
|
|
1346
|
+
if (isEntryFor(clientSdkKeyDigest, candidate)) return {
|
|
1347
|
+
outcome: "stored",
|
|
1348
|
+
cachedValues: candidate
|
|
1349
|
+
};
|
|
1350
|
+
if (hasFirstFormatVersion(candidate)) return { outcome: "first format" };
|
|
1351
|
+
return { outcome: "unreadable" };
|
|
1328
1352
|
};
|
|
1329
1353
|
var CachedValuesStore = class {
|
|
1330
1354
|
warned = /* @__PURE__ */ new Set();
|
|
1331
1355
|
pendingRead;
|
|
1332
1356
|
queuedCachedValues;
|
|
1333
1357
|
writing = false;
|
|
1334
|
-
constructor(provider,
|
|
1358
|
+
constructor(provider, clientSdkKeyDigest, logger) {
|
|
1335
1359
|
this.provider = provider;
|
|
1336
|
-
this.
|
|
1360
|
+
this.clientSdkKeyDigest = clientSdkKeyDigest;
|
|
1337
1361
|
this.logger = logger;
|
|
1338
1362
|
}
|
|
1339
1363
|
read() {
|
|
@@ -1353,23 +1377,16 @@ var CachedValuesStore = class {
|
|
|
1353
1377
|
return { outcome: "failed" };
|
|
1354
1378
|
}
|
|
1355
1379
|
if (raw == null || raw === "") return { outcome: "nothing stored" };
|
|
1356
|
-
const
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
}
|
|
1367
|
-
parseEntry(raw) {
|
|
1368
|
-
try {
|
|
1369
|
-
const candidate = JSON.parse(raw);
|
|
1370
|
-
return isEntryFor(this.clientSdkKey, candidate) ? candidate : void 0;
|
|
1371
|
-
} catch {
|
|
1372
|
-
return;
|
|
1380
|
+
const entryParseOutcome = parseEntry(raw, await this.clientSdkKeyDigest);
|
|
1381
|
+
switch (entryParseOutcome.outcome) {
|
|
1382
|
+
case "stored": return entryParseOutcome;
|
|
1383
|
+
case "first format":
|
|
1384
|
+
await this.removeEntry();
|
|
1385
|
+
return entryParseOutcome;
|
|
1386
|
+
case "unreadable":
|
|
1387
|
+
this.warnOnce("unreadable", "The values kept on the device could not be read and were removed, starting without them");
|
|
1388
|
+
await this.removeEntry();
|
|
1389
|
+
return entryParseOutcome;
|
|
1373
1390
|
}
|
|
1374
1391
|
}
|
|
1375
1392
|
async writeQueued() {
|
|
@@ -1388,7 +1405,7 @@ var CachedValuesStore = class {
|
|
|
1388
1405
|
async writeEntry(cachedValues) {
|
|
1389
1406
|
const entry = {
|
|
1390
1407
|
formatVersion: ENTRY_FORMAT_VERSION,
|
|
1391
|
-
|
|
1408
|
+
clientSdkKeyDigest: await this.clientSdkKeyDigest,
|
|
1392
1409
|
...cachedValues
|
|
1393
1410
|
};
|
|
1394
1411
|
const serialized = JSON.stringify(entry);
|
|
@@ -1452,7 +1469,7 @@ var DefaultConfigDirectorClient = class DefaultConfigDirectorClient {
|
|
|
1452
1469
|
this.timeout = clientOptions?.connection?.timeout ?? 3e3;
|
|
1453
1470
|
const urlFactory = internalClientOptions?.urlFactory ?? defaultUrlFactory;
|
|
1454
1471
|
this.connectionPlan = planConnection(clientSdkKey, clientOptions?.connection, urlFactory);
|
|
1455
|
-
this.cachedValuesStore = this.buildCachedValuesStore(planCaching(this.connectionPlan, clientOptions?.caching, internalClientOptions?.platformCachingProvider));
|
|
1472
|
+
this.cachedValuesStore = this.buildCachedValuesStore(planCaching(this.connectionPlan, clientOptions?.caching, internalClientOptions?.sha256HexDigest, internalClientOptions?.platformCachingProvider));
|
|
1456
1473
|
this.telemetryClient = this.buildTelemetryClient(liveTelemetryClient, clientOptions);
|
|
1457
1474
|
this.transport = internalClientOptions?.transport ?? this.buildTransport(sdkOptions, clientOptions, urlFactory, internalClientOptions);
|
|
1458
1475
|
this.transport.on("configSetReceived", (configSet) => {
|
|
@@ -1495,7 +1512,7 @@ var DefaultConfigDirectorClient = class DefaultConfigDirectorClient {
|
|
|
1495
1512
|
this.logger.debug(`[ConfigDirectorClient] No values are kept on the device: ${cachingPlan.reason}`);
|
|
1496
1513
|
return;
|
|
1497
1514
|
case "cache": {
|
|
1498
|
-
const store = new CachedValuesStore(cachingPlan.provider, cachingPlan.
|
|
1515
|
+
const store = new CachedValuesStore(cachingPlan.provider, cachingPlan.clientSdkKeyDigest, this.logger);
|
|
1499
1516
|
store.read();
|
|
1500
1517
|
return store;
|
|
1501
1518
|
}
|
|
@@ -1511,6 +1528,7 @@ var DefaultConfigDirectorClient = class DefaultConfigDirectorClient {
|
|
|
1511
1528
|
const read = await store.read();
|
|
1512
1529
|
switch (read.outcome) {
|
|
1513
1530
|
case "nothing stored":
|
|
1531
|
+
case "first format":
|
|
1514
1532
|
case "unreadable":
|
|
1515
1533
|
case "failed": return;
|
|
1516
1534
|
case "stored": break;
|
|
@@ -1584,7 +1602,8 @@ var DefaultConfigDirectorClient = class DefaultConfigDirectorClient {
|
|
|
1584
1602
|
clientSdkKey: plan.clientSdkKey,
|
|
1585
1603
|
baseUrl: plan.baseUrl,
|
|
1586
1604
|
logger: this.logger,
|
|
1587
|
-
metadata: clientOptions?.metadata
|
|
1605
|
+
metadata: clientOptions?.metadata,
|
|
1606
|
+
clientSession: this.clientSession
|
|
1588
1607
|
});
|
|
1589
1608
|
}
|
|
1590
1609
|
}
|
|
@@ -1704,7 +1723,7 @@ var DefaultConfigDirectorClient = class DefaultConfigDirectorClient {
|
|
|
1704
1723
|
updateWatchersForConfig(configKey) {
|
|
1705
1724
|
const configState = this.configSet?.configs[configKey];
|
|
1706
1725
|
this.handlersMap.get(configKey)?.forEach((h) => {
|
|
1707
|
-
const value = this.
|
|
1726
|
+
const value = this.evaluationOf(configKey, configState, h.defaultValue).value;
|
|
1708
1727
|
h.handler(value);
|
|
1709
1728
|
});
|
|
1710
1729
|
}
|
|
@@ -1729,11 +1748,14 @@ var DefaultConfigDirectorClient = class DefaultConfigDirectorClient {
|
|
|
1729
1748
|
} else handlers.splice(0);
|
|
1730
1749
|
}
|
|
1731
1750
|
getValue(configKey, defaultValue) {
|
|
1751
|
+
return this.evaluate(configKey, defaultValue).value;
|
|
1752
|
+
}
|
|
1753
|
+
evaluate(configKey, defaultValue) {
|
|
1732
1754
|
this.validateDefaultValue(defaultValue);
|
|
1733
1755
|
const configState = this.configSet?.configs[configKey];
|
|
1734
|
-
return this.
|
|
1756
|
+
return this.evaluationOf(configKey, configState, defaultValue);
|
|
1735
1757
|
}
|
|
1736
|
-
|
|
1758
|
+
evaluationOf(configKey, configState, defaultValue) {
|
|
1737
1759
|
if (!configState) {
|
|
1738
1760
|
this.logger.debug(`[ConfigDirectorClient] No config state found for '${configKey}', returning default value '${defaultValue}'`);
|
|
1739
1761
|
const reason = this.isReady ? "config-state-missing" : "client-not-ready";
|
|
@@ -1746,14 +1768,15 @@ var DefaultConfigDirectorClient = class DefaultConfigDirectorClient {
|
|
|
1746
1768
|
usedDefault: true,
|
|
1747
1769
|
evaluationReason: reason
|
|
1748
1770
|
});
|
|
1749
|
-
|
|
1771
|
+
const evaluation = {
|
|
1750
1772
|
key: configKey,
|
|
1751
1773
|
value: defaultValue,
|
|
1752
1774
|
isDefaultValue: true,
|
|
1753
1775
|
reason,
|
|
1754
1776
|
context: this.context
|
|
1755
|
-
}
|
|
1756
|
-
|
|
1777
|
+
};
|
|
1778
|
+
this.dispatchEvaluationEvent(evaluation);
|
|
1779
|
+
return evaluation;
|
|
1757
1780
|
}
|
|
1758
1781
|
const parseResult = parseConfigValue(configState, defaultValue);
|
|
1759
1782
|
const reason = this.servedReason(parseResult.reason);
|
|
@@ -1768,16 +1791,17 @@ var DefaultConfigDirectorClient = class DefaultConfigDirectorClient {
|
|
|
1768
1791
|
usedDefault: parseResult.usedDefault,
|
|
1769
1792
|
evaluationReason: reason
|
|
1770
1793
|
});
|
|
1771
|
-
|
|
1794
|
+
const evaluation = {
|
|
1772
1795
|
key: configKey,
|
|
1773
1796
|
value: parseResult.parsedValue,
|
|
1774
1797
|
valueId: parseResult.parsedValueId ?? void 0,
|
|
1775
1798
|
isDefaultValue: parseResult.usedDefault,
|
|
1776
1799
|
reason,
|
|
1777
1800
|
context: this.context
|
|
1778
|
-
}
|
|
1801
|
+
};
|
|
1802
|
+
this.dispatchEvaluationEvent(evaluation);
|
|
1779
1803
|
this.logger.debug(`[ConfigDirectorClient] Evaluated '${configKey}' to '${parseResult.parsedValue}'`);
|
|
1780
|
-
return
|
|
1804
|
+
return evaluation;
|
|
1781
1805
|
}
|
|
1782
1806
|
dispatchEvaluationEvent(event) {
|
|
1783
1807
|
setTimeout(() => this.emit("configEvaluated", { evaluation: event }), 0);
|
|
@@ -1931,6 +1955,7 @@ const DEFAULT_WORKER_CLOSE_TIMEOUT = 6e3;
|
|
|
1931
1955
|
var WebWorkerTelemetryClient = class {
|
|
1932
1956
|
logger;
|
|
1933
1957
|
initializeMessage;
|
|
1958
|
+
clientSession;
|
|
1934
1959
|
worker;
|
|
1935
1960
|
turnedOff = false;
|
|
1936
1961
|
pendingEvaluations = [];
|
|
@@ -1945,6 +1970,7 @@ var WebWorkerTelemetryClient = class {
|
|
|
1945
1970
|
this.logger = options.logger;
|
|
1946
1971
|
this.workerCloseTimeout = options.workerCloseTimeout ?? DEFAULT_WORKER_CLOSE_TIMEOUT;
|
|
1947
1972
|
this.pendingEvaluationLimit = options.evaluationQueueLimit ?? 1e3;
|
|
1973
|
+
this.clientSession = options.clientSession;
|
|
1948
1974
|
this.initializeMessage = {
|
|
1949
1975
|
type: "Initialize",
|
|
1950
1976
|
payload: {
|
|
@@ -1967,7 +1993,7 @@ var WebWorkerTelemetryClient = class {
|
|
|
1967
1993
|
return;
|
|
1968
1994
|
}
|
|
1969
1995
|
try {
|
|
1970
|
-
return new Worker(URL.createObjectURL(new Blob(["(function(){let e=async(e,t)=>e.valueId?{valueId:e.valueId}:e.value?e.value.length<=500&&e.type!=`json`?{value:e.value}:{valueId:await t(e.value)}:e,t=e=>{let t=Object.keys(e);if(t.length==0)return!0;for(let n of t)if((e[n]?.length??0)>0)return!1;return!0},n=e=>{if(!e)return!0;let t=Object.keys(e);if(t.length==0)return!0;for(let n of t)if((e[n]??0)>0)return!1;return!0},r=async(e,t,n,r)=>{let i=new AbortController,a=setTimeout(()=>{r.debug(`[fetchWithTimeout] Reached timeout, aborting request`),i.abort()},e);try{let e=await fetch(typeof t==`string`||t instanceof Request?t:t.toString(),{...n,signal:i.signal});return clearTimeout(a),e}catch(e){throw r.warn(`[fetchWithTimeout] Fetch error: `,e),clearTimeout(a),e}},i=e=>e?.name===`NotAllowedError`?!0:e instanceof TypeError;var a=class{constructor(e){this.executeRequests=!0,this.sdkKey=e.sdkKey,this.logger=e.logger,this.sdkIdentity=e.sdkIdentity,this.metaContext=e.metaContext,this.url=e.urlFactory(`client/telemetry/v1`,e.baseUrl)}async report({context:e,discreteEvents:t,aggregatedEvents:n,droppedEvents:r}){if(!this.executeRequests)return{success:!1,fatalError:!0};let
|
|
1996
|
+
return new Worker(URL.createObjectURL(new Blob(["(function(){let e=async(e,t)=>e.valueId?{valueId:e.valueId}:e.value?e.value.length<=500&&e.type!=`json`?{value:e.value}:{valueId:await t(e.value)}:e,t=e=>{let t=Object.keys(e);if(t.length==0)return!0;for(let n of t)if((e[n]?.length??0)>0)return!1;return!0},n=e=>{if(!e)return!0;let t=Object.keys(e);if(t.length==0)return!0;for(let n of t)if((e[n]??0)>0)return!1;return!0},r=async(e,t,n,r)=>{let i=new AbortController,a=setTimeout(()=>{r.debug(`[fetchWithTimeout] Reached timeout, aborting request`),i.abort()},e);try{let e=await fetch(typeof t==`string`||t instanceof Request?t:t.toString(),{...n,signal:i.signal});return clearTimeout(a),e}catch(e){throw r.warn(`[fetchWithTimeout] Fetch error: `,e),clearTimeout(a),e}},i=e=>e?.name===`NotAllowedError`?!0:e instanceof TypeError;var a=class{constructor(e){this.executeRequests=!0,this.sdkKey=e.sdkKey,this.logger=e.logger,this.sdkIdentity=e.sdkIdentity,this.metaContext=e.metaContext,this.url=e.urlFactory(`client/telemetry/v1`,e.baseUrl),this.clientSession=e.clientSession}async report({context:e,discreteEvents:t,aggregatedEvents:n,droppedEvents:r}){if(!this.executeRequests)return{success:!1,fatalError:!0};let{instanceId:i}=await this.clientSession,a={clientSdkKey:this.sdkKey,instanceId:i,metaContext:{sdkName:this.sdkIdentity.sdkName,sdkVersion:this.sdkIdentity.sdkVersion,appName:this.metaContext.appName,appVersion:this.metaContext.appVersion},context:e,discreteEvents:t,aggregatedEvents:n,droppedEvents:r};if(this.isReportEmpty(a))return{success:!0,fatalError:!1};let o=await this.sendReport(a);return o.fatalError&&(this.executeRequests=!1),o}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.started=!1,this.logger=e.logger,this.evaluationEventQueue=new s(e.evaluationQueueLimit??1e3),this.flushIntervalDelay=e.flushIntervalDelay??3e4,this.initialFlushIntervalDelay=e.initialFlushIntervalDelay??5e3}start(){this.started||!this.collectEvents||(this.started=!0,this.flushTimeout=setTimeout(()=>this.flushAndScheduleNext(),this.initialFlushIntervalDelay))}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{constructor(){this.countsByKindSample=new Map,this.countBeyondSampleLimit=0}record(e){return this.startTime??=new Date,this.add(e,1)}absorb(e){for(let t of e)(this.startTime===void 0||t.startTime<this.startTime)&&(this.startTime=t.startTime),this.add(t.event.kindSample,t.count)}add(e,t){if(e!==void 0){let n=this.countsByKindSample.get(e);if(n!==void 0)return this.countsByKindSample.set(e,n+t),`known-kind-sample`;if(this.countsByKindSample.size<20)return this.countsByKindSample.set(e,t),`new-kind-sample`}let n=this.countBeyondSampleLimit===0;return this.countBeyondSampleLimit+=t,n?`first-beyond-sample-limit`:`beyond-sample-limit`}takeSnapshot(){let e=new Date,t=this.startTime??e,n=(n,r)=>({startTime:t,endTime:e,count:n,event:{reason:`invalid-context-kind`,kindSample:r}}),r=Array.from(this.countsByKindSample,([e,t])=>n(t,e)),i=this.countBeyondSampleLimit>0?[...r,n(this.countBeyondSampleLimit)]:r;return this.clear(),i}clear(){this.countsByKindSample.clear(),this.countBeyondSampleLimit=0,this.startTime=void 0}},d=class extends c{constructor(e){super(e),this.invalidContextSubmissions=new u,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)}recordInvalidContext(e){this.invalidContextSubmissions.record(e)}absorbInvalidContextSubmissions(e){this.invalidContextSubmissions.absorb(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),invalidContext:this.invalidContextSubmissions.takeSnapshot()},droppedEvents:{evaluatedConfig:e.droppedCount}})}};let f=(e,t)=>new URL(e,t?.toString()),p=(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`)},m=Math.ceil(128/Math.log2(62)),h=new TextEncoder,g=async e=>{if(typeof crypto>`u`||crypto.subtle==null)return;let t=await crypto.subtle.digest(`SHA-256`,h.encode(e?.toString()??``));return p(new Uint8Array(t,0,16),m)},_,v,y=new Promise(e=>{v=e}),b=(e,t,...n)=>({type:`Log`,payload:{level:e,message:t,args:n}}),x=e=>{try{self.postMessage(e)}catch{self.postMessage(b(e.payload.level,e.payload.message))}},S={debug:function(e,...t){x(b(`debug`,e,t))},info:function(e,...t){x(b(`info`,e,t))},warn:function(e,...t){x(b(`warn`,e,t))},error:function(e,...t){x(b(`error`,e,t))}};addEventListener(`message`,e=>{let t=e.data;if(t.type!=`Initialize`&&!_){S.info(`[TelemetryWorker] Ignoring ${t.type} because no collector is configured`);return}switch(S.debug(`[TelemetryWorker] Received '${t.type}' event:`,t),t.type){case`Initialize`:if(_){S.warn(`[TelemetryWorker] Received a duplicate Initialize event, ignoring it`);break}_=new d({...t.payload,baseUrl:f(t.payload.baseUrl),urlFactory:f,valueIdGenerator:g,logger:S,clientSession:y}),_.start();break;case`ClientSession`:v(t.payload);break;case`EvaluatedConfigEvent`:_?.evaluatedConfig(t.payload);break;case`InvalidContextSubmissions`:_?.absorbInvalidContextSubmissions(t.payload.entries);break;case`UpdateContext`:_?.updateContext(t.payload.context);break;case`Flush`:_?.forceFlush();break;case`Close`:(_?.close()??Promise.resolve()).then(()=>{self.postMessage({type:`Closed`}),self.close()});break}})})();"], { type: "application/javascript" })));
|
|
1971
1997
|
} catch (error) {
|
|
1972
1998
|
this.turnOff("[WebWorkerTelemetryClient] Telemetry is off because the telemetry Web Worker could not be created: ", error);
|
|
1973
1999
|
return;
|
|
@@ -1986,6 +2012,7 @@ var WebWorkerTelemetryClient = class {
|
|
|
1986
2012
|
this.handleWorkerEvent(message.data);
|
|
1987
2013
|
};
|
|
1988
2014
|
worker.postMessage(this.initializeMessage);
|
|
2015
|
+
this.forwardClientSession(worker);
|
|
1989
2016
|
this.worker = worker;
|
|
1990
2017
|
try {
|
|
1991
2018
|
document.addEventListener("visibilitychange", this.visibilityHandler);
|
|
@@ -1997,6 +2024,15 @@ var WebWorkerTelemetryClient = class {
|
|
|
1997
2024
|
this.forwardInvalidContextSubmissions(worker);
|
|
1998
2025
|
return worker;
|
|
1999
2026
|
}
|
|
2027
|
+
forwardClientSession(worker) {
|
|
2028
|
+
this.clientSession.then((clientSession) => {
|
|
2029
|
+
const clientSessionMessage = {
|
|
2030
|
+
type: "ClientSession",
|
|
2031
|
+
payload: clientSession
|
|
2032
|
+
};
|
|
2033
|
+
worker.postMessage(clientSessionMessage);
|
|
2034
|
+
});
|
|
2035
|
+
}
|
|
2000
2036
|
forwardInvalidContextSubmissions(worker) {
|
|
2001
2037
|
if (!worker) return;
|
|
2002
2038
|
const entries = this.pendingInvalidContextSubmissions.takeSnapshot();
|
|
@@ -2130,16 +2166,10 @@ const generateValueId = async (v) => {
|
|
|
2130
2166
|
};
|
|
2131
2167
|
//#endregion
|
|
2132
2168
|
//#region ../js-browser-client/src/local-storage-caching-provider.ts
|
|
2133
|
-
const
|
|
2134
|
-
const storageKeyFor = async (clientSdkKey) => {
|
|
2135
|
-
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(clientSdkKey));
|
|
2136
|
-
return CACHED_VALUES_STORAGE_KEY_PREFIX + toHex(new Uint8Array(digest));
|
|
2137
|
-
};
|
|
2138
|
-
const subtleCryptoAvailable = () => typeof crypto !== "undefined" && crypto.subtle != null;
|
|
2139
|
-
const localStorageCachingProvider = (clientSdkKey) => {
|
|
2169
|
+
const localStorageCachingProvider = (clientSdkKeyDigest) => {
|
|
2140
2170
|
const storage = localStorageIfAvailable();
|
|
2141
|
-
if (!storage
|
|
2142
|
-
const storageKey =
|
|
2171
|
+
if (!storage) return;
|
|
2172
|
+
const storageKey = clientSdkKeyDigest.then((digest) => CACHED_VALUES_STORAGE_KEY_PREFIX + digest);
|
|
2143
2173
|
return {
|
|
2144
2174
|
read: async () => storage.getItem(await storageKey),
|
|
2145
2175
|
write: async (value) => storage.setItem(await storageKey, value),
|
|
@@ -2148,6 +2178,16 @@ const localStorageCachingProvider = (clientSdkKey) => {
|
|
|
2148
2178
|
};
|
|
2149
2179
|
};
|
|
2150
2180
|
//#endregion
|
|
2181
|
+
//#region ../js-browser-client/src/sha256-hex-digest.ts
|
|
2182
|
+
const toHex = (bytes) => Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
2183
|
+
const sha256HexDigestIfAvailable = () => {
|
|
2184
|
+
if (typeof crypto === "undefined" || crypto.subtle == null) return;
|
|
2185
|
+
return async (text) => {
|
|
2186
|
+
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(text));
|
|
2187
|
+
return toHex(new Uint8Array(digest));
|
|
2188
|
+
};
|
|
2189
|
+
};
|
|
2190
|
+
//#endregion
|
|
2151
2191
|
//#region ../js-browser-client/src/BrowserConfigDirectorClient.ts
|
|
2152
2192
|
/**
|
|
2153
2193
|
* Creates a browser client. With a client SDK key it connects to ConfigDirector; with
|
|
@@ -2156,7 +2196,7 @@ const localStorageCachingProvider = (clientSdkKey) => {
|
|
|
2156
2196
|
* and fails like a missing key otherwise.
|
|
2157
2197
|
*/
|
|
2158
2198
|
const createBrowserClient = (clientSdkKeyOrOptions, sdkOptions, clientOptions) => {
|
|
2159
|
-
return DefaultConfigDirectorClient.from(({ clientSdkKey, baseUrl, logger, metadata }) => new WebWorkerTelemetryClient({
|
|
2199
|
+
return DefaultConfigDirectorClient.from(({ clientSdkKey, baseUrl, logger, metadata, clientSession }) => new WebWorkerTelemetryClient({
|
|
2160
2200
|
sdkKey: clientSdkKey,
|
|
2161
2201
|
sdkIdentity: {
|
|
2162
2202
|
sdkName: sdkOptions.sdkName,
|
|
@@ -2169,8 +2209,12 @@ const createBrowserClient = (clientSdkKeyOrOptions, sdkOptions, clientOptions) =
|
|
|
2169
2209
|
logger,
|
|
2170
2210
|
baseUrl,
|
|
2171
2211
|
urlFactory: defaultUrlFactory,
|
|
2172
|
-
valueIdGenerator: generateValueId
|
|
2173
|
-
|
|
2212
|
+
valueIdGenerator: generateValueId,
|
|
2213
|
+
clientSession
|
|
2214
|
+
}), clientSdkKeyOrOptions, sdkOptions, clientOptions, {
|
|
2215
|
+
sha256HexDigest: sha256HexDigestIfAvailable(),
|
|
2216
|
+
platformCachingProvider: localStorageCachingProvider
|
|
2217
|
+
});
|
|
2174
2218
|
};
|
|
2175
2219
|
//#endregion
|
|
2176
2220
|
//#region ../js-client-core/src/installedTestClient.ts
|
|
@@ -2337,7 +2381,7 @@ const buildClient = async (configdirector, logger) => {
|
|
|
2337
2381
|
return {
|
|
2338
2382
|
client: createBrowserClient(configdirector?.clientSdkKey || void 0, {
|
|
2339
2383
|
sdkName: "nuxt-sdk",
|
|
2340
|
-
sdkVersion: "1.
|
|
2384
|
+
sdkVersion: "1.13.0"
|
|
2341
2385
|
}, {
|
|
2342
2386
|
metadata: {
|
|
2343
2387
|
appName: configdirector?.appName,
|
|
@@ -176,6 +176,9 @@ var SsrClient = class {
|
|
|
176
176
|
getValue(configKey, defaultValue) {
|
|
177
177
|
return this.serverClient.getValue(configKey, defaultValue, this.suppliedContext);
|
|
178
178
|
}
|
|
179
|
+
evaluate(configKey, defaultValue) {
|
|
180
|
+
return this.serverClient.evaluate(configKey, defaultValue, this.suppliedContext);
|
|
181
|
+
}
|
|
179
182
|
watch() {
|
|
180
183
|
return () => {};
|
|
181
184
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as ConfigDirectorClient } from "../../types-
|
|
1
|
+
import { n as ConfigDirectorClient } from "../../types-qDEc6g8R.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/runtime/server/composables/useConfigDirectorServerClient.d.ts
|
|
4
4
|
declare const useConfigDirectorServerClient: () => ConfigDirectorClient;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as ConfigDirectorClient } from "../../types-
|
|
1
|
+
import { n as ConfigDirectorClient } from "../../types-qDEc6g8R.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/runtime/server/composables/useConfigDirectorServerClient.d.ts
|
|
4
4
|
declare const useConfigDirectorServerClient: () => ConfigDirectorClient;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as ClientHooks } from "../../types-
|
|
1
|
+
import { t as ClientHooks } from "../../types-qDEc6g8R.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/runtime/server/composables/useConfigDirectorServerHooks.d.ts
|
|
4
4
|
declare const useConfigDirectorServerHooks: (hooks: ClientHooks) => void;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as ClientHooks } from "../../types-
|
|
1
|
+
import { t as ClientHooks } from "../../types-qDEc6g8R.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/runtime/server/composables/useConfigDirectorServerHooks.d.ts
|
|
4
4
|
declare const useConfigDirectorServerHooks: (hooks: ClientHooks) => void;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as ConfigValueType, n as ConfigDirectorContext, r as ConfigEvaluation } from "./native-values-
|
|
1
|
+
import { a as ConfigValueType, n as ConfigDirectorContext, r as ConfigEvaluation } from "./native-values-CfhP0yLS.mjs";
|
|
2
2
|
|
|
3
3
|
//#region ../js-client-core/src/Emitter.d.ts
|
|
4
4
|
type EventType = string | symbol;
|
|
@@ -113,6 +113,16 @@ interface ConfigDirectorClient extends EventProvider<ClientEvents> {
|
|
|
113
113
|
* is called before initialization is done.
|
|
114
114
|
*/
|
|
115
115
|
getValue<T extends ConfigValueType>(configKey: string, defaultValue: T): T;
|
|
116
|
+
/**
|
|
117
|
+
* Evaluates a config as {@link getValue} does and returns the evaluation: the value, the reason it
|
|
118
|
+
* was served and its value ID. Like every read, it fires `configEvaluated` and is counted in
|
|
119
|
+
* telemetry.
|
|
120
|
+
*
|
|
121
|
+
* @returns The evaluation, whose `value` is what {@link getValue} returns
|
|
122
|
+
* @param configKey The config key to evaluate
|
|
123
|
+
* @param defaultValue The in-code default value, returned when no value from ConfigDirector can be served
|
|
124
|
+
*/
|
|
125
|
+
evaluate<T extends ConfigValueType>(configKey: string, defaultValue: T): ConfigEvaluation<T>;
|
|
116
126
|
/**
|
|
117
127
|
* Watches for changes to a a config evaluation value. Whenever the config value changes, the
|
|
118
128
|
* provided callback function will be called with the new value. Changes can happen due to updates
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as ConfigValueType, i as ConfigState, n as ConfigDirectorContext, r as ConfigEvaluation } from "./native-values-
|
|
1
|
+
import { a as ConfigValueType, i as ConfigState, n as ConfigDirectorContext, r as ConfigEvaluation } from "./native-values-CfhP0yLS.mjs";
|
|
2
2
|
|
|
3
3
|
//#region ../js-server-sdk/src/types.d.ts
|
|
4
4
|
type ClientEvents = {
|
|
@@ -59,6 +59,17 @@ interface ConfigDirectorClient {
|
|
|
59
59
|
* @param context The user's context to be used for targeting rules evaluation (optional)
|
|
60
60
|
*/
|
|
61
61
|
getValue<T extends ConfigValueType>(configKey: string, defaultValue: T, context?: ConfigDirectorContext): T;
|
|
62
|
+
/**
|
|
63
|
+
* Evaluates a config as {@link getValue} does and returns the evaluation: the value, the reason it
|
|
64
|
+
* was served and its value ID. Like every read, it fires `configEvaluated` and is counted in
|
|
65
|
+
* telemetry.
|
|
66
|
+
*
|
|
67
|
+
* @returns The evaluation, whose `value` is what {@link getValue} returns
|
|
68
|
+
* @param configKey The config key to evaluate
|
|
69
|
+
* @param defaultValue The in-code default value, returned when no value from ConfigDirector can be served
|
|
70
|
+
* @param context The user's context to be used for targeting rules evaluation (optional)
|
|
71
|
+
*/
|
|
72
|
+
evaluate<T extends ConfigValueType>(configKey: string, defaultValue: T, context?: ConfigDirectorContext): ConfigEvaluation<T>;
|
|
62
73
|
/**
|
|
63
74
|
* Watches for changes to a a config evaluation value. Whenever the config value changes, the
|
|
64
75
|
* provided callback function will be called with the new value. Changes can happen due to updates
|
package/dist/testing.d.mts
CHANGED
|
@@ -19,13 +19,24 @@ interface ConfigDirectorLogger {
|
|
|
19
19
|
warn(message: string, ...args: any): void;
|
|
20
20
|
error(message: string, ...args: any): void;
|
|
21
21
|
}
|
|
22
|
+
/**
|
|
23
|
+
* Why an evaluation returned the value it did. `found-match` returns a value from ConfigDirector,
|
|
24
|
+
* and `cached` a value kept on the device while a client SDK waits for live values. `value-missing`
|
|
25
|
+
* returns the in-code default value because the config has no value. Every other reason returns the
|
|
26
|
+
* in-code default value because of an error: the config does not exist (`config-state-missing`), no
|
|
27
|
+
* values have arrived yet (`client-not-ready`), or the value cannot be read as the type of the in-code
|
|
28
|
+
* default value.
|
|
29
|
+
*/
|
|
22
30
|
type EvaluationReason = "found-match" | "config-state-missing" | "client-not-ready" | "type-mismatch" | "value-missing" | "invalid-number" | "invalid-json" | "invalid-boolean" | "cached";
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
31
|
+
/**
|
|
32
|
+
* One evaluation of a config: the value it returned, why, and the context it was evaluated for.
|
|
33
|
+
*/
|
|
34
|
+
type ConfigEvaluation<T extends ConfigValueType = ConfigValueType> = {
|
|
35
|
+
/** The config key that was evaluated. */key: string; /** The value returned, which is the in-code default value when {@link isDefaultValue} is `true`. */
|
|
36
|
+
value: T; /** The ID ConfigDirector assigned to the value returned, when ConfigDirector sent one with it. */
|
|
37
|
+
valueId?: string; /** Whether the evaluation returned the in-code default value. */
|
|
38
|
+
isDefaultValue: boolean; /** Why the evaluation returned this value. */
|
|
39
|
+
reason: EvaluationReason; /** The context the config was evaluated for, if any. */
|
|
29
40
|
context?: ConfigDirectorContext;
|
|
30
41
|
};
|
|
31
42
|
/**
|
|
@@ -182,6 +193,16 @@ interface ConfigDirectorClient extends EventProvider<ClientEvents> {
|
|
|
182
193
|
* is called before initialization is done.
|
|
183
194
|
*/
|
|
184
195
|
getValue<T extends ConfigValueType>(configKey: string, defaultValue: T): T;
|
|
196
|
+
/**
|
|
197
|
+
* Evaluates a config as {@link getValue} does and returns the evaluation: the value, the reason it
|
|
198
|
+
* was served and its value ID. Like every read, it fires `configEvaluated` and is counted in
|
|
199
|
+
* telemetry.
|
|
200
|
+
*
|
|
201
|
+
* @returns The evaluation, whose `value` is what {@link getValue} returns
|
|
202
|
+
* @param configKey The config key to evaluate
|
|
203
|
+
* @param defaultValue The in-code default value, returned when no value from ConfigDirector can be served
|
|
204
|
+
*/
|
|
205
|
+
evaluate<T extends ConfigValueType>(configKey: string, defaultValue: T): ConfigEvaluation<T>;
|
|
185
206
|
/**
|
|
186
207
|
* Watches for changes to a a config evaluation value. Whenever the config value changes, the
|
|
187
208
|
* provided callback function will be called with the new value. Changes can happen due to updates
|
package/dist/testing.mjs
CHANGED
|
@@ -816,9 +816,12 @@ const parseJson = (jsonString, defaultValue, requestedType, valueId) => {
|
|
|
816
816
|
};
|
|
817
817
|
const isJsonTypeCompatible = (parsed, defaultValue) => {
|
|
818
818
|
if (parsed === null) return false;
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
819
|
+
return jsonShapeOf(parsed) === jsonShapeOf(defaultValue);
|
|
820
|
+
};
|
|
821
|
+
const jsonShapeOf = (value) => {
|
|
822
|
+
if (Array.isArray(value)) return "array";
|
|
823
|
+
const nativeType = typeof value;
|
|
824
|
+
return nativeType === "bigint" ? "number" : nativeType;
|
|
822
825
|
};
|
|
823
826
|
//#endregion
|
|
824
827
|
//#region ../shared/src/logger.ts
|
|
@@ -1258,9 +1261,10 @@ const utf8ByteLength = (text) => {
|
|
|
1258
1261
|
};
|
|
1259
1262
|
//#endregion
|
|
1260
1263
|
//#region ../js-client-core/src/cached-values.ts
|
|
1261
|
-
const ENTRY_FORMAT_VERSION =
|
|
1264
|
+
const ENTRY_FORMAT_VERSION = 2;
|
|
1265
|
+
const FIRST_ENTRY_FORMAT_VERSION = 1;
|
|
1262
1266
|
const DEFAULT_CONTEXT_KIND = "user";
|
|
1263
|
-
const planCaching = (connectionPlan, caching,
|
|
1267
|
+
const planCaching = (connectionPlan, caching, sha256HexDigest, platformCachingProvider) => {
|
|
1264
1268
|
if (connectionPlan.kind === "stay-offline") return {
|
|
1265
1269
|
kind: "no cache",
|
|
1266
1270
|
reason: "offline mode"
|
|
@@ -1269,7 +1273,12 @@ const planCaching = (connectionPlan, caching, platformProvider) => {
|
|
|
1269
1273
|
kind: "no cache",
|
|
1270
1274
|
reason: "turned off"
|
|
1271
1275
|
};
|
|
1272
|
-
|
|
1276
|
+
if (!sha256HexDigest) return {
|
|
1277
|
+
kind: "no cache",
|
|
1278
|
+
reason: "no SHA-256 available"
|
|
1279
|
+
};
|
|
1280
|
+
const clientSdkKeyDigest = sha256HexDigest(connectionPlan.clientSdkKey);
|
|
1281
|
+
const provider = caching?.provider ?? platformCachingProvider?.(clientSdkKeyDigest);
|
|
1273
1282
|
if (!provider) return {
|
|
1274
1283
|
kind: "no cache",
|
|
1275
1284
|
reason: "no provider available"
|
|
@@ -1277,7 +1286,7 @@ const planCaching = (connectionPlan, caching, platformProvider) => {
|
|
|
1277
1286
|
return {
|
|
1278
1287
|
kind: "cache",
|
|
1279
1288
|
provider,
|
|
1280
|
-
|
|
1289
|
+
clientSdkKeyDigest
|
|
1281
1290
|
};
|
|
1282
1291
|
};
|
|
1283
1292
|
const contextTagFor = (context, instanceId) => {
|
|
@@ -1312,19 +1321,34 @@ const isConfigStateMap = (candidate) => {
|
|
|
1312
1321
|
if (!candidate || typeof candidate !== "object" || Array.isArray(candidate)) return false;
|
|
1313
1322
|
return Object.values(candidate).every((config) => config && typeof config === "object" && typeof config.id === "string" && typeof config.key === "string");
|
|
1314
1323
|
};
|
|
1315
|
-
const isEntryFor = (
|
|
1324
|
+
const isEntryFor = (clientSdkKeyDigest, candidate) => {
|
|
1316
1325
|
if (!candidate || typeof candidate !== "object") return false;
|
|
1317
1326
|
const entry = candidate;
|
|
1318
|
-
return entry.formatVersion === ENTRY_FORMAT_VERSION && entry.
|
|
1327
|
+
return entry.formatVersion === ENTRY_FORMAT_VERSION && entry.clientSdkKeyDigest === clientSdkKeyDigest && isContextTag(entry.contextTag) && typeof entry.environmentId === "string" && typeof entry.projectId === "string" && isConfigStateMap(entry.configs) && typeof entry.receivedAt === "number";
|
|
1328
|
+
};
|
|
1329
|
+
const hasFirstFormatVersion = (candidate) => !!candidate && typeof candidate === "object" && candidate.formatVersion === FIRST_ENTRY_FORMAT_VERSION;
|
|
1330
|
+
const parseEntry = (raw, clientSdkKeyDigest) => {
|
|
1331
|
+
let candidate;
|
|
1332
|
+
try {
|
|
1333
|
+
candidate = JSON.parse(raw);
|
|
1334
|
+
} catch {
|
|
1335
|
+
return { outcome: "unreadable" };
|
|
1336
|
+
}
|
|
1337
|
+
if (isEntryFor(clientSdkKeyDigest, candidate)) return {
|
|
1338
|
+
outcome: "stored",
|
|
1339
|
+
cachedValues: candidate
|
|
1340
|
+
};
|
|
1341
|
+
if (hasFirstFormatVersion(candidate)) return { outcome: "first format" };
|
|
1342
|
+
return { outcome: "unreadable" };
|
|
1319
1343
|
};
|
|
1320
1344
|
var CachedValuesStore = class {
|
|
1321
1345
|
warned = /* @__PURE__ */ new Set();
|
|
1322
1346
|
pendingRead;
|
|
1323
1347
|
queuedCachedValues;
|
|
1324
1348
|
writing = false;
|
|
1325
|
-
constructor(provider,
|
|
1349
|
+
constructor(provider, clientSdkKeyDigest, logger) {
|
|
1326
1350
|
this.provider = provider;
|
|
1327
|
-
this.
|
|
1351
|
+
this.clientSdkKeyDigest = clientSdkKeyDigest;
|
|
1328
1352
|
this.logger = logger;
|
|
1329
1353
|
}
|
|
1330
1354
|
read() {
|
|
@@ -1344,23 +1368,16 @@ var CachedValuesStore = class {
|
|
|
1344
1368
|
return { outcome: "failed" };
|
|
1345
1369
|
}
|
|
1346
1370
|
if (raw == null || raw === "") return { outcome: "nothing stored" };
|
|
1347
|
-
const
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
}
|
|
1358
|
-
parseEntry(raw) {
|
|
1359
|
-
try {
|
|
1360
|
-
const candidate = JSON.parse(raw);
|
|
1361
|
-
return isEntryFor(this.clientSdkKey, candidate) ? candidate : void 0;
|
|
1362
|
-
} catch {
|
|
1363
|
-
return;
|
|
1371
|
+
const entryParseOutcome = parseEntry(raw, await this.clientSdkKeyDigest);
|
|
1372
|
+
switch (entryParseOutcome.outcome) {
|
|
1373
|
+
case "stored": return entryParseOutcome;
|
|
1374
|
+
case "first format":
|
|
1375
|
+
await this.removeEntry();
|
|
1376
|
+
return entryParseOutcome;
|
|
1377
|
+
case "unreadable":
|
|
1378
|
+
this.warnOnce("unreadable", "The values kept on the device could not be read and were removed, starting without them");
|
|
1379
|
+
await this.removeEntry();
|
|
1380
|
+
return entryParseOutcome;
|
|
1364
1381
|
}
|
|
1365
1382
|
}
|
|
1366
1383
|
async writeQueued() {
|
|
@@ -1379,7 +1396,7 @@ var CachedValuesStore = class {
|
|
|
1379
1396
|
async writeEntry(cachedValues) {
|
|
1380
1397
|
const entry = {
|
|
1381
1398
|
formatVersion: ENTRY_FORMAT_VERSION,
|
|
1382
|
-
|
|
1399
|
+
clientSdkKeyDigest: await this.clientSdkKeyDigest,
|
|
1383
1400
|
...cachedValues
|
|
1384
1401
|
};
|
|
1385
1402
|
const serialized = JSON.stringify(entry);
|
|
@@ -1443,7 +1460,7 @@ var DefaultConfigDirectorClient = class DefaultConfigDirectorClient {
|
|
|
1443
1460
|
this.timeout = clientOptions?.connection?.timeout ?? 3e3;
|
|
1444
1461
|
const urlFactory = internalClientOptions?.urlFactory ?? defaultUrlFactory;
|
|
1445
1462
|
this.connectionPlan = planConnection(clientSdkKey, clientOptions?.connection, urlFactory);
|
|
1446
|
-
this.cachedValuesStore = this.buildCachedValuesStore(planCaching(this.connectionPlan, clientOptions?.caching, internalClientOptions?.platformCachingProvider));
|
|
1463
|
+
this.cachedValuesStore = this.buildCachedValuesStore(planCaching(this.connectionPlan, clientOptions?.caching, internalClientOptions?.sha256HexDigest, internalClientOptions?.platformCachingProvider));
|
|
1447
1464
|
this.telemetryClient = this.buildTelemetryClient(liveTelemetryClient, clientOptions);
|
|
1448
1465
|
this.transport = internalClientOptions?.transport ?? this.buildTransport(sdkOptions, clientOptions, urlFactory, internalClientOptions);
|
|
1449
1466
|
this.transport.on("configSetReceived", (configSet) => {
|
|
@@ -1486,7 +1503,7 @@ var DefaultConfigDirectorClient = class DefaultConfigDirectorClient {
|
|
|
1486
1503
|
this.logger.debug(`[ConfigDirectorClient] No values are kept on the device: ${cachingPlan.reason}`);
|
|
1487
1504
|
return;
|
|
1488
1505
|
case "cache": {
|
|
1489
|
-
const store = new CachedValuesStore(cachingPlan.provider, cachingPlan.
|
|
1506
|
+
const store = new CachedValuesStore(cachingPlan.provider, cachingPlan.clientSdkKeyDigest, this.logger);
|
|
1490
1507
|
store.read();
|
|
1491
1508
|
return store;
|
|
1492
1509
|
}
|
|
@@ -1502,6 +1519,7 @@ var DefaultConfigDirectorClient = class DefaultConfigDirectorClient {
|
|
|
1502
1519
|
const read = await store.read();
|
|
1503
1520
|
switch (read.outcome) {
|
|
1504
1521
|
case "nothing stored":
|
|
1522
|
+
case "first format":
|
|
1505
1523
|
case "unreadable":
|
|
1506
1524
|
case "failed": return;
|
|
1507
1525
|
case "stored": break;
|
|
@@ -1575,7 +1593,8 @@ var DefaultConfigDirectorClient = class DefaultConfigDirectorClient {
|
|
|
1575
1593
|
clientSdkKey: plan.clientSdkKey,
|
|
1576
1594
|
baseUrl: plan.baseUrl,
|
|
1577
1595
|
logger: this.logger,
|
|
1578
|
-
metadata: clientOptions?.metadata
|
|
1596
|
+
metadata: clientOptions?.metadata,
|
|
1597
|
+
clientSession: this.clientSession
|
|
1579
1598
|
});
|
|
1580
1599
|
}
|
|
1581
1600
|
}
|
|
@@ -1695,7 +1714,7 @@ var DefaultConfigDirectorClient = class DefaultConfigDirectorClient {
|
|
|
1695
1714
|
updateWatchersForConfig(configKey) {
|
|
1696
1715
|
const configState = this.configSet?.configs[configKey];
|
|
1697
1716
|
this.handlersMap.get(configKey)?.forEach((h) => {
|
|
1698
|
-
const value = this.
|
|
1717
|
+
const value = this.evaluationOf(configKey, configState, h.defaultValue).value;
|
|
1699
1718
|
h.handler(value);
|
|
1700
1719
|
});
|
|
1701
1720
|
}
|
|
@@ -1720,11 +1739,14 @@ var DefaultConfigDirectorClient = class DefaultConfigDirectorClient {
|
|
|
1720
1739
|
} else handlers.splice(0);
|
|
1721
1740
|
}
|
|
1722
1741
|
getValue(configKey, defaultValue) {
|
|
1742
|
+
return this.evaluate(configKey, defaultValue).value;
|
|
1743
|
+
}
|
|
1744
|
+
evaluate(configKey, defaultValue) {
|
|
1723
1745
|
this.validateDefaultValue(defaultValue);
|
|
1724
1746
|
const configState = this.configSet?.configs[configKey];
|
|
1725
|
-
return this.
|
|
1747
|
+
return this.evaluationOf(configKey, configState, defaultValue);
|
|
1726
1748
|
}
|
|
1727
|
-
|
|
1749
|
+
evaluationOf(configKey, configState, defaultValue) {
|
|
1728
1750
|
if (!configState) {
|
|
1729
1751
|
this.logger.debug(`[ConfigDirectorClient] No config state found for '${configKey}', returning default value '${defaultValue}'`);
|
|
1730
1752
|
const reason = this.isReady ? "config-state-missing" : "client-not-ready";
|
|
@@ -1737,14 +1759,15 @@ var DefaultConfigDirectorClient = class DefaultConfigDirectorClient {
|
|
|
1737
1759
|
usedDefault: true,
|
|
1738
1760
|
evaluationReason: reason
|
|
1739
1761
|
});
|
|
1740
|
-
|
|
1762
|
+
const evaluation = {
|
|
1741
1763
|
key: configKey,
|
|
1742
1764
|
value: defaultValue,
|
|
1743
1765
|
isDefaultValue: true,
|
|
1744
1766
|
reason,
|
|
1745
1767
|
context: this.context
|
|
1746
|
-
}
|
|
1747
|
-
|
|
1768
|
+
};
|
|
1769
|
+
this.dispatchEvaluationEvent(evaluation);
|
|
1770
|
+
return evaluation;
|
|
1748
1771
|
}
|
|
1749
1772
|
const parseResult = parseConfigValue(configState, defaultValue);
|
|
1750
1773
|
const reason = this.servedReason(parseResult.reason);
|
|
@@ -1759,16 +1782,17 @@ var DefaultConfigDirectorClient = class DefaultConfigDirectorClient {
|
|
|
1759
1782
|
usedDefault: parseResult.usedDefault,
|
|
1760
1783
|
evaluationReason: reason
|
|
1761
1784
|
});
|
|
1762
|
-
|
|
1785
|
+
const evaluation = {
|
|
1763
1786
|
key: configKey,
|
|
1764
1787
|
value: parseResult.parsedValue,
|
|
1765
1788
|
valueId: parseResult.parsedValueId ?? void 0,
|
|
1766
1789
|
isDefaultValue: parseResult.usedDefault,
|
|
1767
1790
|
reason,
|
|
1768
1791
|
context: this.context
|
|
1769
|
-
}
|
|
1792
|
+
};
|
|
1793
|
+
this.dispatchEvaluationEvent(evaluation);
|
|
1770
1794
|
this.logger.debug(`[ConfigDirectorClient] Evaluated '${configKey}' to '${parseResult.parsedValue}'`);
|
|
1771
|
-
return
|
|
1795
|
+
return evaluation;
|
|
1772
1796
|
}
|
|
1773
1797
|
dispatchEvaluationEvent(event) {
|
|
1774
1798
|
setTimeout(() => this.emit("configEvaluated", { evaluation: event }), 0);
|
|
@@ -2130,7 +2154,7 @@ const installTestClient = (testClient) => {
|
|
|
2130
2154
|
const createTestClient = (options) => {
|
|
2131
2155
|
return createTestClient$1({
|
|
2132
2156
|
sdkName: "nuxt-sdk",
|
|
2133
|
-
sdkVersion: "1.
|
|
2157
|
+
sdkVersion: "1.13.0"
|
|
2134
2158
|
}, options);
|
|
2135
2159
|
};
|
|
2136
2160
|
//#endregion
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@configdirector/nuxt-sdk",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.13.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",
|
|
@@ -73,7 +73,7 @@
|
|
|
73
73
|
"eslint": "^10.7.0",
|
|
74
74
|
"jsdom": "^30.0.1",
|
|
75
75
|
"nuxt": "^4.5.2",
|
|
76
|
-
"playwright": "^1.
|
|
76
|
+
"playwright": "^1.64.1",
|
|
77
77
|
"rimraf": "^6.1.3",
|
|
78
78
|
"tsdown": "^0.21.7",
|
|
79
79
|
"tslib": "^2.8.1",
|