@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
|
@@ -393,6 +393,7 @@ var StreamingTransport = class {
|
|
|
393
393
|
eventEmitter = new Emitter();
|
|
394
394
|
url;
|
|
395
395
|
timeoutTimer;
|
|
396
|
+
connectionGeneration = 0;
|
|
396
397
|
constructor(options) {
|
|
397
398
|
this.options = options;
|
|
398
399
|
this.options = options;
|
|
@@ -400,6 +401,9 @@ var StreamingTransport = class {
|
|
|
400
401
|
this.url = options.resolveUrl("client/sse/v1", options.baseUrl);
|
|
401
402
|
}
|
|
402
403
|
async connect(context, timeout) {
|
|
404
|
+
const generation = ++this.connectionGeneration;
|
|
405
|
+
const clientSession = await this.options.clientSession;
|
|
406
|
+
if (generation !== this.connectionGeneration) return this;
|
|
403
407
|
if (this.eventSource) this.close();
|
|
404
408
|
const eventSourcePromise = new Promise((resolve, reject) => {
|
|
405
409
|
this.eventSource = createEventSourceClient({
|
|
@@ -411,7 +415,8 @@ var StreamingTransport = class {
|
|
|
411
415
|
givenContext: context,
|
|
412
416
|
metaContext: this.options.metaContext,
|
|
413
417
|
clientSdkKey: this.options.clientSdkKey,
|
|
414
|
-
instanceId:
|
|
418
|
+
instanceId: clientSession.instanceId,
|
|
419
|
+
ephemeral: clientSession.ephemeral
|
|
415
420
|
}),
|
|
416
421
|
onMessage: ({ data }) => {
|
|
417
422
|
this.dispatchMessage(data);
|
|
@@ -476,6 +481,7 @@ var StreamingTransport = class {
|
|
|
476
481
|
this.eventEmitter.clear();
|
|
477
482
|
}
|
|
478
483
|
close() {
|
|
484
|
+
this.connectionGeneration++;
|
|
479
485
|
this.eventSource?.close();
|
|
480
486
|
clearTimeout(this.timeoutTimer);
|
|
481
487
|
}
|
|
@@ -485,6 +491,183 @@ var StreamingTransport = class {
|
|
|
485
491
|
}
|
|
486
492
|
};
|
|
487
493
|
//#endregion
|
|
494
|
+
//#region ../shared/src/native-values.ts
|
|
495
|
+
const encodeNativeValue = (key, value) => {
|
|
496
|
+
if (key.trim().length == 0) throw new ConfigDirectorValidationError("Invalid config key. The key of a value cannot be empty.");
|
|
497
|
+
return encode(key, value);
|
|
498
|
+
};
|
|
499
|
+
const encode = (key, value) => {
|
|
500
|
+
switch (typeof value) {
|
|
501
|
+
case "boolean": return {
|
|
502
|
+
type: "boolean",
|
|
503
|
+
value: value ? "true" : "false"
|
|
504
|
+
};
|
|
505
|
+
case "number": return encodeNumber(key, value);
|
|
506
|
+
case "string": return {
|
|
507
|
+
type: "string",
|
|
508
|
+
value
|
|
509
|
+
};
|
|
510
|
+
case "object": if (value !== null) {
|
|
511
|
+
assertJsonContents(key, value, "");
|
|
512
|
+
return {
|
|
513
|
+
type: "json",
|
|
514
|
+
value: JSON.stringify(value)
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
throw new ConfigDirectorValidationError(`Invalid value for '${key}': ${describe(value)}. Use a boolean, a number, a string, a plain object, or an array.`);
|
|
519
|
+
};
|
|
520
|
+
const encodeNumber = (key, value) => {
|
|
521
|
+
if (!Number.isFinite(value)) throw new ConfigDirectorValidationError(`Invalid value for '${key}': ${describe(value)} is not a finite number.`);
|
|
522
|
+
if (Number.isInteger(value)) {
|
|
523
|
+
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.`);
|
|
524
|
+
return {
|
|
525
|
+
type: "integer",
|
|
526
|
+
value: String(value)
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
return {
|
|
530
|
+
type: "float",
|
|
531
|
+
value: toPlainDecimal(value)
|
|
532
|
+
};
|
|
533
|
+
};
|
|
534
|
+
const toPlainDecimal = (value) => {
|
|
535
|
+
const text = String(value);
|
|
536
|
+
const exponentIndex = text.indexOf("e-");
|
|
537
|
+
if (exponentIndex < 0) return text;
|
|
538
|
+
const sign = text.startsWith("-") ? "-" : "";
|
|
539
|
+
const [integerDigits = "", fractionDigits = ""] = text.slice(sign.length, exponentIndex).split(".");
|
|
540
|
+
const leadingZeros = Number(text.slice(exponentIndex + 2)) - integerDigits.length;
|
|
541
|
+
return `${sign}0.${"0".repeat(leadingZeros)}${integerDigits}${fractionDigits}`;
|
|
542
|
+
};
|
|
543
|
+
const assertJsonContents = (key, value, path) => {
|
|
544
|
+
if (value === null || typeof value === "boolean" || typeof value === "string") return;
|
|
545
|
+
if (typeof value === "number") {
|
|
546
|
+
if (!Number.isFinite(value)) throw invalidJsonContents(key, path, `${describe(value)} is not a finite number`);
|
|
547
|
+
return;
|
|
548
|
+
}
|
|
549
|
+
if (Array.isArray(value)) {
|
|
550
|
+
value.forEach((item, index) => assertJsonContents(key, item, `${path}[${index}]`));
|
|
551
|
+
return;
|
|
552
|
+
}
|
|
553
|
+
if (isPlainObject$1(value)) {
|
|
554
|
+
for (const [property, item] of Object.entries(value)) assertJsonContents(key, item, path ? `${path}.${property}` : property);
|
|
555
|
+
return;
|
|
556
|
+
}
|
|
557
|
+
throw invalidJsonContents(key, path, `${describe(value)} cannot be encoded as JSON`);
|
|
558
|
+
};
|
|
559
|
+
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.`);
|
|
560
|
+
const isPlainObject$1 = (value) => {
|
|
561
|
+
if (typeof value !== "object" || value === null) return false;
|
|
562
|
+
const prototype = Object.getPrototypeOf(value);
|
|
563
|
+
return prototype === Object.prototype || prototype === null;
|
|
564
|
+
};
|
|
565
|
+
const describe = (value) => {
|
|
566
|
+
if (value === null) return "null";
|
|
567
|
+
if (typeof value === "function") return "a function";
|
|
568
|
+
if (typeof value === "object") return `a ${value.constructor?.name ?? "object"} instance`;
|
|
569
|
+
if (typeof value === "symbol") return "a symbol";
|
|
570
|
+
return String(value);
|
|
571
|
+
};
|
|
572
|
+
//#endregion
|
|
573
|
+
//#region ../js-client-core/src/native-values.ts
|
|
574
|
+
const nativeValueConfigState = (id, key, value, valueId) => {
|
|
575
|
+
const encoded = encodeNativeValue(key, value);
|
|
576
|
+
return {
|
|
577
|
+
id,
|
|
578
|
+
key,
|
|
579
|
+
type: encoded.type,
|
|
580
|
+
value: encoded.value,
|
|
581
|
+
valueId
|
|
582
|
+
};
|
|
583
|
+
};
|
|
584
|
+
//#endregion
|
|
585
|
+
//#region ../js-client-core/src/OfflineTransport.ts
|
|
586
|
+
const OFFLINE_ENVIRONMENT = {
|
|
587
|
+
environmentId: "offline",
|
|
588
|
+
projectId: "offline"
|
|
589
|
+
};
|
|
590
|
+
var OfflineTransport = class {
|
|
591
|
+
emitter = new Emitter();
|
|
592
|
+
constructor(source) {
|
|
593
|
+
this.source = source;
|
|
594
|
+
}
|
|
595
|
+
async connect() {
|
|
596
|
+
const configs = loadOfflineConfigs(this.source);
|
|
597
|
+
this.emitter.emit("configSetReceived", {
|
|
598
|
+
...OFFLINE_ENVIRONMENT,
|
|
599
|
+
kind: "full",
|
|
600
|
+
configs
|
|
601
|
+
});
|
|
602
|
+
return this;
|
|
603
|
+
}
|
|
604
|
+
on = (name, handler) => this.emitter.on(name, handler);
|
|
605
|
+
off = (name, handler) => this.emitter.off(name, handler);
|
|
606
|
+
clear() {
|
|
607
|
+
this.emitter.clear();
|
|
608
|
+
}
|
|
609
|
+
close() {}
|
|
610
|
+
dispose() {
|
|
611
|
+
this.clear();
|
|
612
|
+
}
|
|
613
|
+
};
|
|
614
|
+
const loadOfflineConfigs = (source) => {
|
|
615
|
+
switch (source.kind) {
|
|
616
|
+
case "in-code-defaults": return {};
|
|
617
|
+
case "values": return encodeOfflineValues(source.values);
|
|
618
|
+
case "rejected": throw new ConfigDirectorInitializationError(source.message);
|
|
619
|
+
}
|
|
620
|
+
};
|
|
621
|
+
const encodeOfflineValues = (values) => {
|
|
622
|
+
try {
|
|
623
|
+
return Object.fromEntries(Object.entries(values).map(([key, value]) => [key, nativeValueConfigState(`offline:${key}`, key, value, void 0)]));
|
|
624
|
+
} catch (error) {
|
|
625
|
+
if (error instanceof ConfigDirectorValidationError) throw new ConfigDirectorInitializationError(`The offline values could not be loaded. ${error.message}`);
|
|
626
|
+
throw error;
|
|
627
|
+
}
|
|
628
|
+
};
|
|
629
|
+
//#endregion
|
|
630
|
+
//#region ../shared/src/constants.ts
|
|
631
|
+
const CLIENT_BASE_URL = "https://client-sdk-api.configdirector.com";
|
|
632
|
+
//#endregion
|
|
633
|
+
//#region ../js-client-core/src/connection-plan.ts
|
|
634
|
+
const planConnection = (clientSdkKey, connection, urlFactory) => {
|
|
635
|
+
if (connection?.mode === "offline") return {
|
|
636
|
+
kind: "stay-offline",
|
|
637
|
+
source: offlineSourceOf(connection)
|
|
638
|
+
};
|
|
639
|
+
const mode = connection?.mode ?? "streaming";
|
|
640
|
+
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");
|
|
641
|
+
if (connection?.values !== void 0) return {
|
|
642
|
+
kind: "stay-offline",
|
|
643
|
+
source: {
|
|
644
|
+
kind: "rejected",
|
|
645
|
+
message: `The connection option 'values' applies only when the connection mode is 'offline', but the mode is '${mode}'.`
|
|
646
|
+
}
|
|
647
|
+
};
|
|
648
|
+
return {
|
|
649
|
+
kind: "connect",
|
|
650
|
+
mode,
|
|
651
|
+
clientSdkKey,
|
|
652
|
+
baseUrl: baseUrlOf(connection?.url, urlFactory)
|
|
653
|
+
};
|
|
654
|
+
};
|
|
655
|
+
const baseUrlOf = (url, urlFactory) => {
|
|
656
|
+
if (!url) return CLIENT_BASE_URL;
|
|
657
|
+
try {
|
|
658
|
+
return urlFactory(url);
|
|
659
|
+
} catch (error) {
|
|
660
|
+
throw new ConfigDirectorValidationError(`Invalid base URL '${url}'. Parsing failed: ${error}`);
|
|
661
|
+
}
|
|
662
|
+
};
|
|
663
|
+
const offlineSourceOf = (connection) => {
|
|
664
|
+
if (connection.values !== void 0) return {
|
|
665
|
+
kind: "values",
|
|
666
|
+
values: connection.values
|
|
667
|
+
};
|
|
668
|
+
return { kind: "in-code-defaults" };
|
|
669
|
+
};
|
|
670
|
+
//#endregion
|
|
488
671
|
//#region ../shared/src/value-parser.ts
|
|
489
672
|
const getRequestedType = (defaultValue) => {
|
|
490
673
|
const baseType = typeof defaultValue;
|
|
@@ -704,16 +887,16 @@ const createDefaultLogger$1 = (level, messageDecorator) => {
|
|
|
704
887
|
return new DefaultConsoleLogger(level ?? "warn", messageDecorator ?? new LogMessageDecorator());
|
|
705
888
|
};
|
|
706
889
|
//#endregion
|
|
890
|
+
//#region ../js-client-core/src/telemetry/discarding.ts
|
|
891
|
+
const discardingTelemetryClient = {
|
|
892
|
+
updateContext: async () => {},
|
|
893
|
+
evaluatedConfig: () => {},
|
|
894
|
+
recordInvalidContext: () => {},
|
|
895
|
+
close: async () => {}
|
|
896
|
+
};
|
|
897
|
+
//#endregion
|
|
707
898
|
//#region ../shared/src/url.ts
|
|
708
899
|
const defaultUrlFactory = (input, base) => new URL(input, base?.toString());
|
|
709
|
-
const parseUrl = (url) => {
|
|
710
|
-
if (!url) return;
|
|
711
|
-
try {
|
|
712
|
-
return new URL(url);
|
|
713
|
-
} catch (error) {
|
|
714
|
-
throw new ConfigDirectorValidationError(`Invalid URL '${url}'. Parsing failed: ${error}`);
|
|
715
|
-
}
|
|
716
|
-
};
|
|
717
900
|
//#endregion
|
|
718
901
|
//#region ../shared/src/transport/pollingInterval.ts
|
|
719
902
|
const resolvePollingInterval = (configuredSeconds, defaults) => {
|
|
@@ -740,10 +923,11 @@ const generateInstanceId = () => {
|
|
|
740
923
|
});
|
|
741
924
|
};
|
|
742
925
|
//#endregion
|
|
743
|
-
//#region ../js-client-core/src/
|
|
926
|
+
//#region ../js-client-core/src/client-session.ts
|
|
744
927
|
const STORAGE_KEY = "configdirector-sdk:instance-id";
|
|
745
928
|
const RECORD_VERSION = 1;
|
|
746
929
|
const MAX_AGE_MS = 360 * 60 * 1e3;
|
|
930
|
+
const STORAGE_DEADLINE_MS = 1e3;
|
|
747
931
|
const CHECKSUM_SALT = "configdirector.instanceId.v1";
|
|
748
932
|
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}$/;
|
|
749
933
|
const fnv1a = (input, offsetBasis) => {
|
|
@@ -759,7 +943,7 @@ const checksumFor = (id, createdAt) => {
|
|
|
759
943
|
const low = fnv1a(payload, 2166136261);
|
|
760
944
|
return fnv1a(`${low}|${payload}`, 16777619).toString(16).padStart(8, "0") + low.toString(16).padStart(8, "0");
|
|
761
945
|
};
|
|
762
|
-
const
|
|
946
|
+
const localStorageIfAvailable = () => {
|
|
763
947
|
try {
|
|
764
948
|
return globalThis.localStorage ?? void 0;
|
|
765
949
|
} catch {
|
|
@@ -774,34 +958,37 @@ const isUsableRecord = (record) => {
|
|
|
774
958
|
if (!UUID_V4_PATTERN.test(id)) return false;
|
|
775
959
|
return checksum === checksumFor(id, createdAt);
|
|
776
960
|
};
|
|
777
|
-
const readStoredInstanceId = (storage, logger) => {
|
|
961
|
+
const readStoredInstanceId = async (storage, logger) => {
|
|
778
962
|
let raw;
|
|
779
963
|
try {
|
|
780
|
-
raw = storage.getItem(STORAGE_KEY);
|
|
964
|
+
raw = await storage.getItem(STORAGE_KEY);
|
|
781
965
|
} catch (error) {
|
|
782
|
-
logger
|
|
783
|
-
return;
|
|
966
|
+
logger.debug("[ConfigDirectorClient] Could not read the stored instance id, using an ephemeral one", error);
|
|
967
|
+
return { outcome: "failed" };
|
|
784
968
|
}
|
|
785
|
-
if (
|
|
969
|
+
if (typeof raw !== "string" || raw === "") return { outcome: "nothing usable" };
|
|
786
970
|
let record;
|
|
787
971
|
try {
|
|
788
972
|
record = JSON.parse(raw);
|
|
789
973
|
} catch {
|
|
790
|
-
logger
|
|
791
|
-
return;
|
|
974
|
+
logger.debug("[ConfigDirectorClient] The stored instance id is not valid JSON, generating a new one");
|
|
975
|
+
return { outcome: "nothing usable" };
|
|
792
976
|
}
|
|
793
977
|
if (!isUsableRecord(record)) {
|
|
794
|
-
logger
|
|
795
|
-
return;
|
|
978
|
+
logger.debug("[ConfigDirectorClient] The stored instance id failed validation, generating a new one");
|
|
979
|
+
return { outcome: "nothing usable" };
|
|
796
980
|
}
|
|
797
981
|
const age = Date.now() - record.createdAt;
|
|
798
982
|
if (age < 0 || age >= MAX_AGE_MS) {
|
|
799
|
-
logger
|
|
800
|
-
return;
|
|
983
|
+
logger.debug("[ConfigDirectorClient] The stored instance id has expired, generating a new one");
|
|
984
|
+
return { outcome: "nothing usable" };
|
|
801
985
|
}
|
|
802
|
-
return
|
|
986
|
+
return {
|
|
987
|
+
outcome: "stored",
|
|
988
|
+
instanceId: record.id
|
|
989
|
+
};
|
|
803
990
|
};
|
|
804
|
-
const
|
|
991
|
+
const persistInstanceId = async (storage, id, logger) => {
|
|
805
992
|
const createdAt = Date.now();
|
|
806
993
|
const record = {
|
|
807
994
|
v: RECORD_VERSION,
|
|
@@ -810,19 +997,54 @@ const storeInstanceId = (storage, id, logger) => {
|
|
|
810
997
|
checksum: checksumFor(id, createdAt)
|
|
811
998
|
};
|
|
812
999
|
try {
|
|
813
|
-
storage.setItem(STORAGE_KEY, JSON.stringify(record));
|
|
1000
|
+
await storage.setItem(STORAGE_KEY, JSON.stringify(record));
|
|
1001
|
+
return true;
|
|
814
1002
|
} catch (error) {
|
|
815
|
-
logger
|
|
1003
|
+
logger.debug("[ConfigDirectorClient] Could not persist the instance id, using it as an ephemeral one", error);
|
|
1004
|
+
return false;
|
|
1005
|
+
}
|
|
1006
|
+
};
|
|
1007
|
+
const settleFromStorage = async (storage, provisionalId, deadline, logger) => {
|
|
1008
|
+
const read = await readStoredInstanceId(storage, logger);
|
|
1009
|
+
switch (read.outcome) {
|
|
1010
|
+
case "stored": return { instanceId: read.instanceId };
|
|
1011
|
+
case "failed": return {
|
|
1012
|
+
instanceId: provisionalId,
|
|
1013
|
+
ephemeral: true
|
|
1014
|
+
};
|
|
1015
|
+
case "nothing usable": break;
|
|
816
1016
|
}
|
|
1017
|
+
if (deadline.passed) return {
|
|
1018
|
+
instanceId: provisionalId,
|
|
1019
|
+
ephemeral: true
|
|
1020
|
+
};
|
|
1021
|
+
return await persistInstanceId(storage, provisionalId, logger) ? { instanceId: provisionalId } : {
|
|
1022
|
+
instanceId: provisionalId,
|
|
1023
|
+
ephemeral: true
|
|
1024
|
+
};
|
|
817
1025
|
};
|
|
818
|
-
const
|
|
819
|
-
const
|
|
820
|
-
if (!storage)
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
1026
|
+
const resolveClientSession = (logger, storage = localStorageIfAvailable()) => {
|
|
1027
|
+
const provisionalId = generateInstanceId();
|
|
1028
|
+
if (!storage) {
|
|
1029
|
+
logger.debug("[ConfigDirectorClient] No instance id storage is available, using an ephemeral instance id");
|
|
1030
|
+
return Promise.resolve({
|
|
1031
|
+
instanceId: provisionalId,
|
|
1032
|
+
ephemeral: true
|
|
1033
|
+
});
|
|
1034
|
+
}
|
|
1035
|
+
const deadline = { passed: false };
|
|
1036
|
+
let deadlineTimer;
|
|
1037
|
+
const fallbackAtDeadline = new Promise((resolve) => {
|
|
1038
|
+
deadlineTimer = setTimeout(() => {
|
|
1039
|
+
deadline.passed = true;
|
|
1040
|
+
logger.warn(`[ConfigDirectorClient] The instance id storage did not respond within ${STORAGE_DEADLINE_MS}ms, using an ephemeral instance id`);
|
|
1041
|
+
resolve({
|
|
1042
|
+
instanceId: provisionalId,
|
|
1043
|
+
ephemeral: true
|
|
1044
|
+
});
|
|
1045
|
+
}, STORAGE_DEADLINE_MS);
|
|
1046
|
+
});
|
|
1047
|
+
return Promise.race([settleFromStorage(storage, provisionalId, deadline, logger), fallbackAtDeadline]).finally(() => clearTimeout(deadlineTimer));
|
|
826
1048
|
};
|
|
827
1049
|
//#endregion
|
|
828
1050
|
//#region ../shared/src/transport/AbstractPollingTransport.ts
|
|
@@ -903,6 +1125,7 @@ var PollingTransport = class extends AbstractPollingTransport {
|
|
|
903
1125
|
eventEmitter = new Emitter();
|
|
904
1126
|
url;
|
|
905
1127
|
lastUpdateTimestamp;
|
|
1128
|
+
connectionGeneration = 0;
|
|
906
1129
|
constructor(options) {
|
|
907
1130
|
super();
|
|
908
1131
|
this.options = options;
|
|
@@ -912,19 +1135,26 @@ var PollingTransport = class extends AbstractPollingTransport {
|
|
|
912
1135
|
this.pollingIntervalSeconds = options.pollingInterval ?? 60;
|
|
913
1136
|
}
|
|
914
1137
|
async connect(context, timeout) {
|
|
1138
|
+
const generation = ++this.connectionGeneration;
|
|
915
1139
|
this.clearPollingInterval();
|
|
1140
|
+
const clientSession = await this.options.clientSession;
|
|
1141
|
+
if (generation !== this.connectionGeneration) return this;
|
|
916
1142
|
try {
|
|
917
|
-
await this.fetchConfigs(context, timeout);
|
|
1143
|
+
await this.fetchConfigs(context, timeout, clientSession);
|
|
918
1144
|
} finally {
|
|
919
1145
|
if (!this.fatalError) this.schedulePollingInterval(() => {
|
|
920
|
-
this.fetchConfigs(context, timeout).catch((error) => {
|
|
1146
|
+
this.fetchConfigs(context, timeout, clientSession).catch((error) => {
|
|
921
1147
|
this.logger.error("[PollingTransport] Error during polling:", error);
|
|
922
1148
|
});
|
|
923
1149
|
});
|
|
924
1150
|
}
|
|
925
1151
|
return this;
|
|
926
1152
|
}
|
|
927
|
-
|
|
1153
|
+
close() {
|
|
1154
|
+
this.connectionGeneration++;
|
|
1155
|
+
super.close();
|
|
1156
|
+
}
|
|
1157
|
+
async fetchConfigs(context, timeout, clientSession) {
|
|
928
1158
|
if (this.fatalError) {
|
|
929
1159
|
this.logger.warn("[PollingTransport] There was a prior unrecoverable error. Ignoring attempt to reconnect.");
|
|
930
1160
|
return;
|
|
@@ -937,7 +1167,8 @@ var PollingTransport = class extends AbstractPollingTransport {
|
|
|
937
1167
|
givenContext: context,
|
|
938
1168
|
metaContext: this.options.metaContext,
|
|
939
1169
|
clientSdkKey: this.options.clientSdkKey,
|
|
940
|
-
instanceId:
|
|
1170
|
+
instanceId: clientSession.instanceId,
|
|
1171
|
+
ephemeral: clientSession.ephemeral,
|
|
941
1172
|
lastUpdateTimestamp: this.lastUpdateTimestamp
|
|
942
1173
|
})
|
|
943
1174
|
}, this.logger);
|
|
@@ -978,9 +1209,53 @@ const read = (readValue) => {
|
|
|
978
1209
|
const readUserAgent = () => read((globals) => globals.navigator?.userAgent);
|
|
979
1210
|
const readHost = () => read((globals) => globals.location?.host);
|
|
980
1211
|
//#endregion
|
|
1212
|
+
//#region ../shared/src/context-kind.ts
|
|
1213
|
+
const CONTEXT_KIND_MAXIMUM_LENGTH = 64;
|
|
1214
|
+
const KIND_SAMPLE_MAXIMUM_CHARACTERS = 128;
|
|
1215
|
+
const isContextKindCharacter = (character) => character >= "a" && character <= "z" || character >= "0" && character <= "9" || character === "-" || character === "_";
|
|
1216
|
+
const isValidContextKind = (kind) => {
|
|
1217
|
+
if (kind.length < 1 || kind.length > CONTEXT_KIND_MAXIMUM_LENGTH) return false;
|
|
1218
|
+
for (const character of kind) if (!isContextKindCharacter(character)) return false;
|
|
1219
|
+
return true;
|
|
1220
|
+
};
|
|
1221
|
+
const isAcceptedKind = (kind) => kind === void 0 || kind === null || typeof kind === "string" && isValidContextKind(kind);
|
|
1222
|
+
const stringFormOf = (kind) => {
|
|
1223
|
+
if (typeof kind === "string") return kind;
|
|
1224
|
+
try {
|
|
1225
|
+
return String(kind);
|
|
1226
|
+
} catch {
|
|
1227
|
+
return Object.prototype.toString.call(kind);
|
|
1228
|
+
}
|
|
1229
|
+
};
|
|
1230
|
+
const firstCharactersOf = (text, maximumCharacters) => {
|
|
1231
|
+
let sample = "";
|
|
1232
|
+
let characterCount = 0;
|
|
1233
|
+
for (const character of text) {
|
|
1234
|
+
if (characterCount === maximumCharacters) break;
|
|
1235
|
+
sample += character;
|
|
1236
|
+
characterCount += 1;
|
|
1237
|
+
}
|
|
1238
|
+
return sample;
|
|
1239
|
+
};
|
|
1240
|
+
const admittedContextOrNone = (context) => {
|
|
1241
|
+
const admission = admitContext(context);
|
|
1242
|
+
return admission.outcome === "admitted" ? admission.context : void 0;
|
|
1243
|
+
};
|
|
1244
|
+
const admitContext = (context) => {
|
|
1245
|
+
const kind = context?.kind;
|
|
1246
|
+
if (isAcceptedKind(kind)) return {
|
|
1247
|
+
outcome: "admitted",
|
|
1248
|
+
context
|
|
1249
|
+
};
|
|
1250
|
+
return {
|
|
1251
|
+
outcome: "ignored",
|
|
1252
|
+
kindSample: firstCharactersOf(stringFormOf(kind), KIND_SAMPLE_MAXIMUM_CHARACTERS)
|
|
1253
|
+
};
|
|
1254
|
+
};
|
|
1255
|
+
//#endregion
|
|
981
1256
|
//#region ../js-client-core/src/DefaultConfigDirectorClient.ts
|
|
982
1257
|
const MAX_EXPONENTIAL_DELAY = 9;
|
|
983
|
-
var DefaultConfigDirectorClient = class {
|
|
1258
|
+
var DefaultConfigDirectorClient = class DefaultConfigDirectorClient {
|
|
984
1259
|
logger;
|
|
985
1260
|
telemetryClient;
|
|
986
1261
|
configSet;
|
|
@@ -995,18 +1270,20 @@ var DefaultConfigDirectorClient = class {
|
|
|
995
1270
|
readyPromise;
|
|
996
1271
|
readyResolve;
|
|
997
1272
|
currentContext;
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1273
|
+
connectionPlan;
|
|
1274
|
+
clientSession;
|
|
1275
|
+
static from(liveTelemetryClient, clientSdkKeyOrOptions, sdkOptions, clientOptions) {
|
|
1276
|
+
if (typeof clientSdkKeyOrOptions === "object") return new DefaultConfigDirectorClient(liveTelemetryClient, void 0, sdkOptions, clientSdkKeyOrOptions);
|
|
1277
|
+
return new DefaultConfigDirectorClient(liveTelemetryClient, clientSdkKeyOrOptions, sdkOptions, clientOptions);
|
|
1278
|
+
}
|
|
1279
|
+
constructor(liveTelemetryClient, clientSdkKey, sdkOptions, clientOptions, internalClientOptions) {
|
|
1001
1280
|
this.logger = clientOptions?.logger ?? createDefaultLogger$1();
|
|
1002
|
-
this.
|
|
1281
|
+
this.clientSession = resolveClientSession(this.logger, internalClientOptions?.instanceIdStorage);
|
|
1003
1282
|
this.timeout = clientOptions?.connection?.timeout ?? 3e3;
|
|
1004
|
-
this.validateSdkKeyPresence(clientSdkKey);
|
|
1005
1283
|
const urlFactory = internalClientOptions?.urlFactory ?? defaultUrlFactory;
|
|
1006
|
-
|
|
1007
|
-
this.
|
|
1008
|
-
this.
|
|
1009
|
-
this.transport = internalClientOptions?.transport ?? this.buildTransport(clientSdkKey, sdkOptions, baseUrl, urlFactory, clientOptions, internalClientOptions);
|
|
1284
|
+
this.connectionPlan = planConnection(clientSdkKey, clientOptions?.connection, urlFactory);
|
|
1285
|
+
this.telemetryClient = this.buildTelemetryClient(liveTelemetryClient, clientOptions);
|
|
1286
|
+
this.transport = internalClientOptions?.transport ?? this.buildTransport(sdkOptions, clientOptions, urlFactory, internalClientOptions);
|
|
1010
1287
|
this.transport.on("configSetReceived", (configSet) => {
|
|
1011
1288
|
this.hasReceivedConfigSet = true;
|
|
1012
1289
|
this.readyResolve?.();
|
|
@@ -1043,8 +1320,8 @@ var DefaultConfigDirectorClient = class {
|
|
|
1043
1320
|
const handlers = Array.isArray(handler) ? handler : [handler];
|
|
1044
1321
|
for (const h of handlers) if (typeof h === "function") this.on(event, h);
|
|
1045
1322
|
}
|
|
1046
|
-
|
|
1047
|
-
if (
|
|
1323
|
+
pollingIntervalFor(mode, configuredSeconds) {
|
|
1324
|
+
if (mode !== "polling") return;
|
|
1048
1325
|
const resolved = resolvePollingInterval(configuredSeconds, {
|
|
1049
1326
|
defaultSeconds: 60,
|
|
1050
1327
|
minimumSeconds: 30
|
|
@@ -1052,39 +1329,79 @@ var DefaultConfigDirectorClient = class {
|
|
|
1052
1329
|
if (resolved.warning) this.logger.warn(`[ConfigDirectorClient] ${resolved.warning}`);
|
|
1053
1330
|
return resolved.seconds;
|
|
1054
1331
|
}
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1332
|
+
buildTelemetryClient(liveTelemetryClient, clientOptions) {
|
|
1333
|
+
const plan = this.connectionPlan;
|
|
1334
|
+
switch (plan.kind) {
|
|
1335
|
+
case "stay-offline": return discardingTelemetryClient;
|
|
1336
|
+
case "connect": return liveTelemetryClient({
|
|
1337
|
+
clientSdkKey: plan.clientSdkKey,
|
|
1338
|
+
baseUrl: plan.baseUrl,
|
|
1339
|
+
logger: this.logger,
|
|
1340
|
+
metadata: clientOptions?.metadata
|
|
1341
|
+
});
|
|
1342
|
+
}
|
|
1343
|
+
}
|
|
1344
|
+
buildTransport(sdkOptions, clientOptions, urlFactory, internalClientOptions) {
|
|
1345
|
+
const plan = this.connectionPlan;
|
|
1346
|
+
switch (plan.kind) {
|
|
1347
|
+
case "stay-offline": return new OfflineTransport(plan.source);
|
|
1348
|
+
case "connect": return new (this.getTransportConstructor(plan.mode))({
|
|
1349
|
+
clientSession: this.clientSession,
|
|
1350
|
+
clientSdkKey: plan.clientSdkKey,
|
|
1351
|
+
baseUrl: plan.baseUrl,
|
|
1352
|
+
resolveUrl: urlFactory,
|
|
1353
|
+
metaContext: {
|
|
1354
|
+
...clientOptions?.metadata,
|
|
1355
|
+
sdkName: sdkOptions.sdkName,
|
|
1356
|
+
sdkVersion: sdkOptions.sdkVersion,
|
|
1357
|
+
userAgent: readUserAgent(),
|
|
1358
|
+
host: readHost()
|
|
1359
|
+
},
|
|
1360
|
+
logger: this.logger,
|
|
1361
|
+
fetch: internalClientOptions?.fetch,
|
|
1362
|
+
connectionRetryDelay: internalClientOptions?.connectionRetryDelay ?? ((attempt) => {
|
|
1363
|
+
return Math.pow(2, Math.min(attempt, MAX_EXPONENTIAL_DELAY)) * 1e3;
|
|
1364
|
+
}),
|
|
1365
|
+
pollingInterval: this.pollingIntervalFor(plan.mode, clientOptions?.connection?.pollingInterval)
|
|
1366
|
+
});
|
|
1367
|
+
}
|
|
1075
1368
|
}
|
|
1076
|
-
getTransportConstructor(
|
|
1077
|
-
switch (
|
|
1369
|
+
getTransportConstructor(mode) {
|
|
1370
|
+
switch (mode) {
|
|
1078
1371
|
case "polling": return PollingTransport;
|
|
1079
|
-
|
|
1372
|
+
case "streaming": return StreamingTransport;
|
|
1080
1373
|
}
|
|
1081
1374
|
}
|
|
1082
1375
|
async initialize(context) {
|
|
1083
1376
|
if (!this.hasReceivedConfigSet) this.initializing = true;
|
|
1084
|
-
await this.
|
|
1377
|
+
await this.submitContext(context, "initialization");
|
|
1085
1378
|
}
|
|
1086
1379
|
async updateContext(context) {
|
|
1087
|
-
|
|
1380
|
+
switch (this.connectionPlan.kind) {
|
|
1381
|
+
case "connect":
|
|
1382
|
+
await this.submitContext(context, "context update");
|
|
1383
|
+
return;
|
|
1384
|
+
case "stay-offline":
|
|
1385
|
+
this.storeContext(this.admittedContextFor(context, "context update"));
|
|
1386
|
+
return;
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
storeContext(context) {
|
|
1390
|
+
this.currentContext = context;
|
|
1391
|
+
this.emit("contextUpdated", { context });
|
|
1392
|
+
}
|
|
1393
|
+
async submitContext(context, submission) {
|
|
1394
|
+
await this.connectToTransport(this.admittedContextFor(context, submission), submission);
|
|
1395
|
+
}
|
|
1396
|
+
admittedContextFor(suppliedContext, submission) {
|
|
1397
|
+
const admission = admitContext(suppliedContext);
|
|
1398
|
+
switch (admission.outcome) {
|
|
1399
|
+
case "admitted": return admission.context;
|
|
1400
|
+
case "ignored":
|
|
1401
|
+
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 '_'.`);
|
|
1402
|
+
this.telemetryClient.recordInvalidContext(admission.kindSample);
|
|
1403
|
+
return;
|
|
1404
|
+
}
|
|
1088
1405
|
}
|
|
1089
1406
|
get context() {
|
|
1090
1407
|
return this.currentContext;
|
|
@@ -1113,11 +1430,21 @@ var DefaultConfigDirectorClient = class {
|
|
|
1113
1430
|
if (remainingTimeout > 0) await Promise.race([this.readyPromise, new Promise((resolve) => {
|
|
1114
1431
|
this.timeoutTimer = setTimeout(() => resolve(), remainingTimeout);
|
|
1115
1432
|
})]).finally(() => clearTimeout(this.timeoutTimer));
|
|
1116
|
-
if (!this.ready) this.logger.warn(`[ConfigDirectorClient] Timed out waiting for ${caller} after ${this.timeout}ms.
|
|
1433
|
+
if (!this.ready) this.logger.warn(`[ConfigDirectorClient] Timed out waiting for ${caller} after ${this.timeout}ms. ${this.timeoutWarningDetails()}`);
|
|
1117
1434
|
} catch (error) {
|
|
1435
|
+
if (error instanceof ConfigDirectorInitializationError) {
|
|
1436
|
+
this.initializing = false;
|
|
1437
|
+
throw error;
|
|
1438
|
+
}
|
|
1118
1439
|
this.logger.error(`[ConfigDirectorClient] An error occurred during ${caller}: `, error);
|
|
1119
1440
|
}
|
|
1120
1441
|
}
|
|
1442
|
+
timeoutWarningDetails() {
|
|
1443
|
+
switch (this.connectionPlan.kind) {
|
|
1444
|
+
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.";
|
|
1445
|
+
case "stay-offline": return "The offline values were not delivered, so configs return the default value.";
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
1121
1448
|
findKeysRemovedBy(configSet) {
|
|
1122
1449
|
if (!this.configSet || configSet.kind != "full") return [];
|
|
1123
1450
|
return Object.keys(this.configSet.configs).filter((key) => !Object.prototype.hasOwnProperty.call(configSet.configs, key));
|
|
@@ -1212,17 +1539,6 @@ var DefaultConfigDirectorClient = class {
|
|
|
1212
1539
|
this.logger.error(`[ConfigDirectorClient] Error executing event handlers for '${name}'`, error);
|
|
1213
1540
|
}
|
|
1214
1541
|
}
|
|
1215
|
-
parseUrl(url, urlFactory) {
|
|
1216
|
-
if (!url) return;
|
|
1217
|
-
try {
|
|
1218
|
-
return urlFactory(url);
|
|
1219
|
-
} catch (error) {
|
|
1220
|
-
throw new ConfigDirectorValidationError(`Invalid base URL '${url}'. Parsing failed: ${error}`);
|
|
1221
|
-
}
|
|
1222
|
-
}
|
|
1223
|
-
validateSdkKeyPresence(sdkKey) {
|
|
1224
|
-
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");
|
|
1225
|
-
}
|
|
1226
1542
|
validateDefaultValue(defaultValue) {
|
|
1227
1543
|
if (defaultValue === void 0 || defaultValue === null) throw new ConfigDirectorValidationError("Invalid default value. The default value for a config must be defined and non-null.");
|
|
1228
1544
|
if (typeof defaultValue === "function") throw new ConfigDirectorValidationError("Invalid default value. The default value for a config cannot be a function.");
|
|
@@ -1248,14 +1564,24 @@ var DefaultConfigDirectorClient = class {
|
|
|
1248
1564
|
this.handlersMap.clear();
|
|
1249
1565
|
}
|
|
1250
1566
|
pauseNetwork() {
|
|
1251
|
-
this.
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1567
|
+
switch (this.connectionPlan.kind) {
|
|
1568
|
+
case "stay-offline": return;
|
|
1569
|
+
case "connect":
|
|
1570
|
+
this.logger.debug("[ConfigDirectorClient] pauseNetwork() called, pausing transport connection");
|
|
1571
|
+
clearTimeout(this.timeoutTimer);
|
|
1572
|
+
this.timeoutTimer = void 0;
|
|
1573
|
+
this.transport.close();
|
|
1574
|
+
this.ready = false;
|
|
1575
|
+
return;
|
|
1576
|
+
}
|
|
1256
1577
|
}
|
|
1257
1578
|
async resumeNetwork() {
|
|
1258
|
-
|
|
1579
|
+
switch (this.connectionPlan.kind) {
|
|
1580
|
+
case "stay-offline": return;
|
|
1581
|
+
case "connect":
|
|
1582
|
+
await this.connectToTransport(this.currentContext, "network resume");
|
|
1583
|
+
return;
|
|
1584
|
+
}
|
|
1259
1585
|
}
|
|
1260
1586
|
async close() {
|
|
1261
1587
|
this.logger.debug("[ConfigDirectorClient] close() has been called, closing connection to server");
|
|
@@ -1296,6 +1622,59 @@ const mapToTelemetryValue = (ev) => {
|
|
|
1296
1622
|
if (stringValue && stringValue.length <= CONFIG_VALUE_MAX_LENGTH) return { value: stringValue };
|
|
1297
1623
|
return ev.valueId ? { valueId: ev.valueId } : { value: stringValue };
|
|
1298
1624
|
};
|
|
1625
|
+
var InvalidContextSubmissions = class {
|
|
1626
|
+
countsByKindSample = /* @__PURE__ */ new Map();
|
|
1627
|
+
countBeyondSampleLimit = 0;
|
|
1628
|
+
startTime;
|
|
1629
|
+
record(kindSample) {
|
|
1630
|
+
this.startTime ??= /* @__PURE__ */ new Date();
|
|
1631
|
+
return this.add(kindSample, 1);
|
|
1632
|
+
}
|
|
1633
|
+
absorb(entries) {
|
|
1634
|
+
for (const entry of entries) {
|
|
1635
|
+
if (this.startTime === void 0 || entry.startTime < this.startTime) this.startTime = entry.startTime;
|
|
1636
|
+
this.add(entry.event.kindSample, entry.count);
|
|
1637
|
+
}
|
|
1638
|
+
}
|
|
1639
|
+
add(kindSample, count) {
|
|
1640
|
+
if (kindSample !== void 0) {
|
|
1641
|
+
const knownCount = this.countsByKindSample.get(kindSample);
|
|
1642
|
+
if (knownCount !== void 0) {
|
|
1643
|
+
this.countsByKindSample.set(kindSample, knownCount + count);
|
|
1644
|
+
return "known-kind-sample";
|
|
1645
|
+
}
|
|
1646
|
+
if (this.countsByKindSample.size < 20) {
|
|
1647
|
+
this.countsByKindSample.set(kindSample, count);
|
|
1648
|
+
return "new-kind-sample";
|
|
1649
|
+
}
|
|
1650
|
+
}
|
|
1651
|
+
const firstBeyondSampleLimit = this.countBeyondSampleLimit === 0;
|
|
1652
|
+
this.countBeyondSampleLimit += count;
|
|
1653
|
+
return firstBeyondSampleLimit ? "first-beyond-sample-limit" : "beyond-sample-limit";
|
|
1654
|
+
}
|
|
1655
|
+
takeSnapshot() {
|
|
1656
|
+
const endTime = /* @__PURE__ */ new Date();
|
|
1657
|
+
const startTime = this.startTime ?? endTime;
|
|
1658
|
+
const entryOf = (count, kindSample) => ({
|
|
1659
|
+
startTime,
|
|
1660
|
+
endTime,
|
|
1661
|
+
count,
|
|
1662
|
+
event: {
|
|
1663
|
+
reason: "invalid-context-kind",
|
|
1664
|
+
kindSample
|
|
1665
|
+
}
|
|
1666
|
+
});
|
|
1667
|
+
const sampledEntries = Array.from(this.countsByKindSample, ([kindSample, count]) => entryOf(count, kindSample));
|
|
1668
|
+
const entries = this.countBeyondSampleLimit > 0 ? [...sampledEntries, entryOf(this.countBeyondSampleLimit)] : sampledEntries;
|
|
1669
|
+
this.clear();
|
|
1670
|
+
return entries;
|
|
1671
|
+
}
|
|
1672
|
+
clear() {
|
|
1673
|
+
this.countsByKindSample.clear();
|
|
1674
|
+
this.countBeyondSampleLimit = 0;
|
|
1675
|
+
this.startTime = void 0;
|
|
1676
|
+
}
|
|
1677
|
+
};
|
|
1299
1678
|
//#endregion
|
|
1300
1679
|
//#region ../js-browser-client/src/telemetry/WebWorkerTelemetryClient.ts
|
|
1301
1680
|
const DEFAULT_WORKER_CLOSE_TIMEOUT = 6e3;
|
|
@@ -1306,6 +1685,7 @@ var WebWorkerTelemetryClient = class {
|
|
|
1306
1685
|
turnedOff = false;
|
|
1307
1686
|
pendingEvaluations = [];
|
|
1308
1687
|
pendingEvaluationLimit;
|
|
1688
|
+
pendingInvalidContextSubmissions = new InvalidContextSubmissions();
|
|
1309
1689
|
closePromise;
|
|
1310
1690
|
closeResolve;
|
|
1311
1691
|
closeTimer;
|
|
@@ -1337,7 +1717,7 @@ var WebWorkerTelemetryClient = class {
|
|
|
1337
1717
|
return;
|
|
1338
1718
|
}
|
|
1339
1719
|
try {
|
|
1340
|
-
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 i={clientSdkKey:this.sdkKey,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(i))return{success:!0,fatalError:!1};let a=await this.sendReport(i);return a.fatalError&&(this.executeRequests=!1),a}isReportEmpty(e){return t(e.discreteEvents)&&t(e.aggregatedEvents)&&n(e.droppedEvents)}async sendReport(e){try{let t=await r(5e3,this.url,{method:`POST`,body:JSON.stringify(e),keepalive:!0},this.logger),n=this.isStatusFatal(t.status);return n&&this.logger.warn(`[EventReporter] Received a fatal status response (${t.status}) from the telemetry endpoint. No more telemetry data will be sent.`),{success:t.ok,fatalError:n}}catch(e){let t=i(e);return this.logger.warn(`[EventReporter] Error attempting to send telemetry data: ${e}`,{error:e}),{success:!1,fatalError:t}}}isStatusFatal(e){return!!e&&e>=400&&e<500&&e!=429}},o=class{aggregate(e){if(e.events.length==0)return[];let t=new Map;for(let n of e.events){let e=JSON.stringify(n),r=t.get(e)?.count||0;t.set(e,{event:n,count:r+1})}return Array.from(t).map(([,t])=>({startTime:e.startTime,endTime:e.endTime,count:t.count,event:t.event}))}},s=class{constructor(e){this._events=[],this._droppedEventCount=0,this.limit=e??1e3}get events(){return this._events}get reachedLimit(){return this._events.length>=this.limit}get droppedEventCount(){return this._droppedEventCount}push(...e){this.startTime||=new Date;let t=Math.max(0,e.length-this.limit),n=e.slice(t,e.length);if(this._droppedEventCount+=t,this._events.length+n.length>this.limit){let e=this._events.length+n.length-this.limit;this._events.splice(0,e),this._droppedEventCount+=e}return this._events.push(...n)}takeSnapshot(){let e=new Date,t=this.startTime??e,n=this._events.splice(0),r=this._droppedEventCount;return this.startTime=void 0,this._droppedEventCount=0,{startTime:t,endTime:e,events:n,droppedCount:r}}clear(){this._events=[],this.startTime=void 0,this._droppedEventCount=0}},c=class{constructor(e){this.aggregator=new o,this.collectEvents=!0,this.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 extends c{constructor(e){super(e),this.reporter=new a(e),this.evaluationEventSnapshotPreprocessor=new l(e.valueIdGenerator)}async updateContext(e){this.cancelScheduledFlush(),await this.flush(),this._context=e,await this.flushAndScheduleNext()}evaluatedConfig(e){this.collectEvents&&this.evaluationEventQueue.push(e)}async forceFlush(){this.cancelScheduledFlush(),await this.flushAndScheduleNext()}async flush(){if(!this.reporter)return{success:!1,fatalError:!1};let e=await this.evaluationEventSnapshotPreprocessor.process(this.evaluationEventQueue.takeSnapshot());return await this.reporter?.report({context:this._context,discreteEvents:{},aggregatedEvents:{evaluatedConfig:this.aggregator.aggregate(e)},droppedEvents:{evaluatedConfig:e.droppedCount}})}};let
|
|
1720
|
+
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 i={clientSdkKey:this.sdkKey,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(i))return{success:!0,fatalError:!1};let a=await this.sendReport(i);return a.fatalError&&(this.executeRequests=!1),a}isReportEmpty(e){return t(e.discreteEvents)&&t(e.aggregatedEvents)&&n(e.droppedEvents)}async sendReport(e){try{let t=await r(5e3,this.url,{method:`POST`,body:JSON.stringify(e),keepalive:!0},this.logger),n=this.isStatusFatal(t.status);return n&&this.logger.warn(`[EventReporter] Received a fatal status response (${t.status}) from the telemetry endpoint. No more telemetry data will be sent.`),{success:t.ok,fatalError:n}}catch(e){let t=i(e);return this.logger.warn(`[EventReporter] Error attempting to send telemetry data: ${e}`,{error:e}),{success:!1,fatalError:t}}}isStatusFatal(e){return!!e&&e>=400&&e<500&&e!=429}},o=class{aggregate(e){if(e.events.length==0)return[];let t=new Map;for(let n of e.events){let e=JSON.stringify(n),r=t.get(e)?.count||0;t.set(e,{event:n,count:r+1})}return Array.from(t).map(([,t])=>({startTime:e.startTime,endTime:e.endTime,count:t.count,event:t.event}))}},s=class{constructor(e){this._events=[],this._droppedEventCount=0,this.limit=e??1e3}get events(){return this._events}get reachedLimit(){return this._events.length>=this.limit}get droppedEventCount(){return this._droppedEventCount}push(...e){this.startTime||=new Date;let t=Math.max(0,e.length-this.limit),n=e.slice(t,e.length);if(this._droppedEventCount+=t,this._events.length+n.length>this.limit){let e=this._events.length+n.length-this.limit;this._events.splice(0,e),this._droppedEventCount+=e}return this._events.push(...n)}takeSnapshot(){let e=new Date,t=this.startTime??e,n=this._events.splice(0),r=this._droppedEventCount;return this.startTime=void 0,this._droppedEventCount=0,{startTime:t,endTime:e,events:n,droppedCount:r}}clear(){this._events=[],this.startTime=void 0,this._droppedEventCount=0}},c=class{constructor(e){this.aggregator=new o,this.collectEvents=!0,this.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=(e,t,...n)=>({type:`Log`,payload:{level:e,message:t,args:n}}),y=e=>{try{self.postMessage(e)}catch{self.postMessage(v(e.payload.level,e.payload.message))}},b={debug:function(e,...t){y(v(`debug`,e,t))},info:function(e,...t){y(v(`info`,e,t))},warn:function(e,...t){y(v(`warn`,e,t))},error:function(e,...t){y(v(`error`,e,t))}};addEventListener(`message`,e=>{let t=e.data;if(t.type!=`Initialize`&&!_){b.info(`[TelemetryWorker] Ignoring ${t.type} because no collector is configured`);return}switch(b.debug(`[TelemetryWorker] Received '${t.type}' event:`,t),t.type){case`Initialize`:if(_){b.warn(`[TelemetryWorker] Received a duplicate Initialize event, ignoring it`);break}_=new d({...t.payload,baseUrl:f(t.payload.baseUrl),urlFactory:f,valueIdGenerator:g,logger:b}),_.start();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" })));
|
|
1341
1721
|
} catch (error) {
|
|
1342
1722
|
this.turnOff("[WebWorkerTelemetryClient] Telemetry is off because the telemetry Web Worker could not be created: ", error);
|
|
1343
1723
|
return;
|
|
@@ -1364,8 +1744,19 @@ var WebWorkerTelemetryClient = class {
|
|
|
1364
1744
|
}
|
|
1365
1745
|
for (const evaluation of this.pendingEvaluations) worker.postMessage(evaluation);
|
|
1366
1746
|
this.pendingEvaluations.length = 0;
|
|
1747
|
+
this.forwardInvalidContextSubmissions(worker);
|
|
1367
1748
|
return worker;
|
|
1368
1749
|
}
|
|
1750
|
+
forwardInvalidContextSubmissions(worker) {
|
|
1751
|
+
if (!worker) return;
|
|
1752
|
+
const entries = this.pendingInvalidContextSubmissions.takeSnapshot();
|
|
1753
|
+
if (entries.length === 0) return;
|
|
1754
|
+
const submissionsMessage = {
|
|
1755
|
+
type: "InvalidContextSubmissions",
|
|
1756
|
+
payload: { entries }
|
|
1757
|
+
};
|
|
1758
|
+
worker.postMessage(submissionsMessage);
|
|
1759
|
+
}
|
|
1369
1760
|
async updateContext(value) {
|
|
1370
1761
|
if (this.closePromise) return;
|
|
1371
1762
|
const updateContextMessage = {
|
|
@@ -1387,6 +1778,10 @@ var WebWorkerTelemetryClient = class {
|
|
|
1387
1778
|
if (this.pendingEvaluations.length >= this.pendingEvaluationLimit) this.pendingEvaluations.shift();
|
|
1388
1779
|
this.pendingEvaluations.push(evaluatedConfigMessage);
|
|
1389
1780
|
}
|
|
1781
|
+
recordInvalidContext(kindSample) {
|
|
1782
|
+
this.pendingInvalidContextSubmissions.record(kindSample);
|
|
1783
|
+
this.forwardInvalidContextSubmissions(this.worker);
|
|
1784
|
+
}
|
|
1390
1785
|
sanitizeEvaluatedConfigEvent(event) {
|
|
1391
1786
|
return {
|
|
1392
1787
|
...event,
|
|
@@ -1485,27 +1880,28 @@ const generateValueId = async (v) => {
|
|
|
1485
1880
|
};
|
|
1486
1881
|
//#endregion
|
|
1487
1882
|
//#region ../js-browser-client/src/BrowserConfigDirectorClient.ts
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1883
|
+
/**
|
|
1884
|
+
* Creates a browser client. With a client SDK key it connects to ConfigDirector; with
|
|
1885
|
+
* {@link OfflineClientOptions} alone it runs in `offline` mode and serves `connection.values`. An
|
|
1886
|
+
* absent key with `clientOptions` is the same as the options alone when their mode is `offline`,
|
|
1887
|
+
* and fails like a missing key otherwise.
|
|
1888
|
+
*/
|
|
1889
|
+
const createBrowserClient = (clientSdkKeyOrOptions, sdkOptions, clientOptions) => {
|
|
1890
|
+
return DefaultConfigDirectorClient.from(({ clientSdkKey, baseUrl, logger, metadata }) => new WebWorkerTelemetryClient({
|
|
1492
1891
|
sdkKey: clientSdkKey,
|
|
1493
1892
|
sdkIdentity: {
|
|
1494
1893
|
sdkName: sdkOptions.sdkName,
|
|
1495
1894
|
sdkVersion: sdkOptions.sdkVersion
|
|
1496
1895
|
},
|
|
1497
1896
|
metaContext: {
|
|
1498
|
-
appName:
|
|
1499
|
-
appVersion:
|
|
1897
|
+
appName: metadata?.appName,
|
|
1898
|
+
appVersion: metadata?.appVersion
|
|
1500
1899
|
},
|
|
1501
1900
|
logger,
|
|
1502
1901
|
baseUrl,
|
|
1503
1902
|
urlFactory: defaultUrlFactory,
|
|
1504
1903
|
valueIdGenerator: generateValueId
|
|
1505
|
-
}),
|
|
1506
|
-
...clientOptions,
|
|
1507
|
-
logger
|
|
1508
|
-
});
|
|
1904
|
+
}), clientSdkKeyOrOptions, sdkOptions, clientOptions);
|
|
1509
1905
|
};
|
|
1510
1906
|
//#endregion
|
|
1511
1907
|
//#region ../js-client-core/src/installedTestClient.ts
|
|
@@ -1537,6 +1933,13 @@ const decideMountAction = (client, context) => {
|
|
|
1537
1933
|
return "none";
|
|
1538
1934
|
};
|
|
1539
1935
|
//#endregion
|
|
1936
|
+
//#region ../shared/src/browser-half.ts
|
|
1937
|
+
const decideBrowserHalfSource = (browserHalf, serverConnectionMode) => {
|
|
1938
|
+
if (browserHalf.mode !== void 0 || browserHalf.values !== void 0) return "as-given";
|
|
1939
|
+
if (serverConnectionMode === "offline") return "inherit-offline";
|
|
1940
|
+
return "connect";
|
|
1941
|
+
};
|
|
1942
|
+
//#endregion
|
|
1540
1943
|
//#region src/runtime/app/composables/useConfigDirectorClient.ts
|
|
1541
1944
|
const useConfigDirectorClient = () => {
|
|
1542
1945
|
const { $configDirectorClient } = useNuxtApp();
|
|
@@ -1555,7 +1958,7 @@ const useConfigDirectorContext = () => {
|
|
|
1555
1958
|
const rawContext = toRaw(context);
|
|
1556
1959
|
const oldContext = JSON.stringify(state.value);
|
|
1557
1960
|
const newContext = JSON.stringify(rawContext);
|
|
1558
|
-
state.value = rawContext;
|
|
1961
|
+
state.value = admittedContextOrNone(rawContext);
|
|
1559
1962
|
if (oldContext != newContext) {
|
|
1560
1963
|
const timeout = getEffectiveTimeout(options?.timeoutMilliseconds);
|
|
1561
1964
|
let timer;
|
|
@@ -1581,13 +1984,16 @@ const createDefaultLogger = (level) => {
|
|
|
1581
1984
|
return logger;
|
|
1582
1985
|
};
|
|
1583
1986
|
//#endregion
|
|
1987
|
+
//#region src/runtime/offline-values-route.ts
|
|
1988
|
+
const OFFLINE_VALUES_ROUTE = "/_configdirector/offline-values";
|
|
1989
|
+
//#endregion
|
|
1584
1990
|
//#region src/runtime/plugin.client.ts
|
|
1585
1991
|
const DEFAULT_INITIALIZATION_TIMEOUT_MILLISECONDS = 2e3;
|
|
1586
|
-
var plugin_client_default = defineNuxtPlugin((nuxtApp) => {
|
|
1992
|
+
var plugin_client_default = defineNuxtPlugin(async (nuxtApp) => {
|
|
1587
1993
|
const runtimeConfig = useRuntimeConfig();
|
|
1588
1994
|
const logger = createDefaultLogger(runtimeConfig.public?.configdirector?.logLevel);
|
|
1589
1995
|
const installedTestClient = readInstalledTestClient();
|
|
1590
|
-
const client = installedTestClient ?? buildClient(runtimeConfig.public?.configdirector, logger);
|
|
1996
|
+
const client = installedTestClient ?? await buildClient(runtimeConfig.public?.configdirector, logger);
|
|
1591
1997
|
logger.debug(installedTestClient ? "Installed ConfigDirector Nuxt plugin with a test client" : "Installed ConfigDirector Nuxt plugin");
|
|
1592
1998
|
const readyStatus = shallowRef(client.isReady ? "ready" : "loading");
|
|
1593
1999
|
client.on("clientReady", () => {
|
|
@@ -1608,29 +2014,58 @@ var plugin_client_default = defineNuxtPlugin((nuxtApp) => {
|
|
|
1608
2014
|
configDirectorClientReadyStatus: readonly(readyStatus)
|
|
1609
2015
|
} };
|
|
1610
2016
|
});
|
|
1611
|
-
const buildClient = (configdirector, logger) => {
|
|
1612
|
-
|
|
2017
|
+
const buildClient = async (configdirector, logger) => {
|
|
2018
|
+
const browserHalf = await browserHalfConnectionOf(configdirector, logger);
|
|
2019
|
+
if (browserHalf.mode !== "offline" && !configdirector?.clientSdkKey) {
|
|
1613
2020
|
const message = "The ConfigDirector clientSdkKey must be configured for the plugin to initialize. You can provide it in nuxt.config.ts under 'runtimeConfig.public.configdirector.clientSdkKey' or as a runtime environment variable named NUXT_PUBLIC_CONFIGDIRECTOR_CLIENT_SDK_KEY";
|
|
1614
2021
|
logger.error(message);
|
|
1615
2022
|
throw new ConfigDirectorInitializationError(message);
|
|
1616
2023
|
}
|
|
1617
|
-
|
|
1618
|
-
return createBrowserClient(clientSdkKey, {
|
|
2024
|
+
return createBrowserClient(configdirector?.clientSdkKey || void 0, {
|
|
1619
2025
|
sdkName: "nuxt-sdk",
|
|
1620
|
-
sdkVersion: "1.
|
|
2026
|
+
sdkVersion: "1.11.0"
|
|
1621
2027
|
}, {
|
|
1622
2028
|
metadata: {
|
|
1623
|
-
appName,
|
|
1624
|
-
appVersion
|
|
2029
|
+
appName: configdirector?.appName,
|
|
2030
|
+
appVersion: configdirector?.appVersion
|
|
1625
2031
|
},
|
|
1626
2032
|
connection: {
|
|
1627
|
-
url: baseUrl || void 0,
|
|
1628
|
-
mode:
|
|
1629
|
-
|
|
1630
|
-
|
|
2033
|
+
url: configdirector?.baseUrl || void 0,
|
|
2034
|
+
mode: browserHalf.mode,
|
|
2035
|
+
values: browserHalf.values,
|
|
2036
|
+
pollingInterval: configdirector?.connection?.pollingInterval || void 0,
|
|
2037
|
+
timeout: configdirector?.connection?.timeout || DEFAULT_INITIALIZATION_TIMEOUT_MILLISECONDS
|
|
1631
2038
|
},
|
|
1632
2039
|
logger
|
|
1633
2040
|
});
|
|
1634
2041
|
};
|
|
2042
|
+
const browserHalfConnectionOf = async (configdirector, logger) => {
|
|
2043
|
+
const givenConnection = {
|
|
2044
|
+
mode: configdirector?.connection?.mode || void 0,
|
|
2045
|
+
values: configdirector?.connection?.values
|
|
2046
|
+
};
|
|
2047
|
+
const source = decideBrowserHalfSource(givenConnection, configdirector?.serverConnectionMode);
|
|
2048
|
+
logger.debug(`Browser half source: ${source}`);
|
|
2049
|
+
switch (source) {
|
|
2050
|
+
case "as-given": return givenConnection;
|
|
2051
|
+
case "inherit-offline": return {
|
|
2052
|
+
mode: "offline",
|
|
2053
|
+
values: await fetchServerOfflineValues(logger)
|
|
2054
|
+
};
|
|
2055
|
+
case "connect": return {
|
|
2056
|
+
mode: void 0,
|
|
2057
|
+
values: void 0
|
|
2058
|
+
};
|
|
2059
|
+
}
|
|
2060
|
+
};
|
|
2061
|
+
const fetchServerOfflineValues = async (logger) => {
|
|
2062
|
+
try {
|
|
2063
|
+
return await $fetch(OFFLINE_VALUES_ROUTE);
|
|
2064
|
+
} catch (error) {
|
|
2065
|
+
const message = `The server runs ConfigDirector in offline mode, but its values could not be fetched from ${OFFLINE_VALUES_ROUTE}: ${error instanceof Error ? error.message : String(error)}. A statically generated site has no server to serve them; set 'runtimeConfig.public.configdirector.connection' to { mode: "offline", values } in nuxt.config.ts to give the browser its own values.`;
|
|
2066
|
+
logger.error(message);
|
|
2067
|
+
throw new ConfigDirectorInitializationError(message);
|
|
2068
|
+
}
|
|
2069
|
+
};
|
|
1635
2070
|
//#endregion
|
|
1636
2071
|
export { plugin_client_default as default };
|