@configdirector/react-native-sdk 0.8.0 → 0.9.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.
@@ -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);
@@ -921,18 +921,27 @@ var DefaultConfigDirectorClient = class {
921
921
  const configKeys = Object.keys(configSet.configs);
922
922
  if (!this.configSet || configSet.kind == "full") {
923
923
  this.configSet = configSet;
924
- this.eventEmitter.emit("configsUpdated", { keys: configKeys });
924
+ this.emit("configsUpdated", { keys: configKeys });
925
925
  this.updateWatchers(configSet.configs);
926
926
  } else {
927
927
  this.configSet.configs = {
928
928
  ...this.configSet.configs,
929
929
  ...configSet.configs
930
930
  };
931
- this.eventEmitter.emit("configsUpdated", { keys: configKeys });
931
+ this.emit("configsUpdated", { keys: configKeys });
932
932
  this.updateWatchers(configSet.configs);
933
933
  }
934
934
  this.logger.debug("[ConfigDirectorClient] ConfigSet updated from server:", { keys: configKeys });
935
935
  });
936
+ this.registerHandler("clientReady", clientOptions === null || clientOptions === void 0 || (_clientOptions$hooks = clientOptions.hooks) === null || _clientOptions$hooks === void 0 ? void 0 : _clientOptions$hooks.clientReady);
937
+ this.registerHandler("configEvaluated", clientOptions === null || clientOptions === void 0 || (_clientOptions$hooks2 = clientOptions.hooks) === null || _clientOptions$hooks2 === void 0 ? void 0 : _clientOptions$hooks2.configEvaluated);
938
+ this.registerHandler("configsUpdated", clientOptions === null || clientOptions === void 0 || (_clientOptions$hooks3 = clientOptions.hooks) === null || _clientOptions$hooks3 === void 0 ? void 0 : _clientOptions$hooks3.configsUpdated);
939
+ this.registerHandler("contextUpdated", clientOptions === null || clientOptions === void 0 || (_clientOptions$hooks4 = clientOptions.hooks) === null || _clientOptions$hooks4 === void 0 ? void 0 : _clientOptions$hooks4.contextUpdated);
940
+ }
941
+ registerHandler(event, handler) {
942
+ if (!handler) return;
943
+ const handlers = Array.isArray(handler) ? handler : [handler];
944
+ for (const h of handlers) if (typeof h === "function") this.on(event, h);
936
945
  }
937
946
  getTransportConstructor(connectionMode) {
938
947
  switch (connectionMode) {
@@ -959,14 +968,14 @@ var DefaultConfigDirectorClient = class {
959
968
  }).then(() => {
960
969
  this.ready = true;
961
970
  this.initializing = false;
962
- this.eventEmitter.emit("clientReady", { action: caller });
971
+ this.emit("clientReady", { action: caller });
963
972
  this.logger.debug("[ConfigDirectorClient] Received initial payload from the server, client is ready");
964
973
  });
965
974
  const startTime = (/* @__PURE__ */ new Date()).getTime();
966
975
  await this.transport.connect(context !== null && context !== void 0 ? context : {}, this.timeout);
967
976
  this.currentContext = context;
968
977
  this.telemetryClient.updateContext(context);
969
- this.eventEmitter.emit("contextUpdated", { context });
978
+ this.emit("contextUpdated", { context });
970
979
  const elapsedTime = (/* @__PURE__ */ new Date()).getTime() - startTime;
971
980
  const remainingTimeout = this.timeout - elapsedTime;
972
981
  clearTimeout(this.timeoutTimer);
@@ -1016,10 +1025,11 @@ var DefaultConfigDirectorClient = class {
1016
1025
  return this.getValueFromConfigState(configKey, configState, defaultValue);
1017
1026
  }
1018
1027
  getValueFromConfigState(configKey, configState, defaultValue) {
1019
- var _this$currentContext2;
1028
+ var _this$currentContext2, _parseResult$parsedVa;
1020
1029
  if (!configState) {
1021
1030
  var _this$currentContext;
1022
1031
  this.logger.debug(`[ConfigDirectorClient] No config state found for '${configKey}', returning default value '${defaultValue}'`);
1032
+ const reason = this.isReady ? "config-state-missing" : "client-not-ready";
1023
1033
  this.telemetryClient.evaluatedConfig({
1024
1034
  contextId: (_this$currentContext = this.currentContext) === null || _this$currentContext === void 0 ? void 0 : _this$currentContext.id,
1025
1035
  key: configKey,
@@ -1027,7 +1037,14 @@ var DefaultConfigDirectorClient = class {
1027
1037
  requestedType: getRequestedType(defaultValue),
1028
1038
  evaluatedValue: defaultValue,
1029
1039
  usedDefault: true,
1030
- evaluationReason: "config-state-missing"
1040
+ evaluationReason: reason
1041
+ });
1042
+ this.dispatchEvaluationEvent({
1043
+ key: configKey,
1044
+ value: defaultValue,
1045
+ isDefaultValue: true,
1046
+ reason,
1047
+ context: this.context
1031
1048
  });
1032
1049
  return defaultValue;
1033
1050
  }
@@ -1043,9 +1060,27 @@ var DefaultConfigDirectorClient = class {
1043
1060
  usedDefault: parseResult.usedDefault,
1044
1061
  evaluationReason: parseResult.reason
1045
1062
  });
1063
+ this.dispatchEvaluationEvent({
1064
+ key: configKey,
1065
+ value: parseResult.parsedValue,
1066
+ valueId: (_parseResult$parsedVa = parseResult.parsedValueId) !== null && _parseResult$parsedVa !== void 0 ? _parseResult$parsedVa : void 0,
1067
+ isDefaultValue: parseResult.usedDefault,
1068
+ reason: parseResult.reason,
1069
+ context: this.context
1070
+ });
1046
1071
  this.logger.debug(`[ConfigDirectorClient] Evaluated '${configKey}' to '${parseResult.parsedValue}'`);
1047
1072
  return parseResult.parsedValue;
1048
1073
  }
1074
+ dispatchEvaluationEvent(event) {
1075
+ setTimeout(() => this.emit("configEvaluated", { evaluation: event }), 0);
1076
+ }
1077
+ emit(name, payload) {
1078
+ try {
1079
+ this.eventEmitter.emit(name, payload);
1080
+ } catch (error) {
1081
+ this.logger.error(`[ConfigDirectorClient] Error executing event handlers for '${name}'`, error);
1082
+ }
1083
+ }
1049
1084
  parseUrl(url, urlFactory) {
1050
1085
  if (!url) return;
1051
1086
  try {
@@ -1530,13 +1565,12 @@ const toBase62 = (buf, length) => {
1530
1565
  }
1531
1566
  return result.padStart(length, "0");
1532
1567
  };
1568
+ const BASE62_LENGTH = Math.ceil(128 / Math.log2(62));
1533
1569
  //#endregion
1534
1570
  //#region src/value-id-generator.ts
1535
- const DIGEST_BYTES = 16;
1536
- const BASE62_LENGTH = Math.ceil(DIGEST_BYTES * 8 / Math.log2(62));
1537
1571
  const generateValueId = (v) => {
1538
1572
  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, DIGEST_BYTES), BASE62_LENGTH);
1573
+ 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
1574
  };
1541
1575
  //#endregion
1542
1576
  //#region src/ReactNativeTelemetryClient.ts
@@ -1597,7 +1631,7 @@ const createClient = (clientSdkKey, clientOptions) => {
1597
1631
  const logger = (_clientOptions$logger = clientOptions === null || clientOptions === void 0 ? void 0 : clientOptions.logger) !== null && _clientOptions$logger !== void 0 ? _clientOptions$logger : createConsoleLogger("warn");
1598
1632
  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
1633
  sdkName: "react-native-sdk",
1600
- sdkVersion: "0.8.0"
1634
+ sdkVersion: "0.9.0"
1601
1635
  }, {
1602
1636
  ...clientOptions,
1603
1637
  logger
@@ -1647,7 +1681,8 @@ var ConfigDirectorProvider = class extends Component {
1647
1681
  appName: props.appName,
1648
1682
  appVersion: props.appVersion
1649
1683
  },
1650
- logger: (_props$logger = props.logger) !== null && _props$logger !== void 0 ? _props$logger : createConsoleLogger("warn")
1684
+ logger: (_props$logger = props.logger) !== null && _props$logger !== void 0 ? _props$logger : createConsoleLogger("warn"),
1685
+ hooks: props.hooks
1651
1686
  }),
1652
1687
  status: "loading"
1653
1688
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@configdirector/react-native-sdk",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
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",