@configdirector/react-native-sdk 0.8.0 → 0.9.1
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.
|
@@ -26,6 +26,15 @@ interface ConfigDirectorLogger {
|
|
|
26
26
|
interface ConfigDirectorLogMessageDecorator {
|
|
27
27
|
decorateMessage(message: string): string;
|
|
28
28
|
}
|
|
29
|
+
type EvaluationReason = "found-match" | "config-state-missing" | "client-not-ready" | "type-mismatch" | "value-missing" | "invalid-number" | "invalid-json" | "invalid-boolean";
|
|
30
|
+
type ConfigEvaluation = {
|
|
31
|
+
key: string;
|
|
32
|
+
value: ConfigValueType;
|
|
33
|
+
valueId?: string;
|
|
34
|
+
isDefaultValue: boolean;
|
|
35
|
+
reason: EvaluationReason;
|
|
36
|
+
context?: ConfigDirectorContext;
|
|
37
|
+
};
|
|
29
38
|
/**
|
|
30
39
|
* The user's context to be sent to ConfigDirector. This context will be used for targeting
|
|
31
40
|
* rules evaluation.
|
|
@@ -131,8 +140,31 @@ type ConfigDirectorClientOptions = {
|
|
|
131
140
|
* );
|
|
132
141
|
*/
|
|
133
142
|
logger?: ConfigDirectorLogger;
|
|
143
|
+
/**
|
|
144
|
+
* Handlers for hooks/events emitted by the client. Each hook accepts either a single event handler or
|
|
145
|
+
* an array of event handlers.
|
|
146
|
+
*
|
|
147
|
+
* @example
|
|
148
|
+
* import { createClient, createConsoleLogger } from "@configdirector/client-sdk";
|
|
149
|
+
* const client = createClient(
|
|
150
|
+
* "YOUR-SDK-KEY",
|
|
151
|
+
* {
|
|
152
|
+
* hooks: {
|
|
153
|
+
* configEvaluated: (event) => { console.log(event.evaluation); }
|
|
154
|
+
* },
|
|
155
|
+
* },
|
|
156
|
+
* );
|
|
157
|
+
*/
|
|
158
|
+
hooks?: ClientHooks;
|
|
159
|
+
};
|
|
160
|
+
type ClientHooks = {
|
|
161
|
+
clientReady?: HookHandler<"clientReady"> | HookHandler<"clientReady">[];
|
|
162
|
+
configsUpdated?: HookHandler<"configsUpdated"> | HookHandler<"configsUpdated">[];
|
|
163
|
+
contextUpdated?: HookHandler<"contextUpdated"> | HookHandler<"contextUpdated">[];
|
|
164
|
+
configEvaluated?: HookHandler<"configEvaluated"> | HookHandler<"configEvaluated">[];
|
|
134
165
|
};
|
|
135
166
|
type ClientConnectAction = "initialization" | "context update" | "network resume";
|
|
167
|
+
type HookHandler<TEvent extends keyof ClientEvents> = (payload: ClientEvents[TEvent]) => void;
|
|
136
168
|
type ClientEvents = {
|
|
137
169
|
configsUpdated: {
|
|
138
170
|
keys: string[];
|
|
@@ -143,6 +175,9 @@ type ClientEvents = {
|
|
|
143
175
|
contextUpdated: {
|
|
144
176
|
context: ConfigDirectorContext | undefined;
|
|
145
177
|
};
|
|
178
|
+
configEvaluated: {
|
|
179
|
+
evaluation: ConfigEvaluation;
|
|
180
|
+
};
|
|
146
181
|
};
|
|
147
182
|
type WatchHandler<T extends ConfigValueType> = (message: T) => void;
|
|
148
183
|
/**
|
|
@@ -297,6 +332,7 @@ type ConfigDirectorProviderOptions = {
|
|
|
297
332
|
timeout?: number;
|
|
298
333
|
context?: ConfigDirectorContext;
|
|
299
334
|
logger?: ConfigDirectorLogger;
|
|
335
|
+
hooks?: ClientHooks;
|
|
300
336
|
netInfoSubscribe?: NetInfoSubscribe;
|
|
301
337
|
};
|
|
302
338
|
//#endregion
|
|
@@ -564,7 +564,7 @@ const parseConfigValue = (configState, defaultValue) => {
|
|
|
564
564
|
usedDefault: false,
|
|
565
565
|
reason: "found-match"
|
|
566
566
|
};
|
|
567
|
-
if (typeof defaultValue === "boolean" && configState.type === "boolean") {
|
|
567
|
+
if (typeof defaultValue === "boolean" && (configState.type === "boolean" || configState.type === "string")) {
|
|
568
568
|
const boolValue = parseConfigBoolean(value);
|
|
569
569
|
const hasBoolean = typeof boolValue === "boolean";
|
|
570
570
|
return {
|
|
@@ -586,7 +586,7 @@ const parseConfigValue = (configState, defaultValue) => {
|
|
|
586
586
|
reason: hasNumber ? "found-match" : "invalid-number"
|
|
587
587
|
};
|
|
588
588
|
}
|
|
589
|
-
if (isNumericNativeType(typeof defaultValue) && (configState.type === "float" || configState.type === "enum")) {
|
|
589
|
+
if (isNumericNativeType(typeof defaultValue) && (configState.type === "float" || configState.type === "enum" || configState.type === "string")) {
|
|
590
590
|
const numValue = parseConfigFloat(value);
|
|
591
591
|
const hasNumber = typeof numValue === "number";
|
|
592
592
|
return {
|
|
@@ -873,7 +873,7 @@ const CLIENT_BASE_URL = "https://client-sdk-api.configdirector.com";
|
|
|
873
873
|
const MAX_EXPONENTIAL_DELAY = 9;
|
|
874
874
|
var DefaultConfigDirectorClient = class {
|
|
875
875
|
constructor(telemetryClient, clientSdkKey, sdkOptions, clientOptions, internalClientOptions) {
|
|
876
|
-
var _clientOptions$logger, _clientOptions$connec, _clientOptions$connec2, _internalClientOption, _this$parseUrl, _clientOptions$connec3, _clientOptions$connec4, _clientOptions$connec5, _navigator, _internalClientOption2, _clientOptions$connec6;
|
|
876
|
+
var _clientOptions$logger, _clientOptions$connec, _clientOptions$connec2, _internalClientOption, _this$parseUrl, _clientOptions$connec3, _clientOptions$connec4, _clientOptions$connec5, _navigator, _internalClientOption2, _clientOptions$connec6, _clientOptions$hooks, _clientOptions$hooks2, _clientOptions$hooks3, _clientOptions$hooks4;
|
|
877
877
|
_defineProperty(this, "logger", void 0);
|
|
878
878
|
_defineProperty(this, "telemetryClient", void 0);
|
|
879
879
|
_defineProperty(this, "configSet", void 0);
|
|
@@ -892,6 +892,7 @@ var DefaultConfigDirectorClient = class {
|
|
|
892
892
|
this.instanceId = crypto.randomUUID();
|
|
893
893
|
this.logger = (_clientOptions$logger = clientOptions === null || clientOptions === void 0 ? void 0 : clientOptions.logger) !== null && _clientOptions$logger !== void 0 ? _clientOptions$logger : createDefaultLogger();
|
|
894
894
|
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;
|
|
895
|
+
this.validateSdkKeyPresence(clientSdkKey);
|
|
895
896
|
const urlFactory = (_internalClientOption = internalClientOptions === null || internalClientOptions === void 0 ? void 0 : internalClientOptions.urlFactory) !== null && _internalClientOption !== void 0 ? _internalClientOption : defaultUrlFactory;
|
|
896
897
|
const baseUrl = (_this$parseUrl = this.parseUrl(clientOptions === null || clientOptions === void 0 || (_clientOptions$connec3 = clientOptions.connection) === null || _clientOptions$connec3 === void 0 ? void 0 : _clientOptions$connec3.url, urlFactory)) !== null && _this$parseUrl !== void 0 ? _this$parseUrl : CLIENT_BASE_URL;
|
|
897
898
|
this.connectionMode = (_clientOptions$connec4 = clientOptions === null || clientOptions === void 0 || (_clientOptions$connec5 = clientOptions.connection) === null || _clientOptions$connec5 === void 0 ? void 0 : _clientOptions$connec5.mode) !== null && _clientOptions$connec4 !== void 0 ? _clientOptions$connec4 : "streaming";
|
|
@@ -921,18 +922,27 @@ var DefaultConfigDirectorClient = class {
|
|
|
921
922
|
const configKeys = Object.keys(configSet.configs);
|
|
922
923
|
if (!this.configSet || configSet.kind == "full") {
|
|
923
924
|
this.configSet = configSet;
|
|
924
|
-
this.
|
|
925
|
+
this.emit("configsUpdated", { keys: configKeys });
|
|
925
926
|
this.updateWatchers(configSet.configs);
|
|
926
927
|
} else {
|
|
927
928
|
this.configSet.configs = {
|
|
928
929
|
...this.configSet.configs,
|
|
929
930
|
...configSet.configs
|
|
930
931
|
};
|
|
931
|
-
this.
|
|
932
|
+
this.emit("configsUpdated", { keys: configKeys });
|
|
932
933
|
this.updateWatchers(configSet.configs);
|
|
933
934
|
}
|
|
934
935
|
this.logger.debug("[ConfigDirectorClient] ConfigSet updated from server:", { keys: configKeys });
|
|
935
936
|
});
|
|
937
|
+
this.registerHandler("clientReady", clientOptions === null || clientOptions === void 0 || (_clientOptions$hooks = clientOptions.hooks) === null || _clientOptions$hooks === void 0 ? void 0 : _clientOptions$hooks.clientReady);
|
|
938
|
+
this.registerHandler("configEvaluated", clientOptions === null || clientOptions === void 0 || (_clientOptions$hooks2 = clientOptions.hooks) === null || _clientOptions$hooks2 === void 0 ? void 0 : _clientOptions$hooks2.configEvaluated);
|
|
939
|
+
this.registerHandler("configsUpdated", clientOptions === null || clientOptions === void 0 || (_clientOptions$hooks3 = clientOptions.hooks) === null || _clientOptions$hooks3 === void 0 ? void 0 : _clientOptions$hooks3.configsUpdated);
|
|
940
|
+
this.registerHandler("contextUpdated", clientOptions === null || clientOptions === void 0 || (_clientOptions$hooks4 = clientOptions.hooks) === null || _clientOptions$hooks4 === void 0 ? void 0 : _clientOptions$hooks4.contextUpdated);
|
|
941
|
+
}
|
|
942
|
+
registerHandler(event, handler) {
|
|
943
|
+
if (!handler) return;
|
|
944
|
+
const handlers = Array.isArray(handler) ? handler : [handler];
|
|
945
|
+
for (const h of handlers) if (typeof h === "function") this.on(event, h);
|
|
936
946
|
}
|
|
937
947
|
getTransportConstructor(connectionMode) {
|
|
938
948
|
switch (connectionMode) {
|
|
@@ -959,14 +969,14 @@ var DefaultConfigDirectorClient = class {
|
|
|
959
969
|
}).then(() => {
|
|
960
970
|
this.ready = true;
|
|
961
971
|
this.initializing = false;
|
|
962
|
-
this.
|
|
972
|
+
this.emit("clientReady", { action: caller });
|
|
963
973
|
this.logger.debug("[ConfigDirectorClient] Received initial payload from the server, client is ready");
|
|
964
974
|
});
|
|
965
975
|
const startTime = (/* @__PURE__ */ new Date()).getTime();
|
|
966
976
|
await this.transport.connect(context !== null && context !== void 0 ? context : {}, this.timeout);
|
|
967
977
|
this.currentContext = context;
|
|
968
978
|
this.telemetryClient.updateContext(context);
|
|
969
|
-
this.
|
|
979
|
+
this.emit("contextUpdated", { context });
|
|
970
980
|
const elapsedTime = (/* @__PURE__ */ new Date()).getTime() - startTime;
|
|
971
981
|
const remainingTimeout = this.timeout - elapsedTime;
|
|
972
982
|
clearTimeout(this.timeoutTimer);
|
|
@@ -1016,10 +1026,11 @@ var DefaultConfigDirectorClient = class {
|
|
|
1016
1026
|
return this.getValueFromConfigState(configKey, configState, defaultValue);
|
|
1017
1027
|
}
|
|
1018
1028
|
getValueFromConfigState(configKey, configState, defaultValue) {
|
|
1019
|
-
var _this$currentContext2;
|
|
1029
|
+
var _this$currentContext2, _parseResult$parsedVa;
|
|
1020
1030
|
if (!configState) {
|
|
1021
1031
|
var _this$currentContext;
|
|
1022
1032
|
this.logger.debug(`[ConfigDirectorClient] No config state found for '${configKey}', returning default value '${defaultValue}'`);
|
|
1033
|
+
const reason = this.isReady ? "config-state-missing" : "client-not-ready";
|
|
1023
1034
|
this.telemetryClient.evaluatedConfig({
|
|
1024
1035
|
contextId: (_this$currentContext = this.currentContext) === null || _this$currentContext === void 0 ? void 0 : _this$currentContext.id,
|
|
1025
1036
|
key: configKey,
|
|
@@ -1027,7 +1038,14 @@ var DefaultConfigDirectorClient = class {
|
|
|
1027
1038
|
requestedType: getRequestedType(defaultValue),
|
|
1028
1039
|
evaluatedValue: defaultValue,
|
|
1029
1040
|
usedDefault: true,
|
|
1030
|
-
evaluationReason:
|
|
1041
|
+
evaluationReason: reason
|
|
1042
|
+
});
|
|
1043
|
+
this.dispatchEvaluationEvent({
|
|
1044
|
+
key: configKey,
|
|
1045
|
+
value: defaultValue,
|
|
1046
|
+
isDefaultValue: true,
|
|
1047
|
+
reason,
|
|
1048
|
+
context: this.context
|
|
1031
1049
|
});
|
|
1032
1050
|
return defaultValue;
|
|
1033
1051
|
}
|
|
@@ -1043,9 +1061,27 @@ var DefaultConfigDirectorClient = class {
|
|
|
1043
1061
|
usedDefault: parseResult.usedDefault,
|
|
1044
1062
|
evaluationReason: parseResult.reason
|
|
1045
1063
|
});
|
|
1064
|
+
this.dispatchEvaluationEvent({
|
|
1065
|
+
key: configKey,
|
|
1066
|
+
value: parseResult.parsedValue,
|
|
1067
|
+
valueId: (_parseResult$parsedVa = parseResult.parsedValueId) !== null && _parseResult$parsedVa !== void 0 ? _parseResult$parsedVa : void 0,
|
|
1068
|
+
isDefaultValue: parseResult.usedDefault,
|
|
1069
|
+
reason: parseResult.reason,
|
|
1070
|
+
context: this.context
|
|
1071
|
+
});
|
|
1046
1072
|
this.logger.debug(`[ConfigDirectorClient] Evaluated '${configKey}' to '${parseResult.parsedValue}'`);
|
|
1047
1073
|
return parseResult.parsedValue;
|
|
1048
1074
|
}
|
|
1075
|
+
dispatchEvaluationEvent(event) {
|
|
1076
|
+
setTimeout(() => this.emit("configEvaluated", { evaluation: event }), 0);
|
|
1077
|
+
}
|
|
1078
|
+
emit(name, payload) {
|
|
1079
|
+
try {
|
|
1080
|
+
this.eventEmitter.emit(name, payload);
|
|
1081
|
+
} catch (error) {
|
|
1082
|
+
this.logger.error(`[ConfigDirectorClient] Error executing event handlers for '${name}'`, error);
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1049
1085
|
parseUrl(url, urlFactory) {
|
|
1050
1086
|
if (!url) return;
|
|
1051
1087
|
try {
|
|
@@ -1054,6 +1090,9 @@ var DefaultConfigDirectorClient = class {
|
|
|
1054
1090
|
throw new ConfigDirectorValidationError(`Invalid base URL '${url}'. Parsing failed: ${error}`);
|
|
1055
1091
|
}
|
|
1056
1092
|
}
|
|
1093
|
+
validateSdkKeyPresence(sdkKey) {
|
|
1094
|
+
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");
|
|
1095
|
+
}
|
|
1057
1096
|
validateDefaultValue(defaultValue) {
|
|
1058
1097
|
if (defaultValue === void 0 || defaultValue === null) throw new ConfigDirectorValidationError("Invalid default value. The default value for a config must be defined and non-null.");
|
|
1059
1098
|
if (typeof defaultValue === "function") throw new ConfigDirectorValidationError("Invalid default value. The default value for a config cannot be a function.");
|
|
@@ -1530,13 +1569,12 @@ const toBase62 = (buf, length) => {
|
|
|
1530
1569
|
}
|
|
1531
1570
|
return result.padStart(length, "0");
|
|
1532
1571
|
};
|
|
1572
|
+
const BASE62_LENGTH = Math.ceil(128 / Math.log2(62));
|
|
1533
1573
|
//#endregion
|
|
1534
1574
|
//#region src/value-id-generator.ts
|
|
1535
|
-
const DIGEST_BYTES = 16;
|
|
1536
|
-
const BASE62_LENGTH = Math.ceil(DIGEST_BYTES * 8 / Math.log2(62));
|
|
1537
1575
|
const generateValueId = (v) => {
|
|
1538
1576
|
var _v$toString;
|
|
1539
|
-
return toBase62(sha256(encodeUtf8((_v$toString = v === null || v === void 0 ? void 0 : v.toString()) !== null && _v$toString !== void 0 ? _v$toString : "")).subarray(0,
|
|
1577
|
+
return toBase62(sha256(encodeUtf8((_v$toString = v === null || v === void 0 ? void 0 : v.toString()) !== null && _v$toString !== void 0 ? _v$toString : "")).subarray(0, 16), BASE62_LENGTH);
|
|
1540
1578
|
};
|
|
1541
1579
|
//#endregion
|
|
1542
1580
|
//#region src/ReactNativeTelemetryClient.ts
|
|
@@ -1597,7 +1635,7 @@ const createClient = (clientSdkKey, clientOptions) => {
|
|
|
1597
1635
|
const logger = (_clientOptions$logger = clientOptions === null || clientOptions === void 0 ? void 0 : clientOptions.logger) !== null && _clientOptions$logger !== void 0 ? _clientOptions$logger : createConsoleLogger("warn");
|
|
1598
1636
|
return new DefaultConfigDirectorClient(new ReactNativeTelemetryClient(clientSdkKey, (clientOptions === null || clientOptions === void 0 || (_clientOptions$connec = clientOptions.connection) === null || _clientOptions$connec === void 0 ? void 0 : _clientOptions$connec.url) ? urlFactory(clientOptions.connection.url) : CLIENT_BASE_URL, logger, urlFactory), clientSdkKey, {
|
|
1599
1637
|
sdkName: "react-native-sdk",
|
|
1600
|
-
sdkVersion: "0.
|
|
1638
|
+
sdkVersion: "0.9.1"
|
|
1601
1639
|
}, {
|
|
1602
1640
|
...clientOptions,
|
|
1603
1641
|
logger
|
|
@@ -1647,7 +1685,8 @@ var ConfigDirectorProvider = class extends Component {
|
|
|
1647
1685
|
appName: props.appName,
|
|
1648
1686
|
appVersion: props.appVersion
|
|
1649
1687
|
},
|
|
1650
|
-
logger: (_props$logger = props.logger) !== null && _props$logger !== void 0 ? _props$logger : createConsoleLogger("warn")
|
|
1688
|
+
logger: (_props$logger = props.logger) !== null && _props$logger !== void 0 ? _props$logger : createConsoleLogger("warn"),
|
|
1689
|
+
hooks: props.hooks
|
|
1651
1690
|
}),
|
|
1652
1691
|
status: "loading"
|
|
1653
1692
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@configdirector/react-native-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.1",
|
|
4
4
|
"description": "React Native client SDK for ConfigDirector. ConfigDirector is a remote configuration and feature flag service.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "ConfigDirector",
|
|
@@ -68,7 +68,7 @@
|
|
|
68
68
|
"@types/wtfnode": "^0",
|
|
69
69
|
"babel-jest": "^30.3.0",
|
|
70
70
|
"del-cli": "^7.0.0",
|
|
71
|
-
"eslint": "^10.
|
|
71
|
+
"eslint": "^10.7.0",
|
|
72
72
|
"jest": "^30.3.0",
|
|
73
73
|
"prettier": "^3.8.2",
|
|
74
74
|
"react": "19.1.0",
|