@configdirector/react-native-sdk 0.1.0 → 0.2.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 +85 -2
- package/dist/configdirector-react-native.d.mts +20 -3
- package/dist/configdirector-react-native.mjs +201 -155
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -10,15 +10,94 @@ Install from NPM:
|
|
|
10
10
|
npm install --save @configdirector/react-native-sdk
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
+
### 2. Initialize the provider using your client SDK key
|
|
13
14
|
|
|
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
|
+
```
|
|
15
33
|
|
|
34
|
+
### 3. Retrieve config values
|
|
35
|
+
|
|
36
|
+
Retrieve a config value with the `useConfigValue` hook:
|
|
16
37
|
|
|
17
38
|
```tsx
|
|
18
|
-
import {
|
|
39
|
+
import { useConfigValue } from "@configdirector/react-native-sdk";
|
|
40
|
+
|
|
41
|
+
function MyComponent() {
|
|
42
|
+
const { value } = useConfigValue<string>("my-config", "default value");
|
|
19
43
|
|
|
44
|
+
return <p>My config value: {value}</p>;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export default MyComponent;
|
|
20
48
|
```
|
|
21
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
|
+
```
|
|
22
101
|
|
|
23
102
|
## Requirements
|
|
24
103
|
|
|
@@ -37,3 +116,7 @@ Expo SDK 48 (RN 0.71) or later is required.
|
|
|
37
116
|
## License
|
|
38
117
|
|
|
39
118
|
MIT
|
|
119
|
+
|
|
120
|
+
## Getting Help
|
|
121
|
+
|
|
122
|
+
Reach out to us via https://www.configdirector.com/support
|
|
@@ -123,6 +123,9 @@ type ClientEvents = {
|
|
|
123
123
|
clientReady: {
|
|
124
124
|
action: ClientConnectAction;
|
|
125
125
|
};
|
|
126
|
+
contextUpdated: {
|
|
127
|
+
context: ConfigDirectorContext | undefined;
|
|
128
|
+
};
|
|
126
129
|
};
|
|
127
130
|
type WatchHandler<T extends ConfigValueType> = (message: T) => void;
|
|
128
131
|
/**
|
|
@@ -155,6 +158,15 @@ interface ConfigDirectorClient extends EventProvider<ClientEvents> {
|
|
|
155
158
|
* @param context The current user's context to be used for evaluating targeting rules (required).
|
|
156
159
|
*/
|
|
157
160
|
updateContext(context: ConfigDirectorContext): Promise<void>;
|
|
161
|
+
/**
|
|
162
|
+
* Returns the current context the client is configured with, or undefined if there is no context.
|
|
163
|
+
*
|
|
164
|
+
* Note that the value returned does not updated immediately after calling {@link updateContext}. Only
|
|
165
|
+
* once the underlying connection was successful or timed out, the current context is updated. This is
|
|
166
|
+
* because while the context update is in progress, configs are still evaluated on the previous context.
|
|
167
|
+
*
|
|
168
|
+
*/
|
|
169
|
+
get context(): ConfigDirectorContext | undefined;
|
|
158
170
|
/**
|
|
159
171
|
* Returns whether or not the client is ready after calling {@link initialize} or {@link updateContext}
|
|
160
172
|
*
|
|
@@ -162,6 +174,11 @@ interface ConfigDirectorClient extends EventProvider<ClientEvents> {
|
|
|
162
174
|
* was received.
|
|
163
175
|
*/
|
|
164
176
|
get isReady(): boolean;
|
|
177
|
+
/**
|
|
178
|
+
* Returns whether or not the client is currently initializing. Upon client creation, it is `false`.
|
|
179
|
+
* It is `true` after calling `initialize` and becomes `false` again once initialization is completed.
|
|
180
|
+
*/
|
|
181
|
+
get isInitializing(): boolean;
|
|
165
182
|
/**
|
|
166
183
|
* Evaluates a config and returns its value based on the current context and targeting rules
|
|
167
184
|
*
|
|
@@ -287,10 +304,10 @@ declare const useConfigValue: <T extends ConfigValueType>(key: string, defaultVa
|
|
|
287
304
|
readyStatus: ClientStatus;
|
|
288
305
|
loading: boolean;
|
|
289
306
|
};
|
|
290
|
-
declare const
|
|
307
|
+
declare const useContext: () => {
|
|
291
308
|
updateContext: (context: ConfigDirectorContext) => Promise<void>;
|
|
292
309
|
};
|
|
293
|
-
declare const
|
|
310
|
+
declare const useClient: () => {
|
|
294
311
|
client: ConfigDirectorClient;
|
|
295
312
|
};
|
|
296
313
|
//#endregion
|
|
@@ -306,4 +323,4 @@ declare class ConfigDirectorReactContextError extends Error {
|
|
|
306
323
|
//#region src/logger.d.ts
|
|
307
324
|
declare const createConsoleLogger: (level?: ConfigDirectorLoggingLevel) => DefaultConsoleLogger;
|
|
308
325
|
//#endregion
|
|
309
|
-
export { type ClientStatus, type ConfigDirectorClient, type ConfigDirectorClientOptions, type ConfigDirectorContext, type ConfigDirectorLogger, type ConfigDirectorLoggingLevel, ConfigDirectorProvider, type ConfigDirectorProviderOptions, ConfigDirectorReactContextError, type NetInfoSubscribe, createClient, createConsoleLogger,
|
|
326
|
+
export { type ClientStatus, type ConfigDirectorClient, type ConfigDirectorClientOptions, type ConfigDirectorContext, type ConfigDirectorLogger, type ConfigDirectorLoggingLevel, ConfigDirectorProvider, type ConfigDirectorProviderOptions, ConfigDirectorReactContextError, type NetInfoSubscribe, createClient, createConsoleLogger, useClient, useConfigValue, useContext };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { Component, useContext } from "react";
|
|
1
|
+
import React, { Component, useContext as useContext$1 } from "react";
|
|
2
2
|
import { AppState } from "react-native";
|
|
3
3
|
import { jsx } from "react/jsx-runtime";
|
|
4
4
|
//#region \0@oxc-project+runtime@0.122.0/helpers/typeof.js
|
|
@@ -143,8 +143,8 @@ var EventSourceParser = class {
|
|
|
143
143
|
constructor(options) {
|
|
144
144
|
var _options$onEvent;
|
|
145
145
|
_defineProperty(this, "isFirstChunk", true);
|
|
146
|
-
_defineProperty(this, "
|
|
147
|
-
_defineProperty(this, "currentEvent",
|
|
146
|
+
_defineProperty(this, "bufferedInput", "");
|
|
147
|
+
_defineProperty(this, "currentEvent", { data: "" });
|
|
148
148
|
_defineProperty(this, "lastEventId", void 0);
|
|
149
149
|
_defineProperty(this, "onEvent", void 0);
|
|
150
150
|
_defineProperty(this, "onRetry", void 0);
|
|
@@ -154,83 +154,95 @@ var EventSourceParser = class {
|
|
|
154
154
|
this.onRetry = options.onRetry;
|
|
155
155
|
this.onComment = options.onComment;
|
|
156
156
|
}
|
|
157
|
-
parse(
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
157
|
+
parse(chunk) {
|
|
158
|
+
let input = chunk;
|
|
159
|
+
if (this.isFirstChunk) {
|
|
160
|
+
this.isFirstChunk = false;
|
|
161
|
+
if (input.startsWith("")) input = input.slice(1);
|
|
162
|
+
else if (input.startsWith("")) input = input.slice(3);
|
|
163
|
+
}
|
|
164
|
+
const lines = this.scanLines(this.bufferedInput + input);
|
|
165
|
+
for (const line of lines) this.dispatchLine(line);
|
|
161
166
|
}
|
|
162
167
|
finish() {
|
|
163
|
-
|
|
168
|
+
this.bufferedInput = "";
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Scans `text` character by character to extract complete lines, recognizing
|
|
172
|
+
* CR, LF, and CRLF as line terminators per the SSE spec. Any unterminated
|
|
173
|
+
* trailing text is buffered and prepended to the next chunk.
|
|
174
|
+
*/
|
|
175
|
+
scanLines(text) {
|
|
176
|
+
const lines = [];
|
|
177
|
+
let lineStart = 0;
|
|
178
|
+
let i = 0;
|
|
179
|
+
while (i < text.length) {
|
|
180
|
+
const ch = text[i];
|
|
181
|
+
if (ch === "\r" || ch === "\n") {
|
|
182
|
+
lines.push(text.slice(lineStart, i));
|
|
183
|
+
if (ch === "\r" && text[i + 1] === "\n") i++;
|
|
184
|
+
i++;
|
|
185
|
+
lineStart = i;
|
|
186
|
+
} else i++;
|
|
187
|
+
}
|
|
188
|
+
this.bufferedInput = text.slice(lineStart);
|
|
189
|
+
return lines;
|
|
164
190
|
}
|
|
165
|
-
|
|
191
|
+
dispatchLine(line) {
|
|
166
192
|
if (line.startsWith(":")) {
|
|
167
193
|
var _this$onComment;
|
|
168
|
-
(_this$onComment = this.onComment) === null || _this$onComment === void 0 || _this$onComment.call(this, this.
|
|
194
|
+
(_this$onComment = this.onComment) === null || _this$onComment === void 0 || _this$onComment.call(this, this.extractValue(line, 1));
|
|
169
195
|
return;
|
|
170
196
|
}
|
|
171
197
|
if (line === "") {
|
|
172
|
-
this.
|
|
198
|
+
this.emitEvent();
|
|
173
199
|
return;
|
|
174
200
|
}
|
|
175
|
-
|
|
176
|
-
this.processContent(content);
|
|
201
|
+
this.applyField(this.parseField(line));
|
|
177
202
|
}
|
|
178
|
-
|
|
203
|
+
emitEvent() {
|
|
179
204
|
const data = this.currentEvent.data.endsWith("\n") ? this.currentEvent.data.slice(0, -1) : this.currentEvent.data;
|
|
180
|
-
if (data) this.onEvent({
|
|
205
|
+
if (data !== "") this.onEvent({
|
|
181
206
|
...this.currentEvent,
|
|
182
207
|
id: this.lastEventId,
|
|
183
208
|
data
|
|
184
209
|
});
|
|
185
210
|
this.currentEvent = { data: "" };
|
|
186
211
|
}
|
|
187
|
-
|
|
188
|
-
switch (
|
|
212
|
+
applyField(parsed) {
|
|
213
|
+
switch (parsed.field) {
|
|
189
214
|
case "event":
|
|
190
|
-
this.currentEvent.type =
|
|
215
|
+
this.currentEvent.type = parsed.value;
|
|
191
216
|
break;
|
|
192
217
|
case "data":
|
|
193
|
-
this.currentEvent.data +=
|
|
218
|
+
this.currentEvent.data += parsed.value + "\n";
|
|
194
219
|
break;
|
|
195
220
|
case "id":
|
|
196
|
-
if (!
|
|
221
|
+
if (!parsed.value.includes("\0")) this.lastEventId = parsed.value;
|
|
197
222
|
break;
|
|
198
223
|
case "retry":
|
|
199
|
-
if (/^\d+$/.test(
|
|
224
|
+
if (/^\d+$/.test(parsed.value)) {
|
|
200
225
|
var _this$onRetry;
|
|
201
|
-
(_this$onRetry = this.onRetry) === null || _this$onRetry === void 0 || _this$onRetry.call(this, Number.parseInt(
|
|
226
|
+
(_this$onRetry = this.onRetry) === null || _this$onRetry === void 0 || _this$onRetry.call(this, Number.parseInt(parsed.value, 10));
|
|
202
227
|
}
|
|
203
228
|
break;
|
|
204
229
|
default: break;
|
|
205
230
|
}
|
|
206
231
|
}
|
|
207
|
-
|
|
208
|
-
const
|
|
209
|
-
if (
|
|
232
|
+
parseField(line) {
|
|
233
|
+
const colon = line.indexOf(":");
|
|
234
|
+
if (colon === -1) return {
|
|
210
235
|
field: line,
|
|
211
236
|
value: ""
|
|
212
237
|
};
|
|
213
238
|
return {
|
|
214
|
-
field: line.slice(0,
|
|
215
|
-
value: this.
|
|
239
|
+
field: line.slice(0, colon),
|
|
240
|
+
value: this.extractValue(line, colon + 1)
|
|
216
241
|
};
|
|
217
242
|
}
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
this.previousIncompleteLine = (_lines$pop = lines.pop()) !== null && _lines$pop !== void 0 ? _lines$pop : "";
|
|
222
|
-
return lines;
|
|
223
|
-
}
|
|
224
|
-
getValueFromLine(delimiterIndex, line) {
|
|
225
|
-
if (delimiterIndex === -1) return "";
|
|
226
|
-
const valueStartIndex = line[delimiterIndex + 1] === " " ? delimiterIndex + 2 : delimiterIndex + 1;
|
|
227
|
-
return line.slice(valueStartIndex);
|
|
228
|
-
}
|
|
229
|
-
prepareChunk(chunk) {
|
|
230
|
-
if (this.isFirstChunk) {
|
|
231
|
-
this.isFirstChunk = false;
|
|
232
|
-
return this.previousIncompleteLine + chunk.replace(/^\xEF\xBB\xBF/, "").replace(/^\uFEFF/, "");
|
|
233
|
-
} else return this.previousIncompleteLine + chunk;
|
|
243
|
+
/** Returns the value portion of a line, stripping a single leading space per spec. */
|
|
244
|
+
extractValue(line, from) {
|
|
245
|
+
return line[from] === " " ? line.slice(from + 1) : line.slice(from);
|
|
234
246
|
}
|
|
235
247
|
};
|
|
236
248
|
//#endregion
|
|
@@ -273,34 +285,34 @@ var EventSourceClient = class {
|
|
|
273
285
|
referrerPolicy: options.referrerPolicy
|
|
274
286
|
};
|
|
275
287
|
this._fetch = (_options$fetch = options.fetch) !== null && _options$fetch !== void 0 ? _options$fetch : ((url, init) => fetch(url, init));
|
|
276
|
-
this._onError = this.
|
|
277
|
-
this._onConnect = this.
|
|
278
|
-
this._onDisconnect = this.
|
|
279
|
-
this._onMessage = this.
|
|
280
|
-
this._onComment = this.
|
|
281
|
-
this._shouldReconnect = this.
|
|
282
|
-
this._calculateReconnectDelay = this.
|
|
288
|
+
this._onError = this.resolveCallback(options.onError, () => {}, "onError");
|
|
289
|
+
this._onConnect = this.resolveCallback(options.onConnect, () => {}, "onConnect");
|
|
290
|
+
this._onDisconnect = this.resolveCallback(options.onDisconnect, () => {}, "onDisconnect");
|
|
291
|
+
this._onMessage = this.resolveCallback(options.onMessage, () => {}, "onMessage");
|
|
292
|
+
this._onComment = this.resolveCallback(options.onComment, () => {}, "onComment");
|
|
293
|
+
this._shouldReconnect = this.resolveCallback(options.shouldReconnect, this.DEFAULT_SHOULD_RECONNECT, "shouldReconnect");
|
|
294
|
+
this._calculateReconnectDelay = this.resolveCallback(options.calculateReconnectDelay, this.DEFAULT_CALCULATE_RECONNECT_DELAY, "calculateReconnectDelay");
|
|
283
295
|
}
|
|
284
296
|
set onError(value) {
|
|
285
|
-
this._onError = this.
|
|
297
|
+
this._onError = this.resolveCallback(value, () => {}, "onError");
|
|
286
298
|
}
|
|
287
299
|
set onMessage(value) {
|
|
288
|
-
this._onMessage = this.
|
|
300
|
+
this._onMessage = this.resolveCallback(value, () => {}, "onMessage");
|
|
289
301
|
}
|
|
290
302
|
set onComment(value) {
|
|
291
|
-
this._onComment = this.
|
|
303
|
+
this._onComment = this.resolveCallback(value, () => {}, "onComment");
|
|
292
304
|
}
|
|
293
305
|
set onConnect(value) {
|
|
294
|
-
this._onConnect = this.
|
|
306
|
+
this._onConnect = this.resolveCallback(value, () => {}, "onConnect");
|
|
295
307
|
}
|
|
296
308
|
set onDisconnect(value) {
|
|
297
|
-
this._onDisconnect = this.
|
|
309
|
+
this._onDisconnect = this.resolveCallback(value, () => {}, "onDisconnect");
|
|
298
310
|
}
|
|
299
311
|
set shouldReconnect(value) {
|
|
300
|
-
this._shouldReconnect = this.
|
|
312
|
+
this._shouldReconnect = this.resolveCallback(value, this.DEFAULT_SHOULD_RECONNECT, "shouldReconnect");
|
|
301
313
|
}
|
|
302
314
|
set calculateReconnectDelay(value) {
|
|
303
|
-
this._calculateReconnectDelay = this.
|
|
315
|
+
this._calculateReconnectDelay = this.resolveCallback(value, this.DEFAULT_CALCULATE_RECONNECT_DELAY, "shouldReconnect");
|
|
304
316
|
}
|
|
305
317
|
get readyState() {
|
|
306
318
|
return this._readyState;
|
|
@@ -310,15 +322,20 @@ var EventSourceClient = class {
|
|
|
310
322
|
this.reconnectAttempt = 0;
|
|
311
323
|
this.initiateConnection();
|
|
312
324
|
}
|
|
325
|
+
close() {
|
|
326
|
+
clearTimeout(this.reconnectTimeout);
|
|
327
|
+
this._readyState = ReadyState.CLOSED;
|
|
328
|
+
this.abortController.abort();
|
|
329
|
+
}
|
|
313
330
|
initiateConnection() {
|
|
314
331
|
this.abortController = new AbortController();
|
|
315
332
|
this._readyState = ReadyState.CONNECTING;
|
|
316
333
|
this._fetch(this.url, {
|
|
317
334
|
...this.requestOptions,
|
|
318
|
-
headers: this.
|
|
335
|
+
headers: this.buildRequestHeaders(),
|
|
319
336
|
signal: this.abortController.signal,
|
|
320
337
|
cache: "no-store"
|
|
321
|
-
}).then(this.handleFetchResponse
|
|
338
|
+
}).then((response) => this.handleFetchResponse(response)).catch((error) => {
|
|
322
339
|
if (error.name === "AbortError" || this.abortController.signal.aborted) {
|
|
323
340
|
this.close();
|
|
324
341
|
return;
|
|
@@ -327,23 +344,6 @@ var EventSourceClient = class {
|
|
|
327
344
|
this.scheduleReconnect({ error });
|
|
328
345
|
});
|
|
329
346
|
}
|
|
330
|
-
close() {
|
|
331
|
-
clearTimeout(this.reconnectTimeout);
|
|
332
|
-
this._readyState = ReadyState.CLOSED;
|
|
333
|
-
this.abortController.abort();
|
|
334
|
-
}
|
|
335
|
-
disconnected() {
|
|
336
|
-
clearTimeout(this.reconnectTimeout);
|
|
337
|
-
this._readyState = ReadyState.CLOSED;
|
|
338
|
-
this._onDisconnect();
|
|
339
|
-
}
|
|
340
|
-
buildHeaders() {
|
|
341
|
-
const lastEventIdHeader = this.lastEventId ? { "Last-Event-ID": this.lastEventId } : void 0;
|
|
342
|
-
return {
|
|
343
|
-
...this.headers,
|
|
344
|
-
...lastEventIdHeader
|
|
345
|
-
};
|
|
346
|
-
}
|
|
347
347
|
async handleFetchResponse(response) {
|
|
348
348
|
const { body, status } = response;
|
|
349
349
|
if (status === 204) {
|
|
@@ -358,8 +358,6 @@ var EventSourceClient = class {
|
|
|
358
358
|
this._readyState = ReadyState.OPEN;
|
|
359
359
|
this._onConnect();
|
|
360
360
|
this.reconnectAttempt = 0;
|
|
361
|
-
const reader = body.getReader();
|
|
362
|
-
const decoder = new TextDecoder();
|
|
363
361
|
const parser = new EventSourceParser({
|
|
364
362
|
onEvent: (event) => {
|
|
365
363
|
if (typeof event.id === "string") this.lastEventId = event.id;
|
|
@@ -368,18 +366,35 @@ var EventSourceClient = class {
|
|
|
368
366
|
onComment: (comment) => this._onComment(comment),
|
|
369
367
|
onRetry: (retryDelay) => this.serverReconnectionTime = retryDelay
|
|
370
368
|
});
|
|
371
|
-
|
|
372
|
-
while (!done) {
|
|
373
|
-
const { done: readerDone, value } = await reader.read();
|
|
374
|
-
if (value) parser.parse(decoder.decode(value, { stream: true }));
|
|
375
|
-
done = readerDone;
|
|
376
|
-
}
|
|
377
|
-
parser.finish();
|
|
369
|
+
await this.readResponseStream(body, parser);
|
|
378
370
|
this.scheduleReconnect({
|
|
379
371
|
status,
|
|
380
372
|
error: new StreamClosedError("The server response stream was closed")
|
|
381
373
|
});
|
|
382
374
|
}
|
|
375
|
+
async readResponseStream(body, parser) {
|
|
376
|
+
const reader = body.getReader();
|
|
377
|
+
const decoder = new TextDecoder();
|
|
378
|
+
let done = false;
|
|
379
|
+
while (!done) {
|
|
380
|
+
const result = await reader.read();
|
|
381
|
+
if (result.value) parser.parse(decoder.decode(result.value, { stream: true }));
|
|
382
|
+
done = result.done;
|
|
383
|
+
}
|
|
384
|
+
parser.finish();
|
|
385
|
+
}
|
|
386
|
+
disconnected() {
|
|
387
|
+
clearTimeout(this.reconnectTimeout);
|
|
388
|
+
this._readyState = ReadyState.CLOSED;
|
|
389
|
+
this._onDisconnect();
|
|
390
|
+
}
|
|
391
|
+
buildRequestHeaders() {
|
|
392
|
+
const lastEventIdHeader = this.lastEventId ? { "Last-Event-ID": this.lastEventId } : void 0;
|
|
393
|
+
return {
|
|
394
|
+
...this.headers,
|
|
395
|
+
...lastEventIdHeader
|
|
396
|
+
};
|
|
397
|
+
}
|
|
383
398
|
scheduleReconnect(state) {
|
|
384
399
|
if (this.abortController.signal.aborted) return;
|
|
385
400
|
clearTimeout(this.reconnectTimeout);
|
|
@@ -390,26 +405,22 @@ var EventSourceClient = class {
|
|
|
390
405
|
error: state.error,
|
|
391
406
|
serverReconnectionTime: this.serverReconnectionTime
|
|
392
407
|
};
|
|
393
|
-
if (this._shouldReconnect(reconnectionState)) {
|
|
394
|
-
this._readyState = ReadyState.CONNECTING;
|
|
395
|
-
let delay = this._calculateReconnectDelay(reconnectionState);
|
|
396
|
-
if (!Number.isFinite(delay) || delay < 1 || delay > 1e3 * 60 * 60) {
|
|
397
|
-
this._onError(new ValueOutOfRangeError(`The calculated reconnect delay is out of range: ${delay}. Defaulting to ${this.serverReconnectionTime}`));
|
|
398
|
-
delay = this.serverReconnectionTime;
|
|
399
|
-
}
|
|
400
|
-
this.reconnectTimeout = setTimeout(() => this.initiateConnection(), delay);
|
|
401
|
-
} else {
|
|
408
|
+
if (!this._shouldReconnect(reconnectionState)) {
|
|
402
409
|
this.disconnected();
|
|
403
410
|
return;
|
|
404
411
|
}
|
|
412
|
+
this._readyState = ReadyState.CONNECTING;
|
|
413
|
+
let delay = this._calculateReconnectDelay(reconnectionState);
|
|
414
|
+
if (!Number.isFinite(delay) || delay < 1 || delay > 1e3 * 60 * 60) {
|
|
415
|
+
this._onError(new ValueOutOfRangeError(`The calculated reconnect delay is out of range: ${delay}. Defaulting to ${this.serverReconnectionTime}`));
|
|
416
|
+
delay = this.serverReconnectionTime;
|
|
417
|
+
}
|
|
418
|
+
this.reconnectTimeout = setTimeout(() => this.initiateConnection(), delay);
|
|
405
419
|
}
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
if (func == null) return defaultFunc;
|
|
411
|
-
else if (typeof func === "function") return func;
|
|
412
|
-
else throw new InvalidOptionError(`${name} must be a function`);
|
|
420
|
+
resolveCallback(provided, fallback, optionName) {
|
|
421
|
+
if (provided == null) return fallback;
|
|
422
|
+
if (typeof provided !== "function") throw new InvalidOptionError(`${optionName} must be a function`);
|
|
423
|
+
return provided;
|
|
413
424
|
}
|
|
414
425
|
};
|
|
415
426
|
//#endregion
|
|
@@ -775,6 +786,7 @@ var DefaultConfigDirectorClient = class {
|
|
|
775
786
|
_defineProperty(this, "timeout", void 0);
|
|
776
787
|
_defineProperty(this, "timeoutTimer", void 0);
|
|
777
788
|
_defineProperty(this, "ready", false);
|
|
789
|
+
_defineProperty(this, "initializing", false);
|
|
778
790
|
_defineProperty(this, "readyPromise", void 0);
|
|
779
791
|
_defineProperty(this, "readyResolve", void 0);
|
|
780
792
|
_defineProperty(this, "currentContext", void 0);
|
|
@@ -822,11 +834,15 @@ var DefaultConfigDirectorClient = class {
|
|
|
822
834
|
});
|
|
823
835
|
}
|
|
824
836
|
async initialize(context) {
|
|
837
|
+
this.initializing = true;
|
|
825
838
|
await this.connectToTransport(context, "initialization");
|
|
826
839
|
}
|
|
827
840
|
async updateContext(context) {
|
|
828
841
|
await this.connectToTransport(context, "context update");
|
|
829
842
|
}
|
|
843
|
+
get context() {
|
|
844
|
+
return this.currentContext;
|
|
845
|
+
}
|
|
830
846
|
async connectToTransport(context, caller) {
|
|
831
847
|
try {
|
|
832
848
|
this.ready = false;
|
|
@@ -834,6 +850,7 @@ var DefaultConfigDirectorClient = class {
|
|
|
834
850
|
this.readyResolve = resolve;
|
|
835
851
|
}).then(() => {
|
|
836
852
|
this.ready = true;
|
|
853
|
+
this.initializing = false;
|
|
837
854
|
this.eventEmitter.emit("clientReady", { action: caller });
|
|
838
855
|
this.logger.debug("[ConfigDirectorClient] Received initial payload from the server, client is ready");
|
|
839
856
|
});
|
|
@@ -841,6 +858,7 @@ var DefaultConfigDirectorClient = class {
|
|
|
841
858
|
await this.transport.connect(context !== null && context !== void 0 ? context : {}, this.timeout);
|
|
842
859
|
this.currentContext = context;
|
|
843
860
|
this.telemetryClient.updateContext(context);
|
|
861
|
+
this.eventEmitter.emit("contextUpdated", { context });
|
|
844
862
|
const elapsedTime = (/* @__PURE__ */ new Date()).getTime() - startTime;
|
|
845
863
|
const remainingTimeout = this.timeout - elapsedTime;
|
|
846
864
|
clearTimeout(this.timeoutTimer);
|
|
@@ -936,6 +954,9 @@ var DefaultConfigDirectorClient = class {
|
|
|
936
954
|
get isReady() {
|
|
937
955
|
return this.ready;
|
|
938
956
|
}
|
|
957
|
+
get isInitializing() {
|
|
958
|
+
return this.initializing;
|
|
959
|
+
}
|
|
939
960
|
on(eventName, handler) {
|
|
940
961
|
this.eventEmitter.on(eventName, handler);
|
|
941
962
|
}
|
|
@@ -1243,89 +1264,114 @@ const createConsoleLogger = (level) => createDefaultLogger(level, new MessageDec
|
|
|
1243
1264
|
//#endregion
|
|
1244
1265
|
//#region src/utf8Encoder.ts
|
|
1245
1266
|
/**
|
|
1246
|
-
* Encodes a string to UTF-8
|
|
1247
|
-
*
|
|
1267
|
+
* Encodes a string to a UTF-8 byte sequence.
|
|
1268
|
+
*
|
|
1269
|
+
* Uses the native TextEncoder when available (Hermes runtime). For environments
|
|
1270
|
+
* that lack it (JSC), falls back to a manual implementation that follows the
|
|
1271
|
+
* UTF-8 encoding algorithm from the Unicode standard:
|
|
1272
|
+
*
|
|
1273
|
+
* U+0000–U+007F → 1 byte 0xxxxxxx
|
|
1274
|
+
* U+0080–U+07FF → 2 bytes 110xxxxx 10xxxxxx
|
|
1275
|
+
* U+0800–U+FFFF → 3 bytes 1110xxxx 10xxxxxx 10xxxxxx
|
|
1276
|
+
* U+10000–U+10FFFF → 4 bytes 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
|
|
1277
|
+
*
|
|
1278
|
+
* JavaScript strings are UTF-16, so code points above U+FFFF are stored as
|
|
1279
|
+
* surrogate pairs and must be recombined before encoding.
|
|
1248
1280
|
*/
|
|
1249
1281
|
const encodeUtf8 = (str) => {
|
|
1250
|
-
if (typeof
|
|
1251
|
-
|
|
1282
|
+
if (typeof TextEncoder === "function") return new TextEncoder().encode(str);
|
|
1283
|
+
return encodeManually(str);
|
|
1284
|
+
};
|
|
1285
|
+
const encodeManually = (str) => {
|
|
1252
1286
|
const bytes = [];
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1287
|
+
let i = 0;
|
|
1288
|
+
while (i < str.length) {
|
|
1289
|
+
let cp = str.charCodeAt(i++);
|
|
1290
|
+
if (cp >= 55296 && cp <= 56319 && i < str.length) {
|
|
1291
|
+
const low = str.charCodeAt(i);
|
|
1292
|
+
if (low >= 56320 && low <= 57343) {
|
|
1293
|
+
cp = 65536 + (cp - 55296 << 10) + (low - 56320);
|
|
1294
|
+
i++;
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
if (cp <= 127) bytes.push(cp);
|
|
1298
|
+
else if (cp <= 2047) bytes.push(192 | cp >> 6, 128 | cp & 63);
|
|
1299
|
+
else if (cp <= 65535) bytes.push(224 | cp >> 12, 128 | cp >> 6 & 63, 128 | cp & 63);
|
|
1300
|
+
else bytes.push(240 | cp >> 18, 128 | cp >> 12 & 63, 128 | cp >> 6 & 63, 128 | cp & 63);
|
|
1259
1301
|
}
|
|
1260
1302
|
return new Uint8Array(bytes);
|
|
1261
1303
|
};
|
|
1262
1304
|
//#endregion
|
|
1263
1305
|
//#region src/reactNativeStreamingFetch.ts
|
|
1264
1306
|
/**
|
|
1265
|
-
* A fetch-compatible function for React Native that uses XMLHttpRequest to
|
|
1266
|
-
* streaming SSE responses. React Native's built-in fetch
|
|
1267
|
-
* body before resolving,
|
|
1268
|
-
* XHR's
|
|
1307
|
+
* A fetch-compatible function for React Native that uses XMLHttpRequest to
|
|
1308
|
+
* support streaming SSE responses. React Native's built-in fetch accumulates
|
|
1309
|
+
* the entire response body before resolving, so persistent SSE connections
|
|
1310
|
+
* never complete. XHR's progress events fire incrementally as data arrives,
|
|
1311
|
+
* which we forward into a ReadableStream.
|
|
1269
1312
|
*/
|
|
1270
1313
|
const reactNativeStreamingFetch = (url, init) => {
|
|
1271
1314
|
var _init$signal;
|
|
1272
|
-
if ((_init$signal = init.signal) === null || _init$signal === void 0 ? void 0 : _init$signal.aborted) return Promise.reject(new DOMException("
|
|
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"));
|
|
1273
1316
|
return new Promise((resolve, reject) => {
|
|
1274
1317
|
var _init$method, _ref;
|
|
1275
1318
|
const xhr = new XMLHttpRequest();
|
|
1276
1319
|
xhr.open((_init$method = init.method) !== null && _init$method !== void 0 ? _init$method : "GET", url);
|
|
1277
|
-
const
|
|
1278
|
-
if (
|
|
1279
|
-
let
|
|
1280
|
-
let
|
|
1281
|
-
let
|
|
1282
|
-
const
|
|
1283
|
-
|
|
1320
|
+
const requestHeaders = init.headers;
|
|
1321
|
+
if (requestHeaders) for (const [name, value] of Object.entries(requestHeaders)) xhr.setRequestHeader(name, value);
|
|
1322
|
+
let responseResolved = false;
|
|
1323
|
+
let bytesForwarded = 0;
|
|
1324
|
+
let streamController;
|
|
1325
|
+
const responseBody = new ReadableStream({ start(ctrl) {
|
|
1326
|
+
streamController = ctrl;
|
|
1284
1327
|
} });
|
|
1328
|
+
const flushNewBytes = () => {
|
|
1329
|
+
const next = xhr.responseText.slice(bytesForwarded);
|
|
1330
|
+
if (next.length > 0) {
|
|
1331
|
+
bytesForwarded += next.length;
|
|
1332
|
+
streamController.enqueue(encodeUtf8(next));
|
|
1333
|
+
}
|
|
1334
|
+
};
|
|
1285
1335
|
xhr.onreadystatechange = () => {
|
|
1286
|
-
if (xhr.readyState
|
|
1287
|
-
|
|
1288
|
-
const headers = new Headers();
|
|
1289
|
-
xhr.getAllResponseHeaders().trim().split(/\r?\n/).forEach((line) => {
|
|
1290
|
-
const [key, ...rest] = line.split(": ");
|
|
1291
|
-
if (key) headers.set(key, rest.join(": "));
|
|
1292
|
-
});
|
|
1336
|
+
if (xhr.readyState >= XMLHttpRequest.HEADERS_RECEIVED && !responseResolved) {
|
|
1337
|
+
responseResolved = true;
|
|
1293
1338
|
resolve({
|
|
1294
1339
|
status: xhr.status,
|
|
1295
|
-
headers,
|
|
1296
|
-
body:
|
|
1340
|
+
headers: parseHeaders(xhr.getAllResponseHeaders()),
|
|
1341
|
+
body: responseBody
|
|
1297
1342
|
});
|
|
1298
1343
|
}
|
|
1299
1344
|
};
|
|
1300
1345
|
xhr.onprogress = () => {
|
|
1301
|
-
|
|
1302
|
-
lastLength = xhr.responseText.length;
|
|
1303
|
-
if (newText) controller.enqueue(encodeUtf8(newText));
|
|
1346
|
+
flushNewBytes();
|
|
1304
1347
|
};
|
|
1305
1348
|
xhr.onload = () => {
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
controller.close();
|
|
1349
|
+
flushNewBytes();
|
|
1350
|
+
streamController.close();
|
|
1309
1351
|
};
|
|
1310
1352
|
xhr.onerror = () => {
|
|
1311
|
-
const
|
|
1312
|
-
if (!
|
|
1313
|
-
else
|
|
1314
|
-
xhr.abort();
|
|
1353
|
+
const err = /* @__PURE__ */ new TypeError("Network request failed");
|
|
1354
|
+
if (!responseResolved) reject(err);
|
|
1355
|
+
else streamController.error(err);
|
|
1315
1356
|
};
|
|
1316
1357
|
if (init.signal) init.signal.addEventListener("abort", () => {
|
|
1317
1358
|
xhr.abort();
|
|
1318
|
-
if (!
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
reject(abortError);
|
|
1322
|
-
} else try {
|
|
1323
|
-
controller.close();
|
|
1359
|
+
if (!responseResolved) reject(new DOMException("Request was aborted", "AbortError"));
|
|
1360
|
+
else try {
|
|
1361
|
+
streamController.close();
|
|
1324
1362
|
} catch {}
|
|
1325
1363
|
});
|
|
1326
1364
|
xhr.send((_ref = init.body) !== null && _ref !== void 0 ? _ref : null);
|
|
1327
1365
|
});
|
|
1328
1366
|
};
|
|
1367
|
+
const parseHeaders = (raw) => {
|
|
1368
|
+
const headers = new Headers();
|
|
1369
|
+
for (const line of raw.trim().split(/\r?\n/)) {
|
|
1370
|
+
const sep = line.indexOf(": ");
|
|
1371
|
+
if (sep > 0) headers.set(line.slice(0, sep), line.slice(sep + 2));
|
|
1372
|
+
}
|
|
1373
|
+
return headers;
|
|
1374
|
+
};
|
|
1329
1375
|
//#endregion
|
|
1330
1376
|
//#region src/ReactNativeTelemetryClient.ts
|
|
1331
1377
|
var ReactNativeTelemetryClient = class {
|
|
@@ -1377,7 +1423,7 @@ const createClient = (clientSdkKey, clientOptions) => {
|
|
|
1377
1423
|
const logger = (_clientOptions$logger = clientOptions === null || clientOptions === void 0 ? void 0 : clientOptions.logger) !== null && _clientOptions$logger !== void 0 ? _clientOptions$logger : createConsoleLogger("warn");
|
|
1378
1424
|
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, {
|
|
1379
1425
|
sdkName: "react-native-sdk",
|
|
1380
|
-
sdkVersion: "0.
|
|
1426
|
+
sdkVersion: "0.2.0"
|
|
1381
1427
|
}, {
|
|
1382
1428
|
...clientOptions,
|
|
1383
1429
|
logger
|
|
@@ -1481,7 +1527,7 @@ const noContextError = () => {
|
|
|
1481
1527
|
return new ConfigDirectorReactContextError("The ConfigDirector client was not found in the React context. Please make sure the component using this hook is inside the context of a ConfigDirectorProvider.");
|
|
1482
1528
|
};
|
|
1483
1529
|
const useConfigValue = (key, defaultValue) => {
|
|
1484
|
-
const configDirectorReactContext = useContext(reactContext);
|
|
1530
|
+
const configDirectorReactContext = useContext$1(reactContext);
|
|
1485
1531
|
if (!(configDirectorReactContext === null || configDirectorReactContext === void 0 ? void 0 : configDirectorReactContext.client)) throw noContextError();
|
|
1486
1532
|
return {
|
|
1487
1533
|
value: configDirectorReactContext.client.getValue(key, defaultValue),
|
|
@@ -1489,8 +1535,8 @@ const useConfigValue = (key, defaultValue) => {
|
|
|
1489
1535
|
loading: configDirectorReactContext.status === "loading"
|
|
1490
1536
|
};
|
|
1491
1537
|
};
|
|
1492
|
-
const
|
|
1493
|
-
const configDirectorReactContext = useContext(reactContext);
|
|
1538
|
+
const useContext = () => {
|
|
1539
|
+
const configDirectorReactContext = useContext$1(reactContext);
|
|
1494
1540
|
if (!(configDirectorReactContext === null || configDirectorReactContext === void 0 ? void 0 : configDirectorReactContext.client)) throw noContextError();
|
|
1495
1541
|
const updateContext = async (context) => {
|
|
1496
1542
|
var _configDirectorReactC;
|
|
@@ -1498,10 +1544,10 @@ const useConfigDirectorContext = () => {
|
|
|
1498
1544
|
};
|
|
1499
1545
|
return { updateContext };
|
|
1500
1546
|
};
|
|
1501
|
-
const
|
|
1502
|
-
const configDirectorReactContext = useContext(reactContext);
|
|
1547
|
+
const useClient = () => {
|
|
1548
|
+
const configDirectorReactContext = useContext$1(reactContext);
|
|
1503
1549
|
if (!(configDirectorReactContext === null || configDirectorReactContext === void 0 ? void 0 : configDirectorReactContext.client)) throw noContextError();
|
|
1504
1550
|
return { client: configDirectorReactContext.client };
|
|
1505
1551
|
};
|
|
1506
1552
|
//#endregion
|
|
1507
|
-
export { ConfigDirectorProvider, ConfigDirectorReactContextError, createClient, createConsoleLogger,
|
|
1553
|
+
export { ConfigDirectorProvider, ConfigDirectorReactContextError, createClient, createConsoleLogger, useClient, useConfigValue, useContext };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@configdirector/react-native-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.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",
|