@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 +4 -113
- package/dist/configdirector-react-native.d.mts +18 -9
- package/dist/configdirector-react-native.mjs +145 -49
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -1,121 +1,12 @@
|
|
|
1
1
|
# ConfigDirector React Native SDK
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
This is the React Native SDK for [ConfigDirector](https://www.configdirector.com).
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## Documentation
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Refer to the [official documentation for the React Native SDK](https://docs.configdirector.com/sdks/mobile/react-native).
|
|
8
8
|
|
|
9
|
-
|
|
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
|
-
*
|
|
80
|
-
* If set to
|
|
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
|
|
83
|
-
* initialization, and
|
|
84
|
-
*
|
|
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
|
|
88
|
+
* Defaults to `streaming`
|
|
87
89
|
*/
|
|
88
|
-
|
|
90
|
+
mode?: ConnectionMode;
|
|
89
91
|
/**
|
|
90
|
-
* The
|
|
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
|
-
|
|
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("[
|
|
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
|
-
|
|
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("[
|
|
478
|
+
this.logger.debug("[StreamingTransport] Error: ", error);
|
|
477
479
|
},
|
|
478
480
|
onDisconnect: () => {
|
|
479
|
-
this.logger.debug("[
|
|
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("[
|
|
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/
|
|
704
|
-
var
|
|
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, "
|
|
793
|
+
_defineProperty(this, "lastUpdateTimestamp", void 0);
|
|
711
794
|
this.options = options;
|
|
712
795
|
this.logger = options.logger;
|
|
713
|
-
this.url = options.resolveUrl("client/
|
|
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("[
|
|
718
|
-
return
|
|
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
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
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
|
-
|
|
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
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
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, "
|
|
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.
|
|
799
|
-
const transportConstructor = this.
|
|
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(
|
|
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(
|
|
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.
|
|
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.
|
|
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": "
|
|
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.
|
|
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": ">=
|
|
81
|
+
"expo": ">=52",
|
|
82
82
|
"react": "*",
|
|
83
|
-
"react-native": ">=0.
|
|
83
|
+
"react-native": ">=0.75"
|
|
84
84
|
},
|
|
85
85
|
"peerDependenciesMeta": {
|
|
86
86
|
"expo": {
|