@configdirector/nuxt-sdk 1.9.2 → 1.11.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 +76 -15
- package/dist/module.json +1 -1
- package/dist/module.mjs +13 -4
- 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/useConfigDirectorContext.mjs +2 -1
- package/dist/runtime/app/composables/useConfigDirectorStatus.d.mts +1 -1
- package/dist/runtime/app/composables/useConfigDirectorStatus.d.ts +1 -1
- package/dist/runtime/app/composables/useConfigDirectorValue.d.mts +2 -2
- package/dist/runtime/app/composables/useConfigDirectorValue.d.ts +2 -2
- package/dist/runtime/context-kind-D6YBNij2.mjs +45 -0
- package/dist/runtime/native-values-BV2k_xR2.d.mts +83 -0
- package/dist/runtime/native-values-DZJjFBvk.mjs +116 -0
- package/dist/runtime/nitro/middleware.mjs +5 -1
- package/dist/runtime/nitro/offline-values.d.mts +7 -0
- package/dist/runtime/nitro/offline-values.d.ts +7 -0
- package/dist/runtime/nitro/offline-values.mjs +15 -0
- package/dist/runtime/nitro/plugin.d.mts +3 -1
- package/dist/runtime/nitro/plugin.d.ts +3 -1
- package/dist/runtime/nitro/plugin.mjs +329 -92
- package/dist/runtime/plugin.client.mjs +553 -118
- package/dist/runtime/plugin.server.mjs +50 -6
- package/dist/runtime/server/composables/useConfigDirectorServerClient.d.mts +6 -0
- package/dist/runtime/server/composables/useConfigDirectorServerClient.d.ts +6 -0
- package/dist/runtime/server/composables/{useConfigDirectorClient.mjs → useConfigDirectorServerClient.mjs} +3 -3
- package/dist/runtime/server/composables/useConfigDirectorServerHooks.d.mts +1 -1
- package/dist/runtime/server/composables/useConfigDirectorServerHooks.d.ts +1 -1
- package/dist/runtime/{types-Bgx2-3dY.d.mts → types-CS7AMNWe.d.mts} +1 -1
- package/dist/runtime/{types-CvmjergQ.d.mts → types-Dt3bENKf.d.mts} +13 -5
- package/dist/testing.d.mts +49 -27
- package/dist/testing.mjs +429 -185
- package/package.json +1 -1
- package/dist/runtime/server/composables/useConfigDirectorClient.d.mts +0 -6
- package/dist/runtime/server/composables/useConfigDirectorClient.d.ts +0 -6
- package/dist/runtime/types-CHmsCChN.d.mts +0 -60
- /package/dist/runtime/{types-CkzBXJwA.d.mts → types-C30VJ25l.d.mts} +0 -0
package/dist/testing.mjs
CHANGED
|
@@ -49,6 +49,13 @@ var ConfigDirectorValidationError = class ConfigDirectorValidationError extends
|
|
|
49
49
|
Object.setPrototypeOf(this, ConfigDirectorValidationError.prototype);
|
|
50
50
|
}
|
|
51
51
|
};
|
|
52
|
+
var ConfigDirectorInitializationError = class ConfigDirectorInitializationError extends Error {
|
|
53
|
+
name = "ConfigDirectorInitializationError";
|
|
54
|
+
constructor(message) {
|
|
55
|
+
super(message);
|
|
56
|
+
Object.setPrototypeOf(this, ConfigDirectorInitializationError.prototype);
|
|
57
|
+
}
|
|
58
|
+
};
|
|
52
59
|
//#endregion
|
|
53
60
|
//#region ../eventsource/src/errors.ts
|
|
54
61
|
var StreamClosedError = class StreamClosedError extends Error {
|
|
@@ -383,6 +390,7 @@ var StreamingTransport = class {
|
|
|
383
390
|
eventEmitter = new Emitter();
|
|
384
391
|
url;
|
|
385
392
|
timeoutTimer;
|
|
393
|
+
connectionGeneration = 0;
|
|
386
394
|
constructor(options) {
|
|
387
395
|
this.options = options;
|
|
388
396
|
this.options = options;
|
|
@@ -390,6 +398,9 @@ var StreamingTransport = class {
|
|
|
390
398
|
this.url = options.resolveUrl("client/sse/v1", options.baseUrl);
|
|
391
399
|
}
|
|
392
400
|
async connect(context, timeout) {
|
|
401
|
+
const generation = ++this.connectionGeneration;
|
|
402
|
+
const clientSession = await this.options.clientSession;
|
|
403
|
+
if (generation !== this.connectionGeneration) return this;
|
|
393
404
|
if (this.eventSource) this.close();
|
|
394
405
|
const eventSourcePromise = new Promise((resolve, reject) => {
|
|
395
406
|
this.eventSource = createEventSourceClient({
|
|
@@ -401,7 +412,8 @@ var StreamingTransport = class {
|
|
|
401
412
|
givenContext: context,
|
|
402
413
|
metaContext: this.options.metaContext,
|
|
403
414
|
clientSdkKey: this.options.clientSdkKey,
|
|
404
|
-
instanceId:
|
|
415
|
+
instanceId: clientSession.instanceId,
|
|
416
|
+
ephemeral: clientSession.ephemeral
|
|
405
417
|
}),
|
|
406
418
|
onMessage: ({ data }) => {
|
|
407
419
|
this.dispatchMessage(data);
|
|
@@ -466,6 +478,7 @@ var StreamingTransport = class {
|
|
|
466
478
|
this.eventEmitter.clear();
|
|
467
479
|
}
|
|
468
480
|
close() {
|
|
481
|
+
this.connectionGeneration++;
|
|
469
482
|
this.eventSource?.close();
|
|
470
483
|
clearTimeout(this.timeoutTimer);
|
|
471
484
|
}
|
|
@@ -475,6 +488,183 @@ var StreamingTransport = class {
|
|
|
475
488
|
}
|
|
476
489
|
};
|
|
477
490
|
//#endregion
|
|
491
|
+
//#region ../shared/src/native-values.ts
|
|
492
|
+
const encodeNativeValue = (key, value) => {
|
|
493
|
+
if (key.trim().length == 0) throw new ConfigDirectorValidationError("Invalid config key. The key of a value cannot be empty.");
|
|
494
|
+
return encode(key, value);
|
|
495
|
+
};
|
|
496
|
+
const encode = (key, value) => {
|
|
497
|
+
switch (typeof value) {
|
|
498
|
+
case "boolean": return {
|
|
499
|
+
type: "boolean",
|
|
500
|
+
value: value ? "true" : "false"
|
|
501
|
+
};
|
|
502
|
+
case "number": return encodeNumber(key, value);
|
|
503
|
+
case "string": return {
|
|
504
|
+
type: "string",
|
|
505
|
+
value
|
|
506
|
+
};
|
|
507
|
+
case "object": if (value !== null) {
|
|
508
|
+
assertJsonContents(key, value, "");
|
|
509
|
+
return {
|
|
510
|
+
type: "json",
|
|
511
|
+
value: JSON.stringify(value)
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
throw new ConfigDirectorValidationError(`Invalid value for '${key}': ${describe(value)}. Use a boolean, a number, a string, a plain object, or an array.`);
|
|
516
|
+
};
|
|
517
|
+
const encodeNumber = (key, value) => {
|
|
518
|
+
if (!Number.isFinite(value)) throw new ConfigDirectorValidationError(`Invalid value for '${key}': ${describe(value)} is not a finite number.`);
|
|
519
|
+
if (Number.isInteger(value)) {
|
|
520
|
+
if (!Number.isSafeInteger(value)) throw new ConfigDirectorValidationError(`Invalid value for '${key}': ${value} is not a safe integer, so the SDK could not read it back exactly.`);
|
|
521
|
+
return {
|
|
522
|
+
type: "integer",
|
|
523
|
+
value: String(value)
|
|
524
|
+
};
|
|
525
|
+
}
|
|
526
|
+
return {
|
|
527
|
+
type: "float",
|
|
528
|
+
value: toPlainDecimal(value)
|
|
529
|
+
};
|
|
530
|
+
};
|
|
531
|
+
const toPlainDecimal = (value) => {
|
|
532
|
+
const text = String(value);
|
|
533
|
+
const exponentIndex = text.indexOf("e-");
|
|
534
|
+
if (exponentIndex < 0) return text;
|
|
535
|
+
const sign = text.startsWith("-") ? "-" : "";
|
|
536
|
+
const [integerDigits = "", fractionDigits = ""] = text.slice(sign.length, exponentIndex).split(".");
|
|
537
|
+
const leadingZeros = Number(text.slice(exponentIndex + 2)) - integerDigits.length;
|
|
538
|
+
return `${sign}0.${"0".repeat(leadingZeros)}${integerDigits}${fractionDigits}`;
|
|
539
|
+
};
|
|
540
|
+
const assertJsonContents = (key, value, path) => {
|
|
541
|
+
if (value === null || typeof value === "boolean" || typeof value === "string") return;
|
|
542
|
+
if (typeof value === "number") {
|
|
543
|
+
if (!Number.isFinite(value)) throw invalidJsonContents(key, path, `${describe(value)} is not a finite number`);
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
if (Array.isArray(value)) {
|
|
547
|
+
value.forEach((item, index) => assertJsonContents(key, item, `${path}[${index}]`));
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
550
|
+
if (isPlainObject(value)) {
|
|
551
|
+
for (const [property, item] of Object.entries(value)) assertJsonContents(key, item, path ? `${path}.${property}` : property);
|
|
552
|
+
return;
|
|
553
|
+
}
|
|
554
|
+
throw invalidJsonContents(key, path, `${describe(value)} cannot be encoded as JSON`);
|
|
555
|
+
};
|
|
556
|
+
const invalidJsonContents = (key, path, problem) => new ConfigDirectorValidationError(`Invalid value for '${key}'${path ? ` at '${path}'` : ""}: ${problem}. JSON contents can hold booleans, finite numbers, strings, null, arrays, and plain objects.`);
|
|
557
|
+
const isPlainObject = (value) => {
|
|
558
|
+
if (typeof value !== "object" || value === null) return false;
|
|
559
|
+
const prototype = Object.getPrototypeOf(value);
|
|
560
|
+
return prototype === Object.prototype || prototype === null;
|
|
561
|
+
};
|
|
562
|
+
const describe = (value) => {
|
|
563
|
+
if (value === null) return "null";
|
|
564
|
+
if (typeof value === "function") return "a function";
|
|
565
|
+
if (typeof value === "object") return `a ${value.constructor?.name ?? "object"} instance`;
|
|
566
|
+
if (typeof value === "symbol") return "a symbol";
|
|
567
|
+
return String(value);
|
|
568
|
+
};
|
|
569
|
+
//#endregion
|
|
570
|
+
//#region ../js-client-core/src/native-values.ts
|
|
571
|
+
const nativeValueConfigState = (id, key, value, valueId) => {
|
|
572
|
+
const encoded = encodeNativeValue(key, value);
|
|
573
|
+
return {
|
|
574
|
+
id,
|
|
575
|
+
key,
|
|
576
|
+
type: encoded.type,
|
|
577
|
+
value: encoded.value,
|
|
578
|
+
valueId
|
|
579
|
+
};
|
|
580
|
+
};
|
|
581
|
+
//#endregion
|
|
582
|
+
//#region ../js-client-core/src/OfflineTransport.ts
|
|
583
|
+
const OFFLINE_ENVIRONMENT = {
|
|
584
|
+
environmentId: "offline",
|
|
585
|
+
projectId: "offline"
|
|
586
|
+
};
|
|
587
|
+
var OfflineTransport = class {
|
|
588
|
+
emitter = new Emitter();
|
|
589
|
+
constructor(source) {
|
|
590
|
+
this.source = source;
|
|
591
|
+
}
|
|
592
|
+
async connect() {
|
|
593
|
+
const configs = loadOfflineConfigs(this.source);
|
|
594
|
+
this.emitter.emit("configSetReceived", {
|
|
595
|
+
...OFFLINE_ENVIRONMENT,
|
|
596
|
+
kind: "full",
|
|
597
|
+
configs
|
|
598
|
+
});
|
|
599
|
+
return this;
|
|
600
|
+
}
|
|
601
|
+
on = (name, handler) => this.emitter.on(name, handler);
|
|
602
|
+
off = (name, handler) => this.emitter.off(name, handler);
|
|
603
|
+
clear() {
|
|
604
|
+
this.emitter.clear();
|
|
605
|
+
}
|
|
606
|
+
close() {}
|
|
607
|
+
dispose() {
|
|
608
|
+
this.clear();
|
|
609
|
+
}
|
|
610
|
+
};
|
|
611
|
+
const loadOfflineConfigs = (source) => {
|
|
612
|
+
switch (source.kind) {
|
|
613
|
+
case "in-code-defaults": return {};
|
|
614
|
+
case "values": return encodeOfflineValues(source.values);
|
|
615
|
+
case "rejected": throw new ConfigDirectorInitializationError(source.message);
|
|
616
|
+
}
|
|
617
|
+
};
|
|
618
|
+
const encodeOfflineValues = (values) => {
|
|
619
|
+
try {
|
|
620
|
+
return Object.fromEntries(Object.entries(values).map(([key, value]) => [key, nativeValueConfigState(`offline:${key}`, key, value, void 0)]));
|
|
621
|
+
} catch (error) {
|
|
622
|
+
if (error instanceof ConfigDirectorValidationError) throw new ConfigDirectorInitializationError(`The offline values could not be loaded. ${error.message}`);
|
|
623
|
+
throw error;
|
|
624
|
+
}
|
|
625
|
+
};
|
|
626
|
+
//#endregion
|
|
627
|
+
//#region ../shared/src/constants.ts
|
|
628
|
+
const CLIENT_BASE_URL = "https://client-sdk-api.configdirector.com";
|
|
629
|
+
//#endregion
|
|
630
|
+
//#region ../js-client-core/src/connection-plan.ts
|
|
631
|
+
const planConnection = (clientSdkKey, connection, urlFactory) => {
|
|
632
|
+
if (connection?.mode === "offline") return {
|
|
633
|
+
kind: "stay-offline",
|
|
634
|
+
source: offlineSourceOf(connection)
|
|
635
|
+
};
|
|
636
|
+
const mode = connection?.mode ?? "streaming";
|
|
637
|
+
if (!clientSdkKey || clientSdkKey.trim().length == 0) throw new ConfigDirectorValidationError("No client SDK key was provided, the client cannot be instantiated without a valid client SDK key");
|
|
638
|
+
if (connection?.values !== void 0) return {
|
|
639
|
+
kind: "stay-offline",
|
|
640
|
+
source: {
|
|
641
|
+
kind: "rejected",
|
|
642
|
+
message: `The connection option 'values' applies only when the connection mode is 'offline', but the mode is '${mode}'.`
|
|
643
|
+
}
|
|
644
|
+
};
|
|
645
|
+
return {
|
|
646
|
+
kind: "connect",
|
|
647
|
+
mode,
|
|
648
|
+
clientSdkKey,
|
|
649
|
+
baseUrl: baseUrlOf(connection?.url, urlFactory)
|
|
650
|
+
};
|
|
651
|
+
};
|
|
652
|
+
const baseUrlOf = (url, urlFactory) => {
|
|
653
|
+
if (!url) return CLIENT_BASE_URL;
|
|
654
|
+
try {
|
|
655
|
+
return urlFactory(url);
|
|
656
|
+
} catch (error) {
|
|
657
|
+
throw new ConfigDirectorValidationError(`Invalid base URL '${url}'. Parsing failed: ${error}`);
|
|
658
|
+
}
|
|
659
|
+
};
|
|
660
|
+
const offlineSourceOf = (connection) => {
|
|
661
|
+
if (connection.values !== void 0) return {
|
|
662
|
+
kind: "values",
|
|
663
|
+
values: connection.values
|
|
664
|
+
};
|
|
665
|
+
return { kind: "in-code-defaults" };
|
|
666
|
+
};
|
|
667
|
+
//#endregion
|
|
478
668
|
//#region ../shared/src/value-parser.ts
|
|
479
669
|
const getRequestedType = (defaultValue) => {
|
|
480
670
|
const baseType = typeof defaultValue;
|
|
@@ -694,6 +884,14 @@ const createDefaultLogger = (level, messageDecorator) => {
|
|
|
694
884
|
return new DefaultConsoleLogger(level ?? "warn", messageDecorator ?? new LogMessageDecorator());
|
|
695
885
|
};
|
|
696
886
|
//#endregion
|
|
887
|
+
//#region ../js-client-core/src/telemetry/discarding.ts
|
|
888
|
+
const discardingTelemetryClient = {
|
|
889
|
+
updateContext: async () => {},
|
|
890
|
+
evaluatedConfig: () => {},
|
|
891
|
+
recordInvalidContext: () => {},
|
|
892
|
+
close: async () => {}
|
|
893
|
+
};
|
|
894
|
+
//#endregion
|
|
697
895
|
//#region ../shared/src/url.ts
|
|
698
896
|
const defaultUrlFactory = (input, base) => new URL(input, base?.toString());
|
|
699
897
|
//#endregion
|
|
@@ -722,10 +920,11 @@ const generateInstanceId = () => {
|
|
|
722
920
|
});
|
|
723
921
|
};
|
|
724
922
|
//#endregion
|
|
725
|
-
//#region ../js-client-core/src/
|
|
923
|
+
//#region ../js-client-core/src/client-session.ts
|
|
726
924
|
const STORAGE_KEY = "configdirector-sdk:instance-id";
|
|
727
925
|
const RECORD_VERSION = 1;
|
|
728
926
|
const MAX_AGE_MS = 360 * 60 * 1e3;
|
|
927
|
+
const STORAGE_DEADLINE_MS = 1e3;
|
|
729
928
|
const CHECKSUM_SALT = "configdirector.instanceId.v1";
|
|
730
929
|
const UUID_V4_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
|
|
731
930
|
const fnv1a = (input, offsetBasis) => {
|
|
@@ -741,7 +940,7 @@ const checksumFor = (id, createdAt) => {
|
|
|
741
940
|
const low = fnv1a(payload, 2166136261);
|
|
742
941
|
return fnv1a(`${low}|${payload}`, 16777619).toString(16).padStart(8, "0") + low.toString(16).padStart(8, "0");
|
|
743
942
|
};
|
|
744
|
-
const
|
|
943
|
+
const localStorageIfAvailable = () => {
|
|
745
944
|
try {
|
|
746
945
|
return globalThis.localStorage ?? void 0;
|
|
747
946
|
} catch {
|
|
@@ -756,34 +955,37 @@ const isUsableRecord = (record) => {
|
|
|
756
955
|
if (!UUID_V4_PATTERN.test(id)) return false;
|
|
757
956
|
return checksum === checksumFor(id, createdAt);
|
|
758
957
|
};
|
|
759
|
-
const readStoredInstanceId = (storage, logger) => {
|
|
958
|
+
const readStoredInstanceId = async (storage, logger) => {
|
|
760
959
|
let raw;
|
|
761
960
|
try {
|
|
762
|
-
raw = storage.getItem(STORAGE_KEY);
|
|
961
|
+
raw = await storage.getItem(STORAGE_KEY);
|
|
763
962
|
} catch (error) {
|
|
764
|
-
logger
|
|
765
|
-
return;
|
|
963
|
+
logger.debug("[ConfigDirectorClient] Could not read the stored instance id, using an ephemeral one", error);
|
|
964
|
+
return { outcome: "failed" };
|
|
766
965
|
}
|
|
767
|
-
if (
|
|
966
|
+
if (typeof raw !== "string" || raw === "") return { outcome: "nothing usable" };
|
|
768
967
|
let record;
|
|
769
968
|
try {
|
|
770
969
|
record = JSON.parse(raw);
|
|
771
970
|
} catch {
|
|
772
|
-
logger
|
|
773
|
-
return;
|
|
971
|
+
logger.debug("[ConfigDirectorClient] The stored instance id is not valid JSON, generating a new one");
|
|
972
|
+
return { outcome: "nothing usable" };
|
|
774
973
|
}
|
|
775
974
|
if (!isUsableRecord(record)) {
|
|
776
|
-
logger
|
|
777
|
-
return;
|
|
975
|
+
logger.debug("[ConfigDirectorClient] The stored instance id failed validation, generating a new one");
|
|
976
|
+
return { outcome: "nothing usable" };
|
|
778
977
|
}
|
|
779
978
|
const age = Date.now() - record.createdAt;
|
|
780
979
|
if (age < 0 || age >= MAX_AGE_MS) {
|
|
781
|
-
logger
|
|
782
|
-
return;
|
|
980
|
+
logger.debug("[ConfigDirectorClient] The stored instance id has expired, generating a new one");
|
|
981
|
+
return { outcome: "nothing usable" };
|
|
783
982
|
}
|
|
784
|
-
return
|
|
983
|
+
return {
|
|
984
|
+
outcome: "stored",
|
|
985
|
+
instanceId: record.id
|
|
986
|
+
};
|
|
785
987
|
};
|
|
786
|
-
const
|
|
988
|
+
const persistInstanceId = async (storage, id, logger) => {
|
|
787
989
|
const createdAt = Date.now();
|
|
788
990
|
const record = {
|
|
789
991
|
v: RECORD_VERSION,
|
|
@@ -792,19 +994,54 @@ const storeInstanceId = (storage, id, logger) => {
|
|
|
792
994
|
checksum: checksumFor(id, createdAt)
|
|
793
995
|
};
|
|
794
996
|
try {
|
|
795
|
-
storage.setItem(STORAGE_KEY, JSON.stringify(record));
|
|
997
|
+
await storage.setItem(STORAGE_KEY, JSON.stringify(record));
|
|
998
|
+
return true;
|
|
796
999
|
} catch (error) {
|
|
797
|
-
logger
|
|
1000
|
+
logger.debug("[ConfigDirectorClient] Could not persist the instance id, using it as an ephemeral one", error);
|
|
1001
|
+
return false;
|
|
798
1002
|
}
|
|
799
1003
|
};
|
|
800
|
-
const
|
|
801
|
-
const
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
1004
|
+
const settleFromStorage = async (storage, provisionalId, deadline, logger) => {
|
|
1005
|
+
const read = await readStoredInstanceId(storage, logger);
|
|
1006
|
+
switch (read.outcome) {
|
|
1007
|
+
case "stored": return { instanceId: read.instanceId };
|
|
1008
|
+
case "failed": return {
|
|
1009
|
+
instanceId: provisionalId,
|
|
1010
|
+
ephemeral: true
|
|
1011
|
+
};
|
|
1012
|
+
case "nothing usable": break;
|
|
1013
|
+
}
|
|
1014
|
+
if (deadline.passed) return {
|
|
1015
|
+
instanceId: provisionalId,
|
|
1016
|
+
ephemeral: true
|
|
1017
|
+
};
|
|
1018
|
+
return await persistInstanceId(storage, provisionalId, logger) ? { instanceId: provisionalId } : {
|
|
1019
|
+
instanceId: provisionalId,
|
|
1020
|
+
ephemeral: true
|
|
1021
|
+
};
|
|
1022
|
+
};
|
|
1023
|
+
const resolveClientSession = (logger, storage = localStorageIfAvailable()) => {
|
|
1024
|
+
const provisionalId = generateInstanceId();
|
|
1025
|
+
if (!storage) {
|
|
1026
|
+
logger.debug("[ConfigDirectorClient] No instance id storage is available, using an ephemeral instance id");
|
|
1027
|
+
return Promise.resolve({
|
|
1028
|
+
instanceId: provisionalId,
|
|
1029
|
+
ephemeral: true
|
|
1030
|
+
});
|
|
1031
|
+
}
|
|
1032
|
+
const deadline = { passed: false };
|
|
1033
|
+
let deadlineTimer;
|
|
1034
|
+
const fallbackAtDeadline = new Promise((resolve) => {
|
|
1035
|
+
deadlineTimer = setTimeout(() => {
|
|
1036
|
+
deadline.passed = true;
|
|
1037
|
+
logger.warn(`[ConfigDirectorClient] The instance id storage did not respond within ${STORAGE_DEADLINE_MS}ms, using an ephemeral instance id`);
|
|
1038
|
+
resolve({
|
|
1039
|
+
instanceId: provisionalId,
|
|
1040
|
+
ephemeral: true
|
|
1041
|
+
});
|
|
1042
|
+
}, STORAGE_DEADLINE_MS);
|
|
1043
|
+
});
|
|
1044
|
+
return Promise.race([settleFromStorage(storage, provisionalId, deadline, logger), fallbackAtDeadline]).finally(() => clearTimeout(deadlineTimer));
|
|
808
1045
|
};
|
|
809
1046
|
//#endregion
|
|
810
1047
|
//#region ../shared/src/transport/AbstractPollingTransport.ts
|
|
@@ -885,6 +1122,7 @@ var PollingTransport = class extends AbstractPollingTransport {
|
|
|
885
1122
|
eventEmitter = new Emitter();
|
|
886
1123
|
url;
|
|
887
1124
|
lastUpdateTimestamp;
|
|
1125
|
+
connectionGeneration = 0;
|
|
888
1126
|
constructor(options) {
|
|
889
1127
|
super();
|
|
890
1128
|
this.options = options;
|
|
@@ -894,19 +1132,26 @@ var PollingTransport = class extends AbstractPollingTransport {
|
|
|
894
1132
|
this.pollingIntervalSeconds = options.pollingInterval ?? 60;
|
|
895
1133
|
}
|
|
896
1134
|
async connect(context, timeout) {
|
|
1135
|
+
const generation = ++this.connectionGeneration;
|
|
897
1136
|
this.clearPollingInterval();
|
|
1137
|
+
const clientSession = await this.options.clientSession;
|
|
1138
|
+
if (generation !== this.connectionGeneration) return this;
|
|
898
1139
|
try {
|
|
899
|
-
await this.fetchConfigs(context, timeout);
|
|
1140
|
+
await this.fetchConfigs(context, timeout, clientSession);
|
|
900
1141
|
} finally {
|
|
901
1142
|
if (!this.fatalError) this.schedulePollingInterval(() => {
|
|
902
|
-
this.fetchConfigs(context, timeout).catch((error) => {
|
|
1143
|
+
this.fetchConfigs(context, timeout, clientSession).catch((error) => {
|
|
903
1144
|
this.logger.error("[PollingTransport] Error during polling:", error);
|
|
904
1145
|
});
|
|
905
1146
|
});
|
|
906
1147
|
}
|
|
907
1148
|
return this;
|
|
908
1149
|
}
|
|
909
|
-
|
|
1150
|
+
close() {
|
|
1151
|
+
this.connectionGeneration++;
|
|
1152
|
+
super.close();
|
|
1153
|
+
}
|
|
1154
|
+
async fetchConfigs(context, timeout, clientSession) {
|
|
910
1155
|
if (this.fatalError) {
|
|
911
1156
|
this.logger.warn("[PollingTransport] There was a prior unrecoverable error. Ignoring attempt to reconnect.");
|
|
912
1157
|
return;
|
|
@@ -919,7 +1164,8 @@ var PollingTransport = class extends AbstractPollingTransport {
|
|
|
919
1164
|
givenContext: context,
|
|
920
1165
|
metaContext: this.options.metaContext,
|
|
921
1166
|
clientSdkKey: this.options.clientSdkKey,
|
|
922
|
-
instanceId:
|
|
1167
|
+
instanceId: clientSession.instanceId,
|
|
1168
|
+
ephemeral: clientSession.ephemeral,
|
|
923
1169
|
lastUpdateTimestamp: this.lastUpdateTimestamp
|
|
924
1170
|
})
|
|
925
1171
|
}, this.logger);
|
|
@@ -960,9 +1206,49 @@ const read = (readValue) => {
|
|
|
960
1206
|
const readUserAgent = () => read((globals) => globals.navigator?.userAgent);
|
|
961
1207
|
const readHost = () => read((globals) => globals.location?.host);
|
|
962
1208
|
//#endregion
|
|
1209
|
+
//#region ../shared/src/context-kind.ts
|
|
1210
|
+
const CONTEXT_KIND_MAXIMUM_LENGTH = 64;
|
|
1211
|
+
const KIND_SAMPLE_MAXIMUM_CHARACTERS = 128;
|
|
1212
|
+
const isContextKindCharacter = (character) => character >= "a" && character <= "z" || character >= "0" && character <= "9" || character === "-" || character === "_";
|
|
1213
|
+
const isValidContextKind = (kind) => {
|
|
1214
|
+
if (kind.length < 1 || kind.length > CONTEXT_KIND_MAXIMUM_LENGTH) return false;
|
|
1215
|
+
for (const character of kind) if (!isContextKindCharacter(character)) return false;
|
|
1216
|
+
return true;
|
|
1217
|
+
};
|
|
1218
|
+
const isAcceptedKind = (kind) => kind === void 0 || kind === null || typeof kind === "string" && isValidContextKind(kind);
|
|
1219
|
+
const stringFormOf = (kind) => {
|
|
1220
|
+
if (typeof kind === "string") return kind;
|
|
1221
|
+
try {
|
|
1222
|
+
return String(kind);
|
|
1223
|
+
} catch {
|
|
1224
|
+
return Object.prototype.toString.call(kind);
|
|
1225
|
+
}
|
|
1226
|
+
};
|
|
1227
|
+
const firstCharactersOf = (text, maximumCharacters) => {
|
|
1228
|
+
let sample = "";
|
|
1229
|
+
let characterCount = 0;
|
|
1230
|
+
for (const character of text) {
|
|
1231
|
+
if (characterCount === maximumCharacters) break;
|
|
1232
|
+
sample += character;
|
|
1233
|
+
characterCount += 1;
|
|
1234
|
+
}
|
|
1235
|
+
return sample;
|
|
1236
|
+
};
|
|
1237
|
+
const admitContext = (context) => {
|
|
1238
|
+
const kind = context?.kind;
|
|
1239
|
+
if (isAcceptedKind(kind)) return {
|
|
1240
|
+
outcome: "admitted",
|
|
1241
|
+
context
|
|
1242
|
+
};
|
|
1243
|
+
return {
|
|
1244
|
+
outcome: "ignored",
|
|
1245
|
+
kindSample: firstCharactersOf(stringFormOf(kind), KIND_SAMPLE_MAXIMUM_CHARACTERS)
|
|
1246
|
+
};
|
|
1247
|
+
};
|
|
1248
|
+
//#endregion
|
|
963
1249
|
//#region ../js-client-core/src/DefaultConfigDirectorClient.ts
|
|
964
1250
|
const MAX_EXPONENTIAL_DELAY = 9;
|
|
965
|
-
var DefaultConfigDirectorClient = class {
|
|
1251
|
+
var DefaultConfigDirectorClient = class DefaultConfigDirectorClient {
|
|
966
1252
|
logger;
|
|
967
1253
|
telemetryClient;
|
|
968
1254
|
configSet;
|
|
@@ -977,18 +1263,20 @@ var DefaultConfigDirectorClient = class {
|
|
|
977
1263
|
readyPromise;
|
|
978
1264
|
readyResolve;
|
|
979
1265
|
currentContext;
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
1266
|
+
connectionPlan;
|
|
1267
|
+
clientSession;
|
|
1268
|
+
static from(liveTelemetryClient, clientSdkKeyOrOptions, sdkOptions, clientOptions) {
|
|
1269
|
+
if (typeof clientSdkKeyOrOptions === "object") return new DefaultConfigDirectorClient(liveTelemetryClient, void 0, sdkOptions, clientSdkKeyOrOptions);
|
|
1270
|
+
return new DefaultConfigDirectorClient(liveTelemetryClient, clientSdkKeyOrOptions, sdkOptions, clientOptions);
|
|
1271
|
+
}
|
|
1272
|
+
constructor(liveTelemetryClient, clientSdkKey, sdkOptions, clientOptions, internalClientOptions) {
|
|
983
1273
|
this.logger = clientOptions?.logger ?? createDefaultLogger();
|
|
984
|
-
this.
|
|
1274
|
+
this.clientSession = resolveClientSession(this.logger, internalClientOptions?.instanceIdStorage);
|
|
985
1275
|
this.timeout = clientOptions?.connection?.timeout ?? 3e3;
|
|
986
|
-
this.validateSdkKeyPresence(clientSdkKey);
|
|
987
1276
|
const urlFactory = internalClientOptions?.urlFactory ?? defaultUrlFactory;
|
|
988
|
-
|
|
989
|
-
this.
|
|
990
|
-
this.
|
|
991
|
-
this.transport = internalClientOptions?.transport ?? this.buildTransport(clientSdkKey, sdkOptions, baseUrl, urlFactory, clientOptions, internalClientOptions);
|
|
1277
|
+
this.connectionPlan = planConnection(clientSdkKey, clientOptions?.connection, urlFactory);
|
|
1278
|
+
this.telemetryClient = this.buildTelemetryClient(liveTelemetryClient, clientOptions);
|
|
1279
|
+
this.transport = internalClientOptions?.transport ?? this.buildTransport(sdkOptions, clientOptions, urlFactory, internalClientOptions);
|
|
992
1280
|
this.transport.on("configSetReceived", (configSet) => {
|
|
993
1281
|
this.hasReceivedConfigSet = true;
|
|
994
1282
|
this.readyResolve?.();
|
|
@@ -1025,8 +1313,8 @@ var DefaultConfigDirectorClient = class {
|
|
|
1025
1313
|
const handlers = Array.isArray(handler) ? handler : [handler];
|
|
1026
1314
|
for (const h of handlers) if (typeof h === "function") this.on(event, h);
|
|
1027
1315
|
}
|
|
1028
|
-
|
|
1029
|
-
if (
|
|
1316
|
+
pollingIntervalFor(mode, configuredSeconds) {
|
|
1317
|
+
if (mode !== "polling") return;
|
|
1030
1318
|
const resolved = resolvePollingInterval(configuredSeconds, {
|
|
1031
1319
|
defaultSeconds: 60,
|
|
1032
1320
|
minimumSeconds: 30
|
|
@@ -1034,39 +1322,79 @@ var DefaultConfigDirectorClient = class {
|
|
|
1034
1322
|
if (resolved.warning) this.logger.warn(`[ConfigDirectorClient] ${resolved.warning}`);
|
|
1035
1323
|
return resolved.seconds;
|
|
1036
1324
|
}
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
host: readHost()
|
|
1049
|
-
},
|
|
1050
|
-
logger: this.logger,
|
|
1051
|
-
fetch: internalClientOptions?.fetch,
|
|
1052
|
-
connectionRetryDelay: internalClientOptions?.connectionRetryDelay ?? ((attempt) => {
|
|
1053
|
-
return Math.pow(2, Math.min(attempt, MAX_EXPONENTIAL_DELAY)) * 1e3;
|
|
1054
|
-
}),
|
|
1055
|
-
pollingInterval: this.resolvePollingIntervalForMode(clientOptions?.connection?.pollingInterval)
|
|
1056
|
-
});
|
|
1325
|
+
buildTelemetryClient(liveTelemetryClient, clientOptions) {
|
|
1326
|
+
const plan = this.connectionPlan;
|
|
1327
|
+
switch (plan.kind) {
|
|
1328
|
+
case "stay-offline": return discardingTelemetryClient;
|
|
1329
|
+
case "connect": return liveTelemetryClient({
|
|
1330
|
+
clientSdkKey: plan.clientSdkKey,
|
|
1331
|
+
baseUrl: plan.baseUrl,
|
|
1332
|
+
logger: this.logger,
|
|
1333
|
+
metadata: clientOptions?.metadata
|
|
1334
|
+
});
|
|
1335
|
+
}
|
|
1057
1336
|
}
|
|
1058
|
-
|
|
1059
|
-
|
|
1337
|
+
buildTransport(sdkOptions, clientOptions, urlFactory, internalClientOptions) {
|
|
1338
|
+
const plan = this.connectionPlan;
|
|
1339
|
+
switch (plan.kind) {
|
|
1340
|
+
case "stay-offline": return new OfflineTransport(plan.source);
|
|
1341
|
+
case "connect": return new (this.getTransportConstructor(plan.mode))({
|
|
1342
|
+
clientSession: this.clientSession,
|
|
1343
|
+
clientSdkKey: plan.clientSdkKey,
|
|
1344
|
+
baseUrl: plan.baseUrl,
|
|
1345
|
+
resolveUrl: urlFactory,
|
|
1346
|
+
metaContext: {
|
|
1347
|
+
...clientOptions?.metadata,
|
|
1348
|
+
sdkName: sdkOptions.sdkName,
|
|
1349
|
+
sdkVersion: sdkOptions.sdkVersion,
|
|
1350
|
+
userAgent: readUserAgent(),
|
|
1351
|
+
host: readHost()
|
|
1352
|
+
},
|
|
1353
|
+
logger: this.logger,
|
|
1354
|
+
fetch: internalClientOptions?.fetch,
|
|
1355
|
+
connectionRetryDelay: internalClientOptions?.connectionRetryDelay ?? ((attempt) => {
|
|
1356
|
+
return Math.pow(2, Math.min(attempt, MAX_EXPONENTIAL_DELAY)) * 1e3;
|
|
1357
|
+
}),
|
|
1358
|
+
pollingInterval: this.pollingIntervalFor(plan.mode, clientOptions?.connection?.pollingInterval)
|
|
1359
|
+
});
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
getTransportConstructor(mode) {
|
|
1363
|
+
switch (mode) {
|
|
1060
1364
|
case "polling": return PollingTransport;
|
|
1061
|
-
|
|
1365
|
+
case "streaming": return StreamingTransport;
|
|
1062
1366
|
}
|
|
1063
1367
|
}
|
|
1064
1368
|
async initialize(context) {
|
|
1065
1369
|
if (!this.hasReceivedConfigSet) this.initializing = true;
|
|
1066
|
-
await this.
|
|
1370
|
+
await this.submitContext(context, "initialization");
|
|
1067
1371
|
}
|
|
1068
1372
|
async updateContext(context) {
|
|
1069
|
-
|
|
1373
|
+
switch (this.connectionPlan.kind) {
|
|
1374
|
+
case "connect":
|
|
1375
|
+
await this.submitContext(context, "context update");
|
|
1376
|
+
return;
|
|
1377
|
+
case "stay-offline":
|
|
1378
|
+
this.storeContext(this.admittedContextFor(context, "context update"));
|
|
1379
|
+
return;
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
storeContext(context) {
|
|
1383
|
+
this.currentContext = context;
|
|
1384
|
+
this.emit("contextUpdated", { context });
|
|
1385
|
+
}
|
|
1386
|
+
async submitContext(context, submission) {
|
|
1387
|
+
await this.connectToTransport(this.admittedContextFor(context, submission), submission);
|
|
1388
|
+
}
|
|
1389
|
+
admittedContextFor(suppliedContext, submission) {
|
|
1390
|
+
const admission = admitContext(suppliedContext);
|
|
1391
|
+
switch (admission.outcome) {
|
|
1392
|
+
case "admitted": return admission.context;
|
|
1393
|
+
case "ignored":
|
|
1394
|
+
this.logger.error(`[ConfigDirectorClient] Ignored the context supplied for ${submission} because its kind '${admission.kindSample}' is invalid; continuing as if no context was supplied. A context kind has 1 to 64 characters, using only lowercase letters, digits, '-' and '_'.`);
|
|
1395
|
+
this.telemetryClient.recordInvalidContext(admission.kindSample);
|
|
1396
|
+
return;
|
|
1397
|
+
}
|
|
1070
1398
|
}
|
|
1071
1399
|
get context() {
|
|
1072
1400
|
return this.currentContext;
|
|
@@ -1095,11 +1423,21 @@ var DefaultConfigDirectorClient = class {
|
|
|
1095
1423
|
if (remainingTimeout > 0) await Promise.race([this.readyPromise, new Promise((resolve) => {
|
|
1096
1424
|
this.timeoutTimer = setTimeout(() => resolve(), remainingTimeout);
|
|
1097
1425
|
})]).finally(() => clearTimeout(this.timeoutTimer));
|
|
1098
|
-
if (!this.ready) this.logger.warn(`[ConfigDirectorClient] Timed out waiting for ${caller} after ${this.timeout}ms.
|
|
1426
|
+
if (!this.ready) this.logger.warn(`[ConfigDirectorClient] Timed out waiting for ${caller} after ${this.timeout}ms. ${this.timeoutWarningDetails()}`);
|
|
1099
1427
|
} catch (error) {
|
|
1428
|
+
if (error instanceof ConfigDirectorInitializationError) {
|
|
1429
|
+
this.initializing = false;
|
|
1430
|
+
throw error;
|
|
1431
|
+
}
|
|
1100
1432
|
this.logger.error(`[ConfigDirectorClient] An error occurred during ${caller}: `, error);
|
|
1101
1433
|
}
|
|
1102
1434
|
}
|
|
1435
|
+
timeoutWarningDetails() {
|
|
1436
|
+
switch (this.connectionPlan.kind) {
|
|
1437
|
+
case "connect": return "The client will continue to retry since there were no fatal errors detected. Configs will return the default value until the connection succeeds.";
|
|
1438
|
+
case "stay-offline": return "The offline values were not delivered, so configs return the default value.";
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1103
1441
|
findKeysRemovedBy(configSet) {
|
|
1104
1442
|
if (!this.configSet || configSet.kind != "full") return [];
|
|
1105
1443
|
return Object.keys(this.configSet.configs).filter((key) => !Object.prototype.hasOwnProperty.call(configSet.configs, key));
|
|
@@ -1194,17 +1532,6 @@ var DefaultConfigDirectorClient = class {
|
|
|
1194
1532
|
this.logger.error(`[ConfigDirectorClient] Error executing event handlers for '${name}'`, error);
|
|
1195
1533
|
}
|
|
1196
1534
|
}
|
|
1197
|
-
parseUrl(url, urlFactory) {
|
|
1198
|
-
if (!url) return;
|
|
1199
|
-
try {
|
|
1200
|
-
return urlFactory(url);
|
|
1201
|
-
} catch (error) {
|
|
1202
|
-
throw new ConfigDirectorValidationError(`Invalid base URL '${url}'. Parsing failed: ${error}`);
|
|
1203
|
-
}
|
|
1204
|
-
}
|
|
1205
|
-
validateSdkKeyPresence(sdkKey) {
|
|
1206
|
-
if (!sdkKey || sdkKey.trim().length == 0) throw new ConfigDirectorValidationError("No client SDK key was provided, the client cannot be instantiated without a valid client SDK key");
|
|
1207
|
-
}
|
|
1208
1535
|
validateDefaultValue(defaultValue) {
|
|
1209
1536
|
if (defaultValue === void 0 || defaultValue === null) throw new ConfigDirectorValidationError("Invalid default value. The default value for a config must be defined and non-null.");
|
|
1210
1537
|
if (typeof defaultValue === "function") throw new ConfigDirectorValidationError("Invalid default value. The default value for a config cannot be a function.");
|
|
@@ -1230,14 +1557,24 @@ var DefaultConfigDirectorClient = class {
|
|
|
1230
1557
|
this.handlersMap.clear();
|
|
1231
1558
|
}
|
|
1232
1559
|
pauseNetwork() {
|
|
1233
|
-
this.
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1560
|
+
switch (this.connectionPlan.kind) {
|
|
1561
|
+
case "stay-offline": return;
|
|
1562
|
+
case "connect":
|
|
1563
|
+
this.logger.debug("[ConfigDirectorClient] pauseNetwork() called, pausing transport connection");
|
|
1564
|
+
clearTimeout(this.timeoutTimer);
|
|
1565
|
+
this.timeoutTimer = void 0;
|
|
1566
|
+
this.transport.close();
|
|
1567
|
+
this.ready = false;
|
|
1568
|
+
return;
|
|
1569
|
+
}
|
|
1238
1570
|
}
|
|
1239
1571
|
async resumeNetwork() {
|
|
1240
|
-
|
|
1572
|
+
switch (this.connectionPlan.kind) {
|
|
1573
|
+
case "stay-offline": return;
|
|
1574
|
+
case "connect":
|
|
1575
|
+
await this.connectToTransport(this.currentContext, "network resume");
|
|
1576
|
+
return;
|
|
1577
|
+
}
|
|
1241
1578
|
}
|
|
1242
1579
|
async close() {
|
|
1243
1580
|
this.logger.debug("[ConfigDirectorClient] close() has been called, closing connection to server");
|
|
@@ -1254,97 +1591,6 @@ var DefaultConfigDirectorClient = class {
|
|
|
1254
1591
|
}
|
|
1255
1592
|
};
|
|
1256
1593
|
//#endregion
|
|
1257
|
-
//#region ../shared/src/testing/values.ts
|
|
1258
|
-
const encodeTestValue$1 = (key, value) => {
|
|
1259
|
-
if (key.trim().length == 0) throw new ConfigDirectorValidationError("Invalid config key. The key of a test value cannot be empty.");
|
|
1260
|
-
return encode(key, value);
|
|
1261
|
-
};
|
|
1262
|
-
const encode = (key, value) => {
|
|
1263
|
-
switch (typeof value) {
|
|
1264
|
-
case "boolean": return {
|
|
1265
|
-
type: "boolean",
|
|
1266
|
-
value: value ? "true" : "false"
|
|
1267
|
-
};
|
|
1268
|
-
case "number": return encodeNumber(key, value);
|
|
1269
|
-
case "string": return {
|
|
1270
|
-
type: "string",
|
|
1271
|
-
value
|
|
1272
|
-
};
|
|
1273
|
-
case "object": if (value !== null) {
|
|
1274
|
-
assertJsonContents(key, value, "");
|
|
1275
|
-
return {
|
|
1276
|
-
type: "json",
|
|
1277
|
-
value: JSON.stringify(value)
|
|
1278
|
-
};
|
|
1279
|
-
}
|
|
1280
|
-
}
|
|
1281
|
-
throw new ConfigDirectorValidationError(`Invalid test value for '${key}': ${describe(value)}. Use a boolean, a number, a string, a plain object, or an array.`);
|
|
1282
|
-
};
|
|
1283
|
-
const encodeNumber = (key, value) => {
|
|
1284
|
-
if (!Number.isFinite(value)) throw new ConfigDirectorValidationError(`Invalid test value for '${key}': ${describe(value)} is not a finite number.`);
|
|
1285
|
-
if (Number.isInteger(value)) {
|
|
1286
|
-
if (!Number.isSafeInteger(value)) throw new ConfigDirectorValidationError(`Invalid test value for '${key}': ${value} is not a safe integer, so the SDK could not read it back exactly.`);
|
|
1287
|
-
return {
|
|
1288
|
-
type: "integer",
|
|
1289
|
-
value: String(value)
|
|
1290
|
-
};
|
|
1291
|
-
}
|
|
1292
|
-
return {
|
|
1293
|
-
type: "float",
|
|
1294
|
-
value: toPlainDecimal(value)
|
|
1295
|
-
};
|
|
1296
|
-
};
|
|
1297
|
-
const toPlainDecimal = (value) => {
|
|
1298
|
-
const text = String(value);
|
|
1299
|
-
const exponentIndex = text.indexOf("e-");
|
|
1300
|
-
if (exponentIndex < 0) return text;
|
|
1301
|
-
const sign = text.startsWith("-") ? "-" : "";
|
|
1302
|
-
const [integerDigits = "", fractionDigits = ""] = text.slice(sign.length, exponentIndex).split(".");
|
|
1303
|
-
const leadingZeros = Number(text.slice(exponentIndex + 2)) - integerDigits.length;
|
|
1304
|
-
return `${sign}0.${"0".repeat(leadingZeros)}${integerDigits}${fractionDigits}`;
|
|
1305
|
-
};
|
|
1306
|
-
const assertJsonContents = (key, value, path) => {
|
|
1307
|
-
if (value === null || typeof value === "boolean" || typeof value === "string") return;
|
|
1308
|
-
if (typeof value === "number") {
|
|
1309
|
-
if (!Number.isFinite(value)) throw invalidJsonContents(key, path, `${describe(value)} is not a finite number`);
|
|
1310
|
-
return;
|
|
1311
|
-
}
|
|
1312
|
-
if (Array.isArray(value)) {
|
|
1313
|
-
value.forEach((item, index) => assertJsonContents(key, item, `${path}[${index}]`));
|
|
1314
|
-
return;
|
|
1315
|
-
}
|
|
1316
|
-
if (isPlainObject(value)) {
|
|
1317
|
-
for (const [property, item] of Object.entries(value)) assertJsonContents(key, item, path ? `${path}.${property}` : property);
|
|
1318
|
-
return;
|
|
1319
|
-
}
|
|
1320
|
-
throw invalidJsonContents(key, path, `${describe(value)} cannot be encoded as JSON`);
|
|
1321
|
-
};
|
|
1322
|
-
const invalidJsonContents = (key, path, problem) => new ConfigDirectorValidationError(`Invalid test value for '${key}'${path ? ` at '${path}'` : ""}: ${problem}. JSON contents can hold booleans, finite numbers, strings, null, arrays, and plain objects.`);
|
|
1323
|
-
const isPlainObject = (value) => {
|
|
1324
|
-
if (typeof value !== "object" || value === null) return false;
|
|
1325
|
-
const prototype = Object.getPrototypeOf(value);
|
|
1326
|
-
return prototype === Object.prototype || prototype === null;
|
|
1327
|
-
};
|
|
1328
|
-
const describe = (value) => {
|
|
1329
|
-
if (value === null) return "null";
|
|
1330
|
-
if (typeof value === "function") return "a function";
|
|
1331
|
-
if (typeof value === "object") return `a ${value.constructor?.name ?? "object"} instance`;
|
|
1332
|
-
if (typeof value === "symbol") return "a symbol";
|
|
1333
|
-
return String(value);
|
|
1334
|
-
};
|
|
1335
|
-
//#endregion
|
|
1336
|
-
//#region ../js-client-core/src/testing/values.ts
|
|
1337
|
-
const encodeTestValue = (key, value, revision) => {
|
|
1338
|
-
const encoded = encodeTestValue$1(key, value);
|
|
1339
|
-
return {
|
|
1340
|
-
id: `test-config:${key}`,
|
|
1341
|
-
key,
|
|
1342
|
-
type: encoded.type,
|
|
1343
|
-
value: encoded.value,
|
|
1344
|
-
valueId: `test-value:${key}:${revision}`
|
|
1345
|
-
};
|
|
1346
|
-
};
|
|
1347
|
-
//#endregion
|
|
1348
1594
|
//#region ../js-client-core/src/testing/InMemoryConnection.ts
|
|
1349
1595
|
const TEST_ENVIRONMENT = {
|
|
1350
1596
|
environmentId: "test-environment",
|
|
@@ -1379,7 +1625,7 @@ var InMemoryConnection = class {
|
|
|
1379
1625
|
return [...this.recordedContexts];
|
|
1380
1626
|
}
|
|
1381
1627
|
setValue(key, value) {
|
|
1382
|
-
const configState =
|
|
1628
|
+
const configState = this.encode(key, value);
|
|
1383
1629
|
this.configs.set(key, configState);
|
|
1384
1630
|
this.deliverIfConnected({
|
|
1385
1631
|
...TEST_ENVIRONMENT,
|
|
@@ -1524,18 +1770,16 @@ var InMemoryConnection = class {
|
|
|
1524
1770
|
}
|
|
1525
1771
|
encodeAll(values) {
|
|
1526
1772
|
const encoded = /* @__PURE__ */ new Map();
|
|
1527
|
-
for (const [key, value] of Object.entries(values)) encoded.set(key,
|
|
1773
|
+
for (const [key, value] of Object.entries(values)) encoded.set(key, this.encode(key, value));
|
|
1528
1774
|
return encoded;
|
|
1529
1775
|
}
|
|
1776
|
+
encode(key, value) {
|
|
1777
|
+
return nativeValueConfigState(`test-config:${key}`, key, value, `test-value:${key}:${++this.revision}`);
|
|
1778
|
+
}
|
|
1530
1779
|
};
|
|
1531
1780
|
//#endregion
|
|
1532
1781
|
//#region ../js-client-core/src/testing/TestClient.ts
|
|
1533
1782
|
const TEST_CLIENT_SDK_KEY = "test-client";
|
|
1534
|
-
const noopTelemetryClient = {
|
|
1535
|
-
updateContext: async () => {},
|
|
1536
|
-
evaluatedConfig: () => {},
|
|
1537
|
-
close: async () => {}
|
|
1538
|
-
};
|
|
1539
1783
|
var InMemoryTestClient = class {
|
|
1540
1784
|
constructor(client, connection) {
|
|
1541
1785
|
this.client = client;
|
|
@@ -1575,7 +1819,7 @@ var InMemoryTestClient = class {
|
|
|
1575
1819
|
const createTestClient$1 = (sdkOptions, options) => {
|
|
1576
1820
|
const logger = options?.logger ?? createDefaultLogger();
|
|
1577
1821
|
const connection = new InMemoryConnection(logger, options?.values ?? {});
|
|
1578
|
-
return new InMemoryTestClient(new DefaultConfigDirectorClient(
|
|
1822
|
+
return new InMemoryTestClient(new DefaultConfigDirectorClient(() => discardingTelemetryClient, TEST_CLIENT_SDK_KEY, sdkOptions, {
|
|
1579
1823
|
logger,
|
|
1580
1824
|
connection: { timeout: options?.timeout }
|
|
1581
1825
|
}, { transport: connection }), connection);
|
|
@@ -1638,7 +1882,7 @@ const installTestClient = (testClient) => {
|
|
|
1638
1882
|
const createTestClient = (options) => {
|
|
1639
1883
|
return createTestClient$1({
|
|
1640
1884
|
sdkName: "nuxt-sdk",
|
|
1641
|
-
sdkVersion: "1.
|
|
1885
|
+
sdkVersion: "1.11.0"
|
|
1642
1886
|
}, options);
|
|
1643
1887
|
};
|
|
1644
1888
|
//#endregion
|