@configdirector/react-native-sdk 1.6.1 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -0
- package/dist/configdirector-react-native-testing.d.mts +7 -15
- package/dist/configdirector-react-native-testing.mjs +8 -104
- package/dist/configdirector-react-native.d.mts +18 -6
- package/dist/configdirector-react-native.mjs +120 -16
- package/dist/{installedTestClient-CTPtIG6o.mjs → installedTestClient-CKjaEilj.mjs} +430 -91
- package/dist/{types-fcPMBgiX.d.mts → types-DoORnGTD.d.mts} +104 -48
- package/package.json +8 -2
|
@@ -89,6 +89,13 @@ var ConfigDirectorValidationError = class ConfigDirectorValidationError extends
|
|
|
89
89
|
Object.setPrototypeOf(this, ConfigDirectorValidationError.prototype);
|
|
90
90
|
}
|
|
91
91
|
};
|
|
92
|
+
var ConfigDirectorInitializationError = class ConfigDirectorInitializationError extends Error {
|
|
93
|
+
constructor(message) {
|
|
94
|
+
super(message);
|
|
95
|
+
_defineProperty(this, "name", "ConfigDirectorInitializationError");
|
|
96
|
+
Object.setPrototypeOf(this, ConfigDirectorInitializationError.prototype);
|
|
97
|
+
}
|
|
98
|
+
};
|
|
92
99
|
//#endregion
|
|
93
100
|
//#region ../js-client-core/src/errors.ts
|
|
94
101
|
const isFetchErrorFatal = (fetchError) => {
|
|
@@ -438,11 +445,15 @@ var StreamingTransport = class {
|
|
|
438
445
|
_defineProperty(this, "eventEmitter", new Emitter());
|
|
439
446
|
_defineProperty(this, "url", void 0);
|
|
440
447
|
_defineProperty(this, "timeoutTimer", void 0);
|
|
448
|
+
_defineProperty(this, "connectionGeneration", 0);
|
|
441
449
|
this.options = options;
|
|
442
450
|
this.logger = options.logger;
|
|
443
451
|
this.url = options.resolveUrl("client/sse/v1", options.baseUrl);
|
|
444
452
|
}
|
|
445
453
|
async connect(context, timeout) {
|
|
454
|
+
const generation = ++this.connectionGeneration;
|
|
455
|
+
const clientSession = await this.options.clientSession;
|
|
456
|
+
if (generation !== this.connectionGeneration) return this;
|
|
446
457
|
if (this.eventSource) this.close();
|
|
447
458
|
const eventSourcePromise = new Promise((resolve, reject) => {
|
|
448
459
|
this.eventSource = createEventSourceClient({
|
|
@@ -454,7 +465,8 @@ var StreamingTransport = class {
|
|
|
454
465
|
givenContext: context,
|
|
455
466
|
metaContext: this.options.metaContext,
|
|
456
467
|
clientSdkKey: this.options.clientSdkKey,
|
|
457
|
-
instanceId:
|
|
468
|
+
instanceId: clientSession.instanceId,
|
|
469
|
+
ephemeral: clientSession.ephemeral
|
|
458
470
|
}),
|
|
459
471
|
onMessage: ({ data }) => {
|
|
460
472
|
this.dispatchMessage(data);
|
|
@@ -520,6 +532,7 @@ var StreamingTransport = class {
|
|
|
520
532
|
}
|
|
521
533
|
close() {
|
|
522
534
|
var _this$eventSource;
|
|
535
|
+
this.connectionGeneration++;
|
|
523
536
|
(_this$eventSource = this.eventSource) === null || _this$eventSource === void 0 || _this$eventSource.close();
|
|
524
537
|
clearTimeout(this.timeoutTimer);
|
|
525
538
|
}
|
|
@@ -529,6 +542,187 @@ var StreamingTransport = class {
|
|
|
529
542
|
}
|
|
530
543
|
};
|
|
531
544
|
//#endregion
|
|
545
|
+
//#region ../shared/src/native-values.ts
|
|
546
|
+
const encodeNativeValue = (key, value) => {
|
|
547
|
+
if (key.trim().length == 0) throw new ConfigDirectorValidationError("Invalid config key. The key of a value cannot be empty.");
|
|
548
|
+
return encode(key, value);
|
|
549
|
+
};
|
|
550
|
+
const encode = (key, value) => {
|
|
551
|
+
switch (typeof value) {
|
|
552
|
+
case "boolean": return {
|
|
553
|
+
type: "boolean",
|
|
554
|
+
value: value ? "true" : "false"
|
|
555
|
+
};
|
|
556
|
+
case "number": return encodeNumber(key, value);
|
|
557
|
+
case "string": return {
|
|
558
|
+
type: "string",
|
|
559
|
+
value
|
|
560
|
+
};
|
|
561
|
+
case "object": if (value !== null) {
|
|
562
|
+
assertJsonContents(key, value, "");
|
|
563
|
+
return {
|
|
564
|
+
type: "json",
|
|
565
|
+
value: JSON.stringify(value)
|
|
566
|
+
};
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
throw new ConfigDirectorValidationError(`Invalid value for '${key}': ${describe(value)}. Use a boolean, a number, a string, a plain object, or an array.`);
|
|
570
|
+
};
|
|
571
|
+
const encodeNumber = (key, value) => {
|
|
572
|
+
if (!Number.isFinite(value)) throw new ConfigDirectorValidationError(`Invalid value for '${key}': ${describe(value)} is not a finite number.`);
|
|
573
|
+
if (Number.isInteger(value)) {
|
|
574
|
+
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.`);
|
|
575
|
+
return {
|
|
576
|
+
type: "integer",
|
|
577
|
+
value: String(value)
|
|
578
|
+
};
|
|
579
|
+
}
|
|
580
|
+
return {
|
|
581
|
+
type: "float",
|
|
582
|
+
value: toPlainDecimal(value)
|
|
583
|
+
};
|
|
584
|
+
};
|
|
585
|
+
const toPlainDecimal = (value) => {
|
|
586
|
+
const text = String(value);
|
|
587
|
+
const exponentIndex = text.indexOf("e-");
|
|
588
|
+
if (exponentIndex < 0) return text;
|
|
589
|
+
const sign = text.startsWith("-") ? "-" : "";
|
|
590
|
+
const [integerDigits = "", fractionDigits = ""] = text.slice(sign.length, exponentIndex).split(".");
|
|
591
|
+
const leadingZeros = Number(text.slice(exponentIndex + 2)) - integerDigits.length;
|
|
592
|
+
return `${sign}0.${"0".repeat(leadingZeros)}${integerDigits}${fractionDigits}`;
|
|
593
|
+
};
|
|
594
|
+
const assertJsonContents = (key, value, path) => {
|
|
595
|
+
if (value === null || typeof value === "boolean" || typeof value === "string") return;
|
|
596
|
+
if (typeof value === "number") {
|
|
597
|
+
if (!Number.isFinite(value)) throw invalidJsonContents(key, path, `${describe(value)} is not a finite number`);
|
|
598
|
+
return;
|
|
599
|
+
}
|
|
600
|
+
if (Array.isArray(value)) {
|
|
601
|
+
value.forEach((item, index) => assertJsonContents(key, item, `${path}[${index}]`));
|
|
602
|
+
return;
|
|
603
|
+
}
|
|
604
|
+
if (isPlainObject(value)) {
|
|
605
|
+
for (const [property, item] of Object.entries(value)) assertJsonContents(key, item, path ? `${path}.${property}` : property);
|
|
606
|
+
return;
|
|
607
|
+
}
|
|
608
|
+
throw invalidJsonContents(key, path, `${describe(value)} cannot be encoded as JSON`);
|
|
609
|
+
};
|
|
610
|
+
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.`);
|
|
611
|
+
const isPlainObject = (value) => {
|
|
612
|
+
if (typeof value !== "object" || value === null) return false;
|
|
613
|
+
const prototype = Object.getPrototypeOf(value);
|
|
614
|
+
return prototype === Object.prototype || prototype === null;
|
|
615
|
+
};
|
|
616
|
+
const describe = (value) => {
|
|
617
|
+
if (value === null) return "null";
|
|
618
|
+
if (typeof value === "function") return "a function";
|
|
619
|
+
if (typeof value === "object") {
|
|
620
|
+
var _value$constructor$na, _value$constructor;
|
|
621
|
+
return `a ${(_value$constructor$na = (_value$constructor = value.constructor) === null || _value$constructor === void 0 ? void 0 : _value$constructor.name) !== null && _value$constructor$na !== void 0 ? _value$constructor$na : "object"} instance`;
|
|
622
|
+
}
|
|
623
|
+
if (typeof value === "symbol") return "a symbol";
|
|
624
|
+
return String(value);
|
|
625
|
+
};
|
|
626
|
+
//#endregion
|
|
627
|
+
//#region ../js-client-core/src/native-values.ts
|
|
628
|
+
const nativeValueConfigState = (id, key, value, valueId) => {
|
|
629
|
+
const encoded = encodeNativeValue(key, value);
|
|
630
|
+
return {
|
|
631
|
+
id,
|
|
632
|
+
key,
|
|
633
|
+
type: encoded.type,
|
|
634
|
+
value: encoded.value,
|
|
635
|
+
valueId
|
|
636
|
+
};
|
|
637
|
+
};
|
|
638
|
+
//#endregion
|
|
639
|
+
//#region ../js-client-core/src/OfflineTransport.ts
|
|
640
|
+
const OFFLINE_ENVIRONMENT = {
|
|
641
|
+
environmentId: "offline",
|
|
642
|
+
projectId: "offline"
|
|
643
|
+
};
|
|
644
|
+
var OfflineTransport = class {
|
|
645
|
+
constructor(source) {
|
|
646
|
+
this.source = source;
|
|
647
|
+
_defineProperty(this, "emitter", new Emitter());
|
|
648
|
+
_defineProperty(this, "on", (name, handler) => this.emitter.on(name, handler));
|
|
649
|
+
_defineProperty(this, "off", (name, handler) => this.emitter.off(name, handler));
|
|
650
|
+
}
|
|
651
|
+
async connect() {
|
|
652
|
+
const configs = loadOfflineConfigs(this.source);
|
|
653
|
+
this.emitter.emit("configSetReceived", {
|
|
654
|
+
...OFFLINE_ENVIRONMENT,
|
|
655
|
+
kind: "full",
|
|
656
|
+
configs
|
|
657
|
+
});
|
|
658
|
+
return this;
|
|
659
|
+
}
|
|
660
|
+
clear() {
|
|
661
|
+
this.emitter.clear();
|
|
662
|
+
}
|
|
663
|
+
close() {}
|
|
664
|
+
dispose() {
|
|
665
|
+
this.clear();
|
|
666
|
+
}
|
|
667
|
+
};
|
|
668
|
+
const loadOfflineConfigs = (source) => {
|
|
669
|
+
switch (source.kind) {
|
|
670
|
+
case "in-code-defaults": return {};
|
|
671
|
+
case "values": return encodeOfflineValues(source.values);
|
|
672
|
+
case "rejected": throw new ConfigDirectorInitializationError(source.message);
|
|
673
|
+
}
|
|
674
|
+
};
|
|
675
|
+
const encodeOfflineValues = (values) => {
|
|
676
|
+
try {
|
|
677
|
+
return Object.fromEntries(Object.entries(values).map(([key, value]) => [key, nativeValueConfigState(`offline:${key}`, key, value, void 0)]));
|
|
678
|
+
} catch (error) {
|
|
679
|
+
if (error instanceof ConfigDirectorValidationError) throw new ConfigDirectorInitializationError(`The offline values could not be loaded. ${error.message}`);
|
|
680
|
+
throw error;
|
|
681
|
+
}
|
|
682
|
+
};
|
|
683
|
+
//#endregion
|
|
684
|
+
//#region ../shared/src/constants.ts
|
|
685
|
+
const CLIENT_BASE_URL = "https://client-sdk-api.configdirector.com";
|
|
686
|
+
//#endregion
|
|
687
|
+
//#region ../js-client-core/src/connection-plan.ts
|
|
688
|
+
const planConnection = (clientSdkKey, connection, urlFactory) => {
|
|
689
|
+
var _connection$mode;
|
|
690
|
+
if ((connection === null || connection === void 0 ? void 0 : connection.mode) === "offline") return {
|
|
691
|
+
kind: "stay-offline",
|
|
692
|
+
source: offlineSourceOf(connection)
|
|
693
|
+
};
|
|
694
|
+
const mode = (_connection$mode = connection === null || connection === void 0 ? void 0 : connection.mode) !== null && _connection$mode !== void 0 ? _connection$mode : "streaming";
|
|
695
|
+
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");
|
|
696
|
+
if ((connection === null || connection === void 0 ? void 0 : connection.values) !== void 0) return {
|
|
697
|
+
kind: "stay-offline",
|
|
698
|
+
source: {
|
|
699
|
+
kind: "rejected",
|
|
700
|
+
message: `The connection option 'values' applies only when the connection mode is 'offline', but the mode is '${mode}'.`
|
|
701
|
+
}
|
|
702
|
+
};
|
|
703
|
+
return {
|
|
704
|
+
kind: "connect",
|
|
705
|
+
mode,
|
|
706
|
+
clientSdkKey,
|
|
707
|
+
baseUrl: baseUrlOf(connection === null || connection === void 0 ? void 0 : connection.url, urlFactory)
|
|
708
|
+
};
|
|
709
|
+
};
|
|
710
|
+
const baseUrlOf = (url, urlFactory) => {
|
|
711
|
+
if (!url) return CLIENT_BASE_URL;
|
|
712
|
+
try {
|
|
713
|
+
return urlFactory(url);
|
|
714
|
+
} catch (error) {
|
|
715
|
+
throw new ConfigDirectorValidationError(`Invalid base URL '${url}'. Parsing failed: ${error}`);
|
|
716
|
+
}
|
|
717
|
+
};
|
|
718
|
+
const offlineSourceOf = (connection) => {
|
|
719
|
+
if (connection.values !== void 0) return {
|
|
720
|
+
kind: "values",
|
|
721
|
+
values: connection.values
|
|
722
|
+
};
|
|
723
|
+
return { kind: "in-code-defaults" };
|
|
724
|
+
};
|
|
725
|
+
//#endregion
|
|
532
726
|
//#region ../shared/src/value-parser.ts
|
|
533
727
|
const getRequestedType = (defaultValue) => {
|
|
534
728
|
const baseType = typeof defaultValue;
|
|
@@ -752,12 +946,17 @@ const createDefaultLogger = (level, messageDecorator) => {
|
|
|
752
946
|
return new DefaultConsoleLogger(level !== null && level !== void 0 ? level : "warn", messageDecorator !== null && messageDecorator !== void 0 ? messageDecorator : new LogMessageDecorator());
|
|
753
947
|
};
|
|
754
948
|
//#endregion
|
|
949
|
+
//#region ../js-client-core/src/telemetry/discarding.ts
|
|
950
|
+
const discardingTelemetryClient = {
|
|
951
|
+
updateContext: async () => {},
|
|
952
|
+
evaluatedConfig: () => {},
|
|
953
|
+
recordInvalidContext: () => {},
|
|
954
|
+
close: async () => {}
|
|
955
|
+
};
|
|
956
|
+
//#endregion
|
|
755
957
|
//#region ../shared/src/url.ts
|
|
756
958
|
const defaultUrlFactory = (input, base) => new URL(input, base === null || base === void 0 ? void 0 : base.toString());
|
|
757
959
|
//#endregion
|
|
758
|
-
//#region ../shared/src/constants.ts
|
|
759
|
-
const CLIENT_BASE_URL = "https://client-sdk-api.configdirector.com";
|
|
760
|
-
//#endregion
|
|
761
960
|
//#region ../shared/src/transport/pollingInterval.ts
|
|
762
961
|
const resolvePollingInterval = (configuredSeconds, defaults) => {
|
|
763
962
|
if (configuredSeconds === void 0) return {
|
|
@@ -783,10 +982,11 @@ const generateInstanceId = () => {
|
|
|
783
982
|
});
|
|
784
983
|
};
|
|
785
984
|
//#endregion
|
|
786
|
-
//#region ../js-client-core/src/
|
|
985
|
+
//#region ../js-client-core/src/client-session.ts
|
|
787
986
|
const STORAGE_KEY = "configdirector-sdk:instance-id";
|
|
788
987
|
const RECORD_VERSION = 1;
|
|
789
988
|
const MAX_AGE_MS = 360 * 60 * 1e3;
|
|
989
|
+
const STORAGE_DEADLINE_MS = 1e3;
|
|
790
990
|
const CHECKSUM_SALT = "configdirector.instanceId.v1";
|
|
791
991
|
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}$/;
|
|
792
992
|
const fnv1a = (input, offsetBasis) => {
|
|
@@ -802,7 +1002,7 @@ const checksumFor = (id, createdAt) => {
|
|
|
802
1002
|
const low = fnv1a(payload, 2166136261);
|
|
803
1003
|
return fnv1a(`${low}|${payload}`, 16777619).toString(16).padStart(8, "0") + low.toString(16).padStart(8, "0");
|
|
804
1004
|
};
|
|
805
|
-
const
|
|
1005
|
+
const localStorageIfAvailable = () => {
|
|
806
1006
|
try {
|
|
807
1007
|
var _localStorage;
|
|
808
1008
|
return (_localStorage = globalThis.localStorage) !== null && _localStorage !== void 0 ? _localStorage : void 0;
|
|
@@ -818,34 +1018,37 @@ const isUsableRecord = (record) => {
|
|
|
818
1018
|
if (!UUID_V4_PATTERN.test(id)) return false;
|
|
819
1019
|
return checksum === checksumFor(id, createdAt);
|
|
820
1020
|
};
|
|
821
|
-
const readStoredInstanceId = (storage, logger) => {
|
|
1021
|
+
const readStoredInstanceId = async (storage, logger) => {
|
|
822
1022
|
let raw;
|
|
823
1023
|
try {
|
|
824
|
-
raw = storage.getItem(STORAGE_KEY);
|
|
1024
|
+
raw = await storage.getItem(STORAGE_KEY);
|
|
825
1025
|
} catch (error) {
|
|
826
|
-
logger
|
|
827
|
-
return;
|
|
1026
|
+
logger.debug("[ConfigDirectorClient] Could not read the stored instance id, using an ephemeral one", error);
|
|
1027
|
+
return { outcome: "failed" };
|
|
828
1028
|
}
|
|
829
|
-
if (
|
|
1029
|
+
if (typeof raw !== "string" || raw === "") return { outcome: "nothing usable" };
|
|
830
1030
|
let record;
|
|
831
1031
|
try {
|
|
832
1032
|
record = JSON.parse(raw);
|
|
833
1033
|
} catch {
|
|
834
|
-
logger
|
|
835
|
-
return;
|
|
1034
|
+
logger.debug("[ConfigDirectorClient] The stored instance id is not valid JSON, generating a new one");
|
|
1035
|
+
return { outcome: "nothing usable" };
|
|
836
1036
|
}
|
|
837
1037
|
if (!isUsableRecord(record)) {
|
|
838
|
-
logger
|
|
839
|
-
return;
|
|
1038
|
+
logger.debug("[ConfigDirectorClient] The stored instance id failed validation, generating a new one");
|
|
1039
|
+
return { outcome: "nothing usable" };
|
|
840
1040
|
}
|
|
841
1041
|
const age = Date.now() - record.createdAt;
|
|
842
1042
|
if (age < 0 || age >= MAX_AGE_MS) {
|
|
843
|
-
logger
|
|
844
|
-
return;
|
|
1043
|
+
logger.debug("[ConfigDirectorClient] The stored instance id has expired, generating a new one");
|
|
1044
|
+
return { outcome: "nothing usable" };
|
|
845
1045
|
}
|
|
846
|
-
return
|
|
1046
|
+
return {
|
|
1047
|
+
outcome: "stored",
|
|
1048
|
+
instanceId: record.id
|
|
1049
|
+
};
|
|
847
1050
|
};
|
|
848
|
-
const
|
|
1051
|
+
const persistInstanceId = async (storage, id, logger) => {
|
|
849
1052
|
const createdAt = Date.now();
|
|
850
1053
|
const record = {
|
|
851
1054
|
v: RECORD_VERSION,
|
|
@@ -854,19 +1057,54 @@ const storeInstanceId = (storage, id, logger) => {
|
|
|
854
1057
|
checksum: checksumFor(id, createdAt)
|
|
855
1058
|
};
|
|
856
1059
|
try {
|
|
857
|
-
storage.setItem(STORAGE_KEY, JSON.stringify(record));
|
|
1060
|
+
await storage.setItem(STORAGE_KEY, JSON.stringify(record));
|
|
1061
|
+
return true;
|
|
858
1062
|
} catch (error) {
|
|
859
|
-
logger
|
|
1063
|
+
logger.debug("[ConfigDirectorClient] Could not persist the instance id, using it as an ephemeral one", error);
|
|
1064
|
+
return false;
|
|
1065
|
+
}
|
|
1066
|
+
};
|
|
1067
|
+
const settleFromStorage = async (storage, provisionalId, deadline, logger) => {
|
|
1068
|
+
const read = await readStoredInstanceId(storage, logger);
|
|
1069
|
+
switch (read.outcome) {
|
|
1070
|
+
case "stored": return { instanceId: read.instanceId };
|
|
1071
|
+
case "failed": return {
|
|
1072
|
+
instanceId: provisionalId,
|
|
1073
|
+
ephemeral: true
|
|
1074
|
+
};
|
|
1075
|
+
case "nothing usable": break;
|
|
860
1076
|
}
|
|
1077
|
+
if (deadline.passed) return {
|
|
1078
|
+
instanceId: provisionalId,
|
|
1079
|
+
ephemeral: true
|
|
1080
|
+
};
|
|
1081
|
+
return await persistInstanceId(storage, provisionalId, logger) ? { instanceId: provisionalId } : {
|
|
1082
|
+
instanceId: provisionalId,
|
|
1083
|
+
ephemeral: true
|
|
1084
|
+
};
|
|
861
1085
|
};
|
|
862
|
-
const
|
|
863
|
-
const
|
|
864
|
-
if (!storage)
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
1086
|
+
const resolveClientSession = (logger, storage = localStorageIfAvailable()) => {
|
|
1087
|
+
const provisionalId = generateInstanceId();
|
|
1088
|
+
if (!storage) {
|
|
1089
|
+
logger.debug("[ConfigDirectorClient] No instance id storage is available, using an ephemeral instance id");
|
|
1090
|
+
return Promise.resolve({
|
|
1091
|
+
instanceId: provisionalId,
|
|
1092
|
+
ephemeral: true
|
|
1093
|
+
});
|
|
1094
|
+
}
|
|
1095
|
+
const deadline = { passed: false };
|
|
1096
|
+
let deadlineTimer;
|
|
1097
|
+
const fallbackAtDeadline = new Promise((resolve) => {
|
|
1098
|
+
deadlineTimer = setTimeout(() => {
|
|
1099
|
+
deadline.passed = true;
|
|
1100
|
+
logger.warn(`[ConfigDirectorClient] The instance id storage did not respond within ${STORAGE_DEADLINE_MS}ms, using an ephemeral instance id`);
|
|
1101
|
+
resolve({
|
|
1102
|
+
instanceId: provisionalId,
|
|
1103
|
+
ephemeral: true
|
|
1104
|
+
});
|
|
1105
|
+
}, STORAGE_DEADLINE_MS);
|
|
1106
|
+
});
|
|
1107
|
+
return Promise.race([settleFromStorage(storage, provisionalId, deadline, logger), fallbackAtDeadline]).finally(() => clearTimeout(deadlineTimer));
|
|
870
1108
|
};
|
|
871
1109
|
//#endregion
|
|
872
1110
|
//#region ../shared/src/transport/AbstractPollingTransport.ts
|
|
@@ -954,25 +1192,33 @@ var PollingTransport = class extends AbstractPollingTransport {
|
|
|
954
1192
|
_defineProperty(this, "eventEmitter", new Emitter());
|
|
955
1193
|
_defineProperty(this, "url", void 0);
|
|
956
1194
|
_defineProperty(this, "lastUpdateTimestamp", void 0);
|
|
1195
|
+
_defineProperty(this, "connectionGeneration", 0);
|
|
957
1196
|
this.options = options;
|
|
958
1197
|
this.logger = options.logger;
|
|
959
1198
|
this.url = options.resolveUrl("client/polling/v1", options.baseUrl);
|
|
960
1199
|
this.pollingIntervalSeconds = (_options$pollingInter = options.pollingInterval) !== null && _options$pollingInter !== void 0 ? _options$pollingInter : 60;
|
|
961
1200
|
}
|
|
962
1201
|
async connect(context, timeout) {
|
|
1202
|
+
const generation = ++this.connectionGeneration;
|
|
963
1203
|
this.clearPollingInterval();
|
|
1204
|
+
const clientSession = await this.options.clientSession;
|
|
1205
|
+
if (generation !== this.connectionGeneration) return this;
|
|
964
1206
|
try {
|
|
965
|
-
await this.fetchConfigs(context, timeout);
|
|
1207
|
+
await this.fetchConfigs(context, timeout, clientSession);
|
|
966
1208
|
} finally {
|
|
967
1209
|
if (!this.fatalError) this.schedulePollingInterval(() => {
|
|
968
|
-
this.fetchConfigs(context, timeout).catch((error) => {
|
|
1210
|
+
this.fetchConfigs(context, timeout, clientSession).catch((error) => {
|
|
969
1211
|
this.logger.error("[PollingTransport] Error during polling:", error);
|
|
970
1212
|
});
|
|
971
1213
|
});
|
|
972
1214
|
}
|
|
973
1215
|
return this;
|
|
974
1216
|
}
|
|
975
|
-
|
|
1217
|
+
close() {
|
|
1218
|
+
this.connectionGeneration++;
|
|
1219
|
+
super.close();
|
|
1220
|
+
}
|
|
1221
|
+
async fetchConfigs(context, timeout, clientSession) {
|
|
976
1222
|
if (this.fatalError) {
|
|
977
1223
|
this.logger.warn("[PollingTransport] There was a prior unrecoverable error. Ignoring attempt to reconnect.");
|
|
978
1224
|
return;
|
|
@@ -985,7 +1231,8 @@ var PollingTransport = class extends AbstractPollingTransport {
|
|
|
985
1231
|
givenContext: context,
|
|
986
1232
|
metaContext: this.options.metaContext,
|
|
987
1233
|
clientSdkKey: this.options.clientSdkKey,
|
|
988
|
-
instanceId:
|
|
1234
|
+
instanceId: clientSession.instanceId,
|
|
1235
|
+
ephemeral: clientSession.ephemeral,
|
|
989
1236
|
lastUpdateTimestamp: this.lastUpdateTimestamp
|
|
990
1237
|
})
|
|
991
1238
|
}, this.logger);
|
|
@@ -1032,11 +1279,55 @@ const readHost = () => read((globals) => {
|
|
|
1032
1279
|
return (_globals$location = globals.location) === null || _globals$location === void 0 ? void 0 : _globals$location.host;
|
|
1033
1280
|
});
|
|
1034
1281
|
//#endregion
|
|
1282
|
+
//#region ../shared/src/context-kind.ts
|
|
1283
|
+
const CONTEXT_KIND_MAXIMUM_LENGTH = 64;
|
|
1284
|
+
const KIND_SAMPLE_MAXIMUM_CHARACTERS = 128;
|
|
1285
|
+
const isContextKindCharacter = (character) => character >= "a" && character <= "z" || character >= "0" && character <= "9" || character === "-" || character === "_";
|
|
1286
|
+
const isValidContextKind = (kind) => {
|
|
1287
|
+
if (kind.length < 1 || kind.length > CONTEXT_KIND_MAXIMUM_LENGTH) return false;
|
|
1288
|
+
for (const character of kind) if (!isContextKindCharacter(character)) return false;
|
|
1289
|
+
return true;
|
|
1290
|
+
};
|
|
1291
|
+
const isAcceptedKind = (kind) => kind === void 0 || kind === null || typeof kind === "string" && isValidContextKind(kind);
|
|
1292
|
+
const stringFormOf = (kind) => {
|
|
1293
|
+
if (typeof kind === "string") return kind;
|
|
1294
|
+
try {
|
|
1295
|
+
return String(kind);
|
|
1296
|
+
} catch {
|
|
1297
|
+
return Object.prototype.toString.call(kind);
|
|
1298
|
+
}
|
|
1299
|
+
};
|
|
1300
|
+
const firstCharactersOf = (text, maximumCharacters) => {
|
|
1301
|
+
let sample = "";
|
|
1302
|
+
let characterCount = 0;
|
|
1303
|
+
for (const character of text) {
|
|
1304
|
+
if (characterCount === maximumCharacters) break;
|
|
1305
|
+
sample += character;
|
|
1306
|
+
characterCount += 1;
|
|
1307
|
+
}
|
|
1308
|
+
return sample;
|
|
1309
|
+
};
|
|
1310
|
+
const admitContext = (context) => {
|
|
1311
|
+
const kind = context === null || context === void 0 ? void 0 : context.kind;
|
|
1312
|
+
if (isAcceptedKind(kind)) return {
|
|
1313
|
+
outcome: "admitted",
|
|
1314
|
+
context
|
|
1315
|
+
};
|
|
1316
|
+
return {
|
|
1317
|
+
outcome: "ignored",
|
|
1318
|
+
kindSample: firstCharactersOf(stringFormOf(kind), KIND_SAMPLE_MAXIMUM_CHARACTERS)
|
|
1319
|
+
};
|
|
1320
|
+
};
|
|
1321
|
+
//#endregion
|
|
1035
1322
|
//#region ../js-client-core/src/DefaultConfigDirectorClient.ts
|
|
1036
1323
|
const MAX_EXPONENTIAL_DELAY = 9;
|
|
1037
|
-
var DefaultConfigDirectorClient = class {
|
|
1038
|
-
|
|
1039
|
-
|
|
1324
|
+
var DefaultConfigDirectorClient = class DefaultConfigDirectorClient {
|
|
1325
|
+
static from(liveTelemetryClient, clientSdkKeyOrOptions, sdkOptions, clientOptions) {
|
|
1326
|
+
if (typeof clientSdkKeyOrOptions === "object") return new DefaultConfigDirectorClient(liveTelemetryClient, void 0, sdkOptions, clientSdkKeyOrOptions);
|
|
1327
|
+
return new DefaultConfigDirectorClient(liveTelemetryClient, clientSdkKeyOrOptions, sdkOptions, clientOptions);
|
|
1328
|
+
}
|
|
1329
|
+
constructor(liveTelemetryClient, clientSdkKey, sdkOptions, clientOptions, internalClientOptions) {
|
|
1330
|
+
var _clientOptions$logger, _clientOptions$connec, _clientOptions$connec2, _internalClientOption, _internalClientOption2, _clientOptions$hooks, _clientOptions$hooks2, _clientOptions$hooks3, _clientOptions$hooks4, _clientOptions$hooks5;
|
|
1040
1331
|
_defineProperty(this, "logger", void 0);
|
|
1041
1332
|
_defineProperty(this, "telemetryClient", void 0);
|
|
1042
1333
|
_defineProperty(this, "configSet", void 0);
|
|
@@ -1051,17 +1342,15 @@ var DefaultConfigDirectorClient = class {
|
|
|
1051
1342
|
_defineProperty(this, "readyPromise", void 0);
|
|
1052
1343
|
_defineProperty(this, "readyResolve", void 0);
|
|
1053
1344
|
_defineProperty(this, "currentContext", void 0);
|
|
1054
|
-
_defineProperty(this, "
|
|
1055
|
-
_defineProperty(this, "
|
|
1345
|
+
_defineProperty(this, "connectionPlan", void 0);
|
|
1346
|
+
_defineProperty(this, "clientSession", void 0);
|
|
1056
1347
|
this.logger = (_clientOptions$logger = clientOptions === null || clientOptions === void 0 ? void 0 : clientOptions.logger) !== null && _clientOptions$logger !== void 0 ? _clientOptions$logger : createDefaultLogger();
|
|
1057
|
-
this.
|
|
1348
|
+
this.clientSession = resolveClientSession(this.logger, internalClientOptions === null || internalClientOptions === void 0 ? void 0 : internalClientOptions.instanceIdStorage);
|
|
1058
1349
|
this.timeout = (_clientOptions$connec = clientOptions === null || clientOptions === void 0 || (_clientOptions$connec2 = clientOptions.connection) === null || _clientOptions$connec2 === void 0 ? void 0 : _clientOptions$connec2.timeout) !== null && _clientOptions$connec !== void 0 ? _clientOptions$connec : 3e3;
|
|
1059
|
-
this.validateSdkKeyPresence(clientSdkKey);
|
|
1060
1350
|
const urlFactory = (_internalClientOption = internalClientOptions === null || internalClientOptions === void 0 ? void 0 : internalClientOptions.urlFactory) !== null && _internalClientOption !== void 0 ? _internalClientOption : defaultUrlFactory;
|
|
1061
|
-
|
|
1062
|
-
this.
|
|
1063
|
-
this.
|
|
1064
|
-
this.transport = (_internalClientOption2 = internalClientOptions === null || internalClientOptions === void 0 ? void 0 : internalClientOptions.transport) !== null && _internalClientOption2 !== void 0 ? _internalClientOption2 : this.buildTransport(clientSdkKey, sdkOptions, baseUrl, urlFactory, clientOptions, internalClientOptions);
|
|
1351
|
+
this.connectionPlan = planConnection(clientSdkKey, clientOptions === null || clientOptions === void 0 ? void 0 : clientOptions.connection, urlFactory);
|
|
1352
|
+
this.telemetryClient = this.buildTelemetryClient(liveTelemetryClient, clientOptions);
|
|
1353
|
+
this.transport = (_internalClientOption2 = internalClientOptions === null || internalClientOptions === void 0 ? void 0 : internalClientOptions.transport) !== null && _internalClientOption2 !== void 0 ? _internalClientOption2 : this.buildTransport(sdkOptions, clientOptions, urlFactory, internalClientOptions);
|
|
1065
1354
|
this.transport.on("configSetReceived", (configSet) => {
|
|
1066
1355
|
var _this$readyResolve;
|
|
1067
1356
|
this.hasReceivedConfigSet = true;
|
|
@@ -1099,8 +1388,8 @@ var DefaultConfigDirectorClient = class {
|
|
|
1099
1388
|
const handlers = Array.isArray(handler) ? handler : [handler];
|
|
1100
1389
|
for (const h of handlers) if (typeof h === "function") this.on(event, h);
|
|
1101
1390
|
}
|
|
1102
|
-
|
|
1103
|
-
if (
|
|
1391
|
+
pollingIntervalFor(mode, configuredSeconds) {
|
|
1392
|
+
if (mode !== "polling") return;
|
|
1104
1393
|
const resolved = resolvePollingInterval(configuredSeconds, {
|
|
1105
1394
|
defaultSeconds: 60,
|
|
1106
1395
|
minimumSeconds: 30
|
|
@@ -1108,40 +1397,81 @@ var DefaultConfigDirectorClient = class {
|
|
|
1108
1397
|
if (resolved.warning) this.logger.warn(`[ConfigDirectorClient] ${resolved.warning}`);
|
|
1109
1398
|
return resolved.seconds;
|
|
1110
1399
|
}
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
userAgent: readUserAgent(),
|
|
1123
|
-
host: readHost()
|
|
1124
|
-
},
|
|
1125
|
-
logger: this.logger,
|
|
1126
|
-
fetch: internalClientOptions === null || internalClientOptions === void 0 ? void 0 : internalClientOptions.fetch,
|
|
1127
|
-
connectionRetryDelay: (_internalClientOption3 = internalClientOptions === null || internalClientOptions === void 0 ? void 0 : internalClientOptions.connectionRetryDelay) !== null && _internalClientOption3 !== void 0 ? _internalClientOption3 : ((attempt) => {
|
|
1128
|
-
return Math.pow(2, Math.min(attempt, MAX_EXPONENTIAL_DELAY)) * 1e3;
|
|
1129
|
-
}),
|
|
1130
|
-
pollingInterval: this.resolvePollingIntervalForMode(clientOptions === null || clientOptions === void 0 || (_clientOptions$connec6 = clientOptions.connection) === null || _clientOptions$connec6 === void 0 ? void 0 : _clientOptions$connec6.pollingInterval)
|
|
1131
|
-
});
|
|
1400
|
+
buildTelemetryClient(liveTelemetryClient, clientOptions) {
|
|
1401
|
+
const plan = this.connectionPlan;
|
|
1402
|
+
switch (plan.kind) {
|
|
1403
|
+
case "stay-offline": return discardingTelemetryClient;
|
|
1404
|
+
case "connect": return liveTelemetryClient({
|
|
1405
|
+
clientSdkKey: plan.clientSdkKey,
|
|
1406
|
+
baseUrl: plan.baseUrl,
|
|
1407
|
+
logger: this.logger,
|
|
1408
|
+
metadata: clientOptions === null || clientOptions === void 0 ? void 0 : clientOptions.metadata
|
|
1409
|
+
});
|
|
1410
|
+
}
|
|
1132
1411
|
}
|
|
1133
|
-
|
|
1134
|
-
|
|
1412
|
+
buildTransport(sdkOptions, clientOptions, urlFactory, internalClientOptions) {
|
|
1413
|
+
const plan = this.connectionPlan;
|
|
1414
|
+
switch (plan.kind) {
|
|
1415
|
+
case "stay-offline": return new OfflineTransport(plan.source);
|
|
1416
|
+
case "connect":
|
|
1417
|
+
var _internalClientOption3, _clientOptions$connec3;
|
|
1418
|
+
return new (this.getTransportConstructor(plan.mode))({
|
|
1419
|
+
clientSession: this.clientSession,
|
|
1420
|
+
clientSdkKey: plan.clientSdkKey,
|
|
1421
|
+
baseUrl: plan.baseUrl,
|
|
1422
|
+
resolveUrl: urlFactory,
|
|
1423
|
+
metaContext: {
|
|
1424
|
+
...clientOptions === null || clientOptions === void 0 ? void 0 : clientOptions.metadata,
|
|
1425
|
+
sdkName: sdkOptions.sdkName,
|
|
1426
|
+
sdkVersion: sdkOptions.sdkVersion,
|
|
1427
|
+
userAgent: readUserAgent(),
|
|
1428
|
+
host: readHost()
|
|
1429
|
+
},
|
|
1430
|
+
logger: this.logger,
|
|
1431
|
+
fetch: internalClientOptions === null || internalClientOptions === void 0 ? void 0 : internalClientOptions.fetch,
|
|
1432
|
+
connectionRetryDelay: (_internalClientOption3 = internalClientOptions === null || internalClientOptions === void 0 ? void 0 : internalClientOptions.connectionRetryDelay) !== null && _internalClientOption3 !== void 0 ? _internalClientOption3 : ((attempt) => {
|
|
1433
|
+
return Math.pow(2, Math.min(attempt, MAX_EXPONENTIAL_DELAY)) * 1e3;
|
|
1434
|
+
}),
|
|
1435
|
+
pollingInterval: this.pollingIntervalFor(plan.mode, clientOptions === null || clientOptions === void 0 || (_clientOptions$connec3 = clientOptions.connection) === null || _clientOptions$connec3 === void 0 ? void 0 : _clientOptions$connec3.pollingInterval)
|
|
1436
|
+
});
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
getTransportConstructor(mode) {
|
|
1440
|
+
switch (mode) {
|
|
1135
1441
|
case "polling": return PollingTransport;
|
|
1136
|
-
|
|
1442
|
+
case "streaming": return StreamingTransport;
|
|
1137
1443
|
}
|
|
1138
1444
|
}
|
|
1139
1445
|
async initialize(context) {
|
|
1140
1446
|
if (!this.hasReceivedConfigSet) this.initializing = true;
|
|
1141
|
-
await this.
|
|
1447
|
+
await this.submitContext(context, "initialization");
|
|
1142
1448
|
}
|
|
1143
1449
|
async updateContext(context) {
|
|
1144
|
-
|
|
1450
|
+
switch (this.connectionPlan.kind) {
|
|
1451
|
+
case "connect":
|
|
1452
|
+
await this.submitContext(context, "context update");
|
|
1453
|
+
return;
|
|
1454
|
+
case "stay-offline":
|
|
1455
|
+
this.storeContext(this.admittedContextFor(context, "context update"));
|
|
1456
|
+
return;
|
|
1457
|
+
}
|
|
1458
|
+
}
|
|
1459
|
+
storeContext(context) {
|
|
1460
|
+
this.currentContext = context;
|
|
1461
|
+
this.emit("contextUpdated", { context });
|
|
1462
|
+
}
|
|
1463
|
+
async submitContext(context, submission) {
|
|
1464
|
+
await this.connectToTransport(this.admittedContextFor(context, submission), submission);
|
|
1465
|
+
}
|
|
1466
|
+
admittedContextFor(suppliedContext, submission) {
|
|
1467
|
+
const admission = admitContext(suppliedContext);
|
|
1468
|
+
switch (admission.outcome) {
|
|
1469
|
+
case "admitted": return admission.context;
|
|
1470
|
+
case "ignored":
|
|
1471
|
+
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 '_'.`);
|
|
1472
|
+
this.telemetryClient.recordInvalidContext(admission.kindSample);
|
|
1473
|
+
return;
|
|
1474
|
+
}
|
|
1145
1475
|
}
|
|
1146
1476
|
get context() {
|
|
1147
1477
|
return this.currentContext;
|
|
@@ -1170,11 +1500,21 @@ var DefaultConfigDirectorClient = class {
|
|
|
1170
1500
|
if (remainingTimeout > 0) await Promise.race([this.readyPromise, new Promise((resolve) => {
|
|
1171
1501
|
this.timeoutTimer = setTimeout(() => resolve(), remainingTimeout);
|
|
1172
1502
|
})]).finally(() => clearTimeout(this.timeoutTimer));
|
|
1173
|
-
if (!this.ready) this.logger.warn(`[ConfigDirectorClient] Timed out waiting for ${caller} after ${this.timeout}ms.
|
|
1503
|
+
if (!this.ready) this.logger.warn(`[ConfigDirectorClient] Timed out waiting for ${caller} after ${this.timeout}ms. ${this.timeoutWarningDetails()}`);
|
|
1174
1504
|
} catch (error) {
|
|
1505
|
+
if (error instanceof ConfigDirectorInitializationError) {
|
|
1506
|
+
this.initializing = false;
|
|
1507
|
+
throw error;
|
|
1508
|
+
}
|
|
1175
1509
|
this.logger.error(`[ConfigDirectorClient] An error occurred during ${caller}: `, error);
|
|
1176
1510
|
}
|
|
1177
1511
|
}
|
|
1512
|
+
timeoutWarningDetails() {
|
|
1513
|
+
switch (this.connectionPlan.kind) {
|
|
1514
|
+
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.";
|
|
1515
|
+
case "stay-offline": return "The offline values were not delivered, so configs return the default value.";
|
|
1516
|
+
}
|
|
1517
|
+
}
|
|
1178
1518
|
findKeysRemovedBy(configSet) {
|
|
1179
1519
|
if (!this.configSet || configSet.kind != "full") return [];
|
|
1180
1520
|
return Object.keys(this.configSet.configs).filter((key) => !Object.prototype.hasOwnProperty.call(configSet.configs, key));
|
|
@@ -1273,17 +1613,6 @@ var DefaultConfigDirectorClient = class {
|
|
|
1273
1613
|
this.logger.error(`[ConfigDirectorClient] Error executing event handlers for '${name}'`, error);
|
|
1274
1614
|
}
|
|
1275
1615
|
}
|
|
1276
|
-
parseUrl(url, urlFactory) {
|
|
1277
|
-
if (!url) return;
|
|
1278
|
-
try {
|
|
1279
|
-
return urlFactory(url);
|
|
1280
|
-
} catch (error) {
|
|
1281
|
-
throw new ConfigDirectorValidationError(`Invalid base URL '${url}'. Parsing failed: ${error}`);
|
|
1282
|
-
}
|
|
1283
|
-
}
|
|
1284
|
-
validateSdkKeyPresence(sdkKey) {
|
|
1285
|
-
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");
|
|
1286
|
-
}
|
|
1287
1616
|
validateDefaultValue(defaultValue) {
|
|
1288
1617
|
if (defaultValue === void 0 || defaultValue === null) throw new ConfigDirectorValidationError("Invalid default value. The default value for a config must be defined and non-null.");
|
|
1289
1618
|
if (typeof defaultValue === "function") throw new ConfigDirectorValidationError("Invalid default value. The default value for a config cannot be a function.");
|
|
@@ -1309,14 +1638,24 @@ var DefaultConfigDirectorClient = class {
|
|
|
1309
1638
|
this.handlersMap.clear();
|
|
1310
1639
|
}
|
|
1311
1640
|
pauseNetwork() {
|
|
1312
|
-
this.
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1641
|
+
switch (this.connectionPlan.kind) {
|
|
1642
|
+
case "stay-offline": return;
|
|
1643
|
+
case "connect":
|
|
1644
|
+
this.logger.debug("[ConfigDirectorClient] pauseNetwork() called, pausing transport connection");
|
|
1645
|
+
clearTimeout(this.timeoutTimer);
|
|
1646
|
+
this.timeoutTimer = void 0;
|
|
1647
|
+
this.transport.close();
|
|
1648
|
+
this.ready = false;
|
|
1649
|
+
return;
|
|
1650
|
+
}
|
|
1317
1651
|
}
|
|
1318
1652
|
async resumeNetwork() {
|
|
1319
|
-
|
|
1653
|
+
switch (this.connectionPlan.kind) {
|
|
1654
|
+
case "stay-offline": return;
|
|
1655
|
+
case "connect":
|
|
1656
|
+
await this.connectToTransport(this.currentContext, "network resume");
|
|
1657
|
+
return;
|
|
1658
|
+
}
|
|
1320
1659
|
}
|
|
1321
1660
|
async close() {
|
|
1322
1661
|
var _this$readyResolve2;
|
|
@@ -1338,4 +1677,4 @@ var DefaultConfigDirectorClient = class {
|
|
|
1338
1677
|
const INSTALLED_TEST_CLIENT_SLOT = Symbol.for("@configdirector/installed-test-client");
|
|
1339
1678
|
const readInstalledTestClient = () => globalThis[INSTALLED_TEST_CLIENT_SLOT];
|
|
1340
1679
|
//#endregion
|
|
1341
|
-
export {
|
|
1680
|
+
export { discardingTelemetryClient as a, isFetchErrorFatal as c, Emitter as d, _defineProperty as f, fetchWithTimeout as i, ConfigDirectorConnectionError as l, readInstalledTestClient as n, createDefaultLogger as o, DefaultConfigDirectorClient as r, nativeValueConfigState as s, INSTALLED_TEST_CLIENT_SLOT as t, ConfigDirectorValidationError as u };
|