@configdirector/react-native-sdk 0.2.0 → 0.5.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 CHANGED
@@ -1,121 +1,12 @@
1
1
  # ConfigDirector React Native SDK
2
2
 
3
- ## Getting started
3
+ This is the React Native SDK for [ConfigDirector](https://www.configdirector.com).
4
4
 
5
- ### 1. Install
5
+ ## Documentation
6
6
 
7
- Install from NPM:
7
+ Refer to the [official documentation for the React Native SDK](https://docs.configdirector.com/sdks/mobile/react-native).
8
8
 
9
- ```bash
10
- npm install --save @configdirector/react-native-sdk
11
- ```
12
-
13
- ### 2. Initialize the provider using your client SDK key
14
-
15
- ```tsx
16
- import { View } from "react-native";
17
- import AwesomeApp from "./AwesomeApp";
18
- import { ConfigDirectorProvider } from "@configdirector/react-native-sdk";
19
-
20
- export default function App() {
21
- return (
22
- <ConfigDirectorProvider
23
- sdkKey={"YOUR-CLIENT-SDK-KEY"}
24
- appName="MyAwesomeApp"
25
- appVersion="1.0.0">
26
- <View>
27
- <AwesomeApp />
28
- </View>
29
- </ConfigDirectorProvider>
30
- );
31
- }
32
- ```
33
-
34
- ### 3. Retrieve config values
35
-
36
- Retrieve a config value with the `useConfigValue` hook:
37
-
38
- ```tsx
39
- import { useConfigValue } from "@configdirector/react-native-sdk";
40
-
41
- function MyComponent() {
42
- const { value } = useConfigValue<string>("my-config", "default value");
43
-
44
- return <p>My config value: {value}</p>;
45
- }
46
-
47
- export default MyComponent;
48
- ```
49
-
50
- You can also determine if the client is still initializing, so rather than transition from the default value to the evaluated value, you can show a loading state until the client is ready and config values are evaluated:
51
-
52
- ```tsx
53
- import { useConfigValue } from "@configdirector/react-native-sdk";
54
-
55
- function MyComponent() {
56
- const { value, loading } = useConfigValue<string>(
57
- "my-config",
58
- "default value",
59
- );
60
-
61
- if (loading) {
62
- return <p>Loading...</p>;
63
- } else {
64
- return <p>My config value: {value}</p>;
65
- }
66
- }
67
-
68
- export default MyComponent;
69
- ```
70
-
71
- ### 4. Updating the user context
72
-
73
- Update the user context with the `useContext` hook:
74
-
75
- ```tsx
76
- import {
77
- useConfigValue,
78
- useContext,
79
- } from "@configdirector/react-native-sdk";
80
-
81
- function MyComponent() {
82
- const { value: myConfigValue } = useConfigValue<string>(
83
- "my-config",
84
- "default value",
85
- );
86
- const { updateContext } = useContext();
87
-
88
- useEffect(() => {
89
- updateContext({
90
- id: "54321",
91
- name: "Another User",
92
- traits: { customProperty: "customValue" },
93
- });
94
- }, []);
95
-
96
- return <p>My config value: {myConfigValue}</p>;
97
- }
98
-
99
- export default MyComponent;
100
- ```
101
-
102
- ## Requirements
103
-
104
- - React Native **0.71 or later** (requires `ReadableStream` support)
105
- - **Hermes** is strongly recommended. JavaScriptCore (JSC) is supported via a built-in UTF-8 fallback, but Hermes is the default engine in RN 0.70+ and provides better performance.
106
-
107
- ## Expo
108
-
109
- This SDK is compatible with both **Expo managed workflow** and **bare workflow**.
110
-
111
- - In the **managed workflow**, Hermes is the default engine — no additional setup needed.
112
- - In the **bare workflow**, ensure Hermes is enabled in your `Podfile` (iOS) and `gradle.properties` (Android), which is the default in Expo SDK 47+.
113
-
114
- Expo SDK 48 (RN 0.71) or later is required.
115
-
116
- ## License
117
-
118
- MIT
9
+ There is also [a quickstart guide for ConfigDirector and any of our SDKs](https://docs.configdirector.com/getting-started/quickstart).
119
10
 
120
11
  ## Getting Help
121
12
 
@@ -61,6 +61,7 @@ type ConfigDirectorMetaContext = {
61
61
  appVersion?: string;
62
62
  appName?: string;
63
63
  };
64
+ type ConnectionMode = "streaming" | "polling" | "one-time";
64
65
  //#endregion
65
66
  //#region ../js-client-core/src/types.d.ts
66
67
  /**
@@ -76,18 +77,26 @@ type ConfigDirectorClientOptions = {
76
77
  */
77
78
  connection?: {
78
79
  /**
79
- * Whether to open a streaming connection or use a one-time pull of configuration state.
80
- * If set to true, the streaming connection will remain open and receive updates whenever
80
+ * The connection mode to be used, one of `streaming` (default), `polling`, or `one-time`.
81
+ * If set to `streaming`, the connection will remain open and receive updates whenever
81
82
  * config state is updated on the ConfigDirector dashboard.
82
- * When set to false, there will be an initial request to retrieve config state during
83
- * initialization, and an additional request whenever {@link ConfigDirectorClient.updateContext}
84
- * is called. But not updates will be received after those requests.
83
+ * When set to `polling`, there will be an initial request to retrieve config state during
84
+ * initialization, and additional requests on a `pollingInterval`.
85
+ * The `one-time` connection mode will only retrieve config state during initialization and
86
+ * context updates. It will not poll for regular updates.
85
87
  *
86
- * Defaults to true (streaming connection)
88
+ * Defaults to `streaming`
87
89
  */
88
- streaming?: boolean;
90
+ mode?: ConnectionMode;
89
91
  /**
90
- * The timeout, in milliseconds, to be used in initialization and when updating the context.
92
+ * The polling interval in _seconds_ when the `mode` is set to `polling`. This option has no
93
+ * effect when the `mode` is set to `streaming` or `one-time`.
94
+ *
95
+ * Defaults to 60 seconds
96
+ */
97
+ pollingInterval?: number;
98
+ /**
99
+ * The timeout, in _milliseconds_, to be used in initialization and when updating the context.
91
100
  * If streaming is enabled, the operation (initialization or context update) may still succeed
92
101
  * after it times out if no unrecoverable errors are encountered (like an invalid SDK key).
93
102
  * If streaming is disabled, if the operation times out, it will not be retried.
@@ -246,7 +255,7 @@ declare class DefaultConsoleLogger implements ConfigDirectorLogger {
246
255
  info(message: string, ...args: any): void;
247
256
  warn(message: string, ...args: any): void;
248
257
  error(message: string, ...args: any): void;
249
- private log;
258
+ protected log(loggerFunction: (...args: any) => void, level: ConfigDirectorLoggingLevel, message: string, ...args: any): void;
250
259
  }
251
260
  //#endregion
252
261
  //#region src/types.d.ts
@@ -459,7 +459,7 @@ var StreamingTransport = class {
459
459
  this.dispatchMessage(data);
460
460
  },
461
461
  onConnect: () => {
462
- this.logger.debug("[EventSourceTransport] Connected");
462
+ this.logger.debug("[StreamingTransport] Connected");
463
463
  resolve(this);
464
464
  },
465
465
  shouldReconnect: (state) => {
@@ -469,14 +469,16 @@ var StreamingTransport = class {
469
469
  },
470
470
  calculateReconnectDelay: (state) => {
471
471
  const delay = this.options.connectionRetryDelay(state.attempt);
472
- this.logger.warn(`[EventSourceTransport] Scheduling reconnect in ${delay}ms.`);
472
+ const logMessage = `[StreamingTransport] Scheduling reconnect attempt #${state.attempt} in ${delay}ms.`;
473
+ if (state.attempt <= 5) this.logger.info(logMessage);
474
+ else this.logger.warn(logMessage);
473
475
  return delay;
474
476
  },
475
477
  onError: (error) => {
476
- this.logger.debug("[EventSourceTransport] Error: ", error);
478
+ this.logger.debug("[StreamingTransport] Error: ", error);
477
479
  },
478
480
  onDisconnect: () => {
479
- this.logger.debug("[EventSourceTransport] Disconnected");
481
+ this.logger.debug("[StreamingTransport] Disconnected");
480
482
  }
481
483
  });
482
484
  this.eventSource.connect();
@@ -492,7 +494,7 @@ var StreamingTransport = class {
492
494
  const json = JSON.parse(data);
493
495
  this.eventEmitter.emit("configSetReceived", json);
494
496
  } catch (error) {
495
- this.logger.error("[EventSourceTransport] Error parsing and dispatching config data update: ", error);
497
+ this.logger.error("[StreamingTransport] Error parsing and dispatching config data update: ", error);
496
498
  }
497
499
  }
498
500
  prepareFatalError(responseStatus, error) {
@@ -551,6 +553,7 @@ const parseConfigValue = (configState, defaultValue) => {
551
553
  usedDefault: true,
552
554
  reason: "value-missing"
553
555
  };
556
+ if (configState.type === "json") return parseJson(value, defaultValue, requestedType);
554
557
  if (typeof defaultValue === "string") return {
555
558
  parsedValue: value,
556
559
  requestedType,
@@ -612,6 +615,29 @@ const parseConfigFloat = (value) => {
612
615
  if (isNaN(num) || !isFinite(num)) return;
613
616
  return num;
614
617
  };
618
+ const parseJson = (jsonString, defaultValue, requestedType) => {
619
+ if (typeof defaultValue === "string") return {
620
+ parsedValue: jsonString,
621
+ requestedType,
622
+ usedDefault: false,
623
+ reason: "found-match"
624
+ };
625
+ try {
626
+ return {
627
+ parsedValue: JSON.parse(jsonString),
628
+ requestedType,
629
+ usedDefault: false,
630
+ reason: "found-match"
631
+ };
632
+ } catch {
633
+ return {
634
+ parsedValue: defaultValue,
635
+ requestedType,
636
+ usedDefault: true,
637
+ reason: "invalid-json"
638
+ };
639
+ }
640
+ };
615
641
  //#endregion
616
642
  //#region ../shared/src/logger.ts
617
643
  const LEVELS = {
@@ -679,6 +705,61 @@ const createDefaultLogger = (level, messageDecorator) => {
679
705
  return new DefaultConsoleLogger(level !== null && level !== void 0 ? level : "warn", messageDecorator !== null && messageDecorator !== void 0 ? messageDecorator : new LogMessageDecorator());
680
706
  };
681
707
  //#endregion
708
+ //#region ../shared/src/transport/AbstractPollingTransport.ts
709
+ var AbstractPollingTransport = class {
710
+ constructor() {
711
+ _defineProperty(this, "fatalError", false);
712
+ _defineProperty(this, "pollingInterval", void 0);
713
+ _defineProperty(this, "pollingIntervalSeconds", 60);
714
+ }
715
+ clearPollingInterval() {
716
+ clearInterval(this.pollingInterval);
717
+ }
718
+ schedulePollingInterval(callback) {
719
+ clearInterval(this.pollingInterval);
720
+ if (this.pollingIntervalSeconds <= 0) return;
721
+ this.pollingInterval = setInterval(callback, this.pollingIntervalSeconds * 1e3);
722
+ }
723
+ async handleNonOkResponse(response) {
724
+ if (!response.ok) if (this.isStatusFatal(response.status)) {
725
+ this.fatalError = true;
726
+ throw this.prepareFatalResponseStatusError(response.status, await response.text());
727
+ } else throw new ConfigDirectorConnectionError(`Connection failed with status: ${response.status}`, response.status);
728
+ }
729
+ handleFetchError(fetchError) {
730
+ if (this.isFetchErrorFatal(fetchError)) {
731
+ this.fatalError = true;
732
+ this.close();
733
+ throw new ConfigDirectorConnectionError(`Connection failed with fatal error: ${fetchError}. This is an unrecoverable error, retry attempts will be ignored.`);
734
+ } else if (fetchError instanceof SyntaxError) throw new ConfigDirectorConnectionError(`Failed to parse the response from the server: ${fetchError}`);
735
+ else throw new ConfigDirectorConnectionError(`Connection failed with error: ${fetchError}.`);
736
+ }
737
+ prepareFatalResponseStatusError(responseStatus, errorBody) {
738
+ var _errorBody$trim$lengt, _errorBody$trim;
739
+ const status = responseStatus !== null && responseStatus !== void 0 ? responseStatus : 0;
740
+ return new ConfigDirectorConnectionError(`${`Connection failed with status: ${responseStatus !== null && responseStatus !== void 0 ? responseStatus : "unknown"}`}${((_errorBody$trim$lengt = errorBody === null || errorBody === void 0 || (_errorBody$trim = errorBody.trim()) === null || _errorBody$trim === void 0 ? void 0 : _errorBody$trim.length) !== null && _errorBody$trim$lengt !== void 0 ? _errorBody$trim$lengt : 0) > 0 ? ` (${errorBody})` : ""}. This is an unrecoverable error, retry attempts will be ignored.`, status);
741
+ }
742
+ isStatusFatal(status) {
743
+ return !!status && status >= 400 && status < 500;
744
+ }
745
+ isFetchErrorFatal(fetchError) {
746
+ if ((fetchError === null || fetchError === void 0 ? void 0 : fetchError.name) === "NotAllowedError") return true;
747
+ else if (fetchError instanceof TypeError) return true;
748
+ return false;
749
+ }
750
+ get isConnected() {
751
+ return this.pollingInterval !== void 0;
752
+ }
753
+ close() {
754
+ clearInterval(this.pollingInterval);
755
+ this.pollingInterval = void 0;
756
+ }
757
+ dispose() {
758
+ this.close();
759
+ this.clear();
760
+ }
761
+ };
762
+ //#endregion
682
763
  //#region ../shared/src/fetchWithTimeout.ts
683
764
  const fetchWithTimeout = async (timeout, resource, options, logger) => {
684
765
  const abortController = new AbortController();
@@ -700,22 +781,35 @@ const fetchWithTimeout = async (timeout, resource, options, logger) => {
700
781
  }
701
782
  };
702
783
  //#endregion
703
- //#region ../js-client-core/src/PullTransport.ts
704
- var PullTransport = class {
784
+ //#region ../js-client-core/src/PollingTransport.ts
785
+ var PollingTransport = class extends AbstractPollingTransport {
705
786
  constructor(options) {
787
+ var _options$pollingInter;
788
+ super();
706
789
  this.options = options;
707
790
  _defineProperty(this, "logger", void 0);
708
791
  _defineProperty(this, "eventEmitter", new Emitter());
709
792
  _defineProperty(this, "url", void 0);
710
- _defineProperty(this, "fatalError", false);
793
+ _defineProperty(this, "lastUpdateTimestamp", void 0);
711
794
  this.options = options;
712
795
  this.logger = options.logger;
713
- this.url = options.resolveUrl("client/pull/v1", options.baseUrl);
796
+ this.url = options.resolveUrl("client/polling/v1", options.baseUrl);
797
+ this.pollingIntervalSeconds = (_options$pollingInter = options.pollingInterval) !== null && _options$pollingInter !== void 0 ? _options$pollingInter : 60;
714
798
  }
715
799
  async connect(context, timeout) {
800
+ this.clearPollingInterval();
801
+ await this.fetchConfigs(context, timeout);
802
+ this.schedulePollingInterval(() => {
803
+ this.fetchConfigs(context, timeout).catch((error) => {
804
+ this.logger.error("[PollingTransport] Error during polling:", error);
805
+ });
806
+ });
807
+ return this;
808
+ }
809
+ async fetchConfigs(context, timeout) {
716
810
  if (this.fatalError) {
717
- this.logger.warn("[PullTransport] There was a prior unrecoverable error. Ignoring attempt to reconnect.");
718
- return this;
811
+ this.logger.warn("[PollingTransport] There was a prior unrecoverable error. Ignoring attempt to reconnect.");
812
+ return;
719
813
  }
720
814
  try {
721
815
  const response = await fetchWithTimeout(timeout, this.url, {
@@ -724,32 +818,20 @@ var PullTransport = class {
724
818
  body: JSON.stringify({
725
819
  givenContext: context,
726
820
  metaContext: this.options.metaContext,
727
- clientSdkKey: this.options.clientSdkKey
821
+ clientSdkKey: this.options.clientSdkKey,
822
+ lastUpdateTimestamp: this.lastUpdateTimestamp
728
823
  })
729
824
  }, this.logger);
730
- if (!response.ok) if (this.isStatusFatal(response.status)) {
731
- this.fatalError = true;
732
- throw this.prepareFatalResponseStatusError(response.status, await response.text());
733
- } else throw new ConfigDirectorConnectionError(`Connection failed with status: ${response.status}`, response.status);
734
- const json = JSON.parse(await response.text());
735
- this.eventEmitter.emit("configSetReceived", json);
736
- return this;
825
+ await this.handleNonOkResponse(response);
826
+ if (response.status == 200) {
827
+ const json = JSON.parse(await response.text());
828
+ this.lastUpdateTimestamp = json.timestamp;
829
+ this.eventEmitter.emit("configSetReceived", json);
830
+ }
737
831
  } catch (fetchError) {
738
- if (isFetchErrorFatal(fetchError)) {
739
- this.fatalError = true;
740
- throw new ConfigDirectorConnectionError(`Connection failed with fatal error: ${fetchError}. This is an unrecoverable error, retry attempts will be ignored.`);
741
- } else if (fetchError instanceof SyntaxError) throw new ConfigDirectorConnectionError(`Failed to parse the response from the server: ${fetchError}`);
742
- else throw new ConfigDirectorConnectionError(`Connection failed with error: ${fetchError}.`);
832
+ this.handleFetchError(fetchError);
743
833
  }
744
834
  }
745
- prepareFatalResponseStatusError(responseStatus, errorBody) {
746
- var _errorBody$trim$lengt, _errorBody$trim;
747
- const status = responseStatus !== null && responseStatus !== void 0 ? responseStatus : 0;
748
- return new ConfigDirectorConnectionError(`${`Connection failed with status: ${responseStatus !== null && responseStatus !== void 0 ? responseStatus : "unknown"}`}${((_errorBody$trim$lengt = errorBody === null || errorBody === void 0 || (_errorBody$trim = errorBody.trim()) === null || _errorBody$trim === void 0 ? void 0 : _errorBody$trim.length) !== null && _errorBody$trim$lengt !== void 0 ? _errorBody$trim$lengt : 0) > 0 ? ` (${errorBody})` : ""}. This is an unrecoverable error, retry attempts will be ignored.`, status);
749
- }
750
- isStatusFatal(status) {
751
- return !!status && status >= 400 && status < 500;
752
- }
753
835
  on(name, handler) {
754
836
  this.eventEmitter.on(name, handler);
755
837
  }
@@ -759,10 +841,13 @@ var PullTransport = class {
759
841
  clear() {
760
842
  this.eventEmitter.clear();
761
843
  }
762
- close() {}
763
- dispose() {
764
- this.close();
765
- this.clear();
844
+ };
845
+ //#endregion
846
+ //#region ../js-client-core/src/OneTimeTransport.ts
847
+ var OneTimeTransport = class extends PollingTransport {
848
+ constructor(options) {
849
+ super(options);
850
+ this.pollingIntervalSeconds = 0;
766
851
  }
767
852
  };
768
853
  //#endregion
@@ -776,7 +861,7 @@ const CLIENT_BASE_URL = "https://client-sdk-api.configdirector.com";
776
861
  const MAX_EXPONENTIAL_DELAY = 9;
777
862
  var DefaultConfigDirectorClient = class {
778
863
  constructor(telemetryClient, clientSdkKey, sdkOptions, clientOptions, internalClientOptions) {
779
- var _clientOptions$logger, _clientOptions$connec, _clientOptions$connec2, _internalClientOption, _this$parseUrl, _clientOptions$connec3, _clientOptions$connec4, _navigator, _internalClientOption2;
864
+ var _clientOptions$logger, _clientOptions$connec, _clientOptions$connec2, _internalClientOption, _this$parseUrl, _clientOptions$connec3, _clientOptions$connec4, _clientOptions$connec5, _navigator, _internalClientOption2, _clientOptions$connec6;
780
865
  _defineProperty(this, "logger", void 0);
781
866
  _defineProperty(this, "telemetryClient", void 0);
782
867
  _defineProperty(this, "configSet", void 0);
@@ -790,13 +875,13 @@ var DefaultConfigDirectorClient = class {
790
875
  _defineProperty(this, "readyPromise", void 0);
791
876
  _defineProperty(this, "readyResolve", void 0);
792
877
  _defineProperty(this, "currentContext", void 0);
793
- _defineProperty(this, "streaming", void 0);
878
+ _defineProperty(this, "connectionMode", void 0);
794
879
  this.logger = (_clientOptions$logger = clientOptions === null || clientOptions === void 0 ? void 0 : clientOptions.logger) !== null && _clientOptions$logger !== void 0 ? _clientOptions$logger : createDefaultLogger();
795
880
  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;
796
881
  const urlFactory = (_internalClientOption = internalClientOptions === null || internalClientOptions === void 0 ? void 0 : internalClientOptions.urlFactory) !== null && _internalClientOption !== void 0 ? _internalClientOption : defaultUrlFactory;
797
882
  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;
798
- this.streaming = (clientOptions === null || clientOptions === void 0 || (_clientOptions$connec4 = clientOptions.connection) === null || _clientOptions$connec4 === void 0 ? void 0 : _clientOptions$connec4.streaming) === false ? false : true;
799
- const transportConstructor = this.streaming ? StreamingTransport : PullTransport;
883
+ 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";
884
+ const transportConstructor = this.getTransportConstructor(this.connectionMode);
800
885
  this.telemetryClient = telemetryClient;
801
886
  this.transport = new transportConstructor({
802
887
  clientSdkKey,
@@ -812,7 +897,8 @@ var DefaultConfigDirectorClient = class {
812
897
  fetch: internalClientOptions === null || internalClientOptions === void 0 ? void 0 : internalClientOptions.fetch,
813
898
  connectionRetryDelay: (_internalClientOption2 = internalClientOptions === null || internalClientOptions === void 0 ? void 0 : internalClientOptions.connectionRetryDelay) !== null && _internalClientOption2 !== void 0 ? _internalClientOption2 : ((attempt) => {
814
899
  return Math.pow(2, Math.min(attempt, MAX_EXPONENTIAL_DELAY)) * 1e3;
815
- })
900
+ }),
901
+ pollingInterval: clientOptions === null || clientOptions === void 0 || (_clientOptions$connec6 = clientOptions.connection) === null || _clientOptions$connec6 === void 0 ? void 0 : _clientOptions$connec6.pollingInterval
816
902
  });
817
903
  this.transport.on("configSetReceived", (configSet) => {
818
904
  var _this$readyResolve;
@@ -833,6 +919,13 @@ var DefaultConfigDirectorClient = class {
833
919
  this.logger.debug("[ConfigDirectorClient] ConfigSet updated from server:", { keys: configKeys });
834
920
  });
835
921
  }
922
+ getTransportConstructor(connectionMode) {
923
+ switch (connectionMode) {
924
+ case "one-time": return OneTimeTransport;
925
+ case "polling": return PollingTransport;
926
+ default: return StreamingTransport;
927
+ }
928
+ }
836
929
  async initialize(context) {
837
930
  this.initializing = true;
838
931
  await this.connectToTransport(context, "initialization");
@@ -866,10 +959,7 @@ var DefaultConfigDirectorClient = class {
866
959
  if (remainingTimeout > 0) await Promise.race([this.readyPromise, new Promise((resolve) => {
867
960
  this.timeoutTimer = setTimeout(() => resolve(), remainingTimeout);
868
961
  })]).finally(() => clearTimeout(this.timeoutTimer));
869
- if (!this.ready) {
870
- const warningDetails = this.streaming ? "The client will continue to retry since there were no fatal errors detected. Configs will return the default value until the connection succeeds." : "Since the client was configured without streaming, configs may not update and always return the default value.";
871
- this.logger.warn(`[ConfigDirectorClient] Timed out waiting for ${caller} after ${this.timeout}ms. ${warningDetails}`);
872
- }
962
+ if (!this.ready) this.logger.warn(`[ConfigDirectorClient] Timed out waiting for ${caller} after ${this.timeout}ms. The client will continue to retry since there were no fatal errors detected. Configs will return the default value until the connection succeeds.`);
873
963
  } catch (error) {
874
964
  this.logger.error(`[ConfigDirectorClient] An error occurred during ${caller}: `, error);
875
965
  }
@@ -929,6 +1019,7 @@ var DefaultConfigDirectorClient = class {
929
1019
  const parseResult = parseConfigValue(configState, defaultValue);
930
1020
  this.telemetryClient.evaluatedConfig({
931
1021
  contextId: (_this$currentContext2 = this.currentContext) === null || _this$currentContext2 === void 0 ? void 0 : _this$currentContext2.id,
1022
+ type: configState.type,
932
1023
  key: configKey,
933
1024
  defaultValue,
934
1025
  requestedType: parseResult.requestedType,
@@ -999,7 +1090,7 @@ var DefaultConfigDirectorClient = class {
999
1090
  //#region ../shared/src/telemetry/utils.ts
1000
1091
  const CONFIG_VALUE_MAX_LENGTH = 500;
1001
1092
  const sanitizeValue = (value, type) => {
1002
- if (type === "json") try {
1093
+ if (type === "json" || type == null && typeof value === "object") try {
1003
1094
  return djb2Hash(JSON.stringify(value));
1004
1095
  } catch {
1005
1096
  return value.toString().slice(0, CONFIG_VALUE_MAX_LENGTH);
@@ -1303,6 +1394,11 @@ const encodeManually = (str) => {
1303
1394
  };
1304
1395
  //#endregion
1305
1396
  //#region src/reactNativeStreamingFetch.ts
1397
+ const abortError = (message) => {
1398
+ const err = new Error(message);
1399
+ err.name = "AbortError";
1400
+ return err;
1401
+ };
1306
1402
  /**
1307
1403
  * A fetch-compatible function for React Native that uses XMLHttpRequest to
1308
1404
  * support streaming SSE responses. React Native's built-in fetch accumulates
@@ -1312,7 +1408,7 @@ const encodeManually = (str) => {
1312
1408
  */
1313
1409
  const reactNativeStreamingFetch = (url, init) => {
1314
1410
  var _init$signal;
1315
- if ((_init$signal = init.signal) === null || _init$signal === void 0 ? void 0 : _init$signal.aborted) return Promise.reject(new DOMException("Request was aborted before it was opened", "AbortError"));
1411
+ if ((_init$signal = init.signal) === null || _init$signal === void 0 ? void 0 : _init$signal.aborted) return Promise.reject(abortError("Request was aborted before it was opened"));
1316
1412
  return new Promise((resolve, reject) => {
1317
1413
  var _init$method, _ref;
1318
1414
  const xhr = new XMLHttpRequest();
@@ -1356,7 +1452,7 @@ const reactNativeStreamingFetch = (url, init) => {
1356
1452
  };
1357
1453
  if (init.signal) init.signal.addEventListener("abort", () => {
1358
1454
  xhr.abort();
1359
- if (!responseResolved) reject(new DOMException("Request was aborted", "AbortError"));
1455
+ if (!responseResolved) reject(abortError("Request was aborted"));
1360
1456
  else try {
1361
1457
  streamController.close();
1362
1458
  } catch {}
@@ -1423,7 +1519,7 @@ const createClient = (clientSdkKey, clientOptions) => {
1423
1519
  const logger = (_clientOptions$logger = clientOptions === null || clientOptions === void 0 ? void 0 : clientOptions.logger) !== null && _clientOptions$logger !== void 0 ? _clientOptions$logger : createConsoleLogger("warn");
1424
1520
  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, {
1425
1521
  sdkName: "react-native-sdk",
1426
- sdkVersion: "0.2.0"
1522
+ sdkVersion: "0.5.0"
1427
1523
  }, {
1428
1524
  ...clientOptions,
1429
1525
  logger
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@configdirector/react-native-sdk",
3
- "version": "0.2.0",
3
+ "version": "0.5.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",
@@ -21,7 +21,7 @@
21
21
  "homepage": "https://www.configdirector.com",
22
22
  "repository": {
23
23
  "type": "git",
24
- "url": "https://github.com/ConfigDirector/js-sdks"
24
+ "url": "git+https://github.com/ConfigDirector/js-sdks.git"
25
25
  },
26
26
  "main": "./dist/configdirector-react-native.mjs",
27
27
  "types": "./dist/configdirector-react-native.d.mts",
@@ -39,7 +39,7 @@
39
39
  ],
40
40
  "scripts": {
41
41
  "example": "yarn workspace @configdirector/react-native-sdk-example",
42
- "clean": "del-cli dist",
42
+ "clean": "rm -fr dist",
43
43
  "build": "tsdown",
44
44
  "prepare": "yarn build",
45
45
  "typecheck": "tsc",
@@ -72,15 +72,15 @@
72
72
  "react-native": "0.81.5",
73
73
  "react-test-renderer": "19.1.0",
74
74
  "tsdown": "^0.21.7",
75
- "turbo": "^2.8.21",
75
+ "turbo": "^2.9.16",
76
76
  "typescript": "^5.9.3",
77
77
  "typescript-eslint": "^8.58.1",
78
78
  "wtfnode": "^0.10.1"
79
79
  },
80
80
  "peerDependencies": {
81
- "expo": ">=48",
81
+ "expo": ">=52",
82
82
  "react": "*",
83
- "react-native": ">=0.71"
83
+ "react-native": ">=0.75"
84
84
  },
85
85
  "peerDependenciesMeta": {
86
86
  "expo": {