@configdirector/react-native-sdk 0.1.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/LICENSE +21 -0
- package/README.md +39 -0
- package/dist/configdirector-react-native.d.mts +309 -0
- package/dist/configdirector-react-native.mjs +1507 -0
- package/package.json +118 -0
|
@@ -0,0 +1,1507 @@
|
|
|
1
|
+
import React, { Component, useContext } from "react";
|
|
2
|
+
import { AppState } from "react-native";
|
|
3
|
+
import { jsx } from "react/jsx-runtime";
|
|
4
|
+
//#region \0@oxc-project+runtime@0.122.0/helpers/typeof.js
|
|
5
|
+
function _typeof(o) {
|
|
6
|
+
"@babel/helpers - typeof";
|
|
7
|
+
return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function(o) {
|
|
8
|
+
return typeof o;
|
|
9
|
+
} : function(o) {
|
|
10
|
+
return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o;
|
|
11
|
+
}, _typeof(o);
|
|
12
|
+
}
|
|
13
|
+
//#endregion
|
|
14
|
+
//#region \0@oxc-project+runtime@0.122.0/helpers/toPrimitive.js
|
|
15
|
+
function toPrimitive(t, r) {
|
|
16
|
+
if ("object" != _typeof(t) || !t) return t;
|
|
17
|
+
var e = t[Symbol.toPrimitive];
|
|
18
|
+
if (void 0 !== e) {
|
|
19
|
+
var i = e.call(t, r || "default");
|
|
20
|
+
if ("object" != _typeof(i)) return i;
|
|
21
|
+
throw new TypeError("@@toPrimitive must return a primitive value.");
|
|
22
|
+
}
|
|
23
|
+
return ("string" === r ? String : Number)(t);
|
|
24
|
+
}
|
|
25
|
+
//#endregion
|
|
26
|
+
//#region \0@oxc-project+runtime@0.122.0/helpers/toPropertyKey.js
|
|
27
|
+
function toPropertyKey(t) {
|
|
28
|
+
var i = toPrimitive(t, "string");
|
|
29
|
+
return "symbol" == _typeof(i) ? i : i + "";
|
|
30
|
+
}
|
|
31
|
+
//#endregion
|
|
32
|
+
//#region \0@oxc-project+runtime@0.122.0/helpers/defineProperty.js
|
|
33
|
+
function _defineProperty(e, r, t) {
|
|
34
|
+
return (r = toPropertyKey(r)) in e ? Object.defineProperty(e, r, {
|
|
35
|
+
value: t,
|
|
36
|
+
enumerable: !0,
|
|
37
|
+
configurable: !0,
|
|
38
|
+
writable: !0
|
|
39
|
+
}) : e[r] = t, e;
|
|
40
|
+
}
|
|
41
|
+
//#endregion
|
|
42
|
+
//#region ../js-client-core/src/Emitter.ts
|
|
43
|
+
var Emitter = class {
|
|
44
|
+
constructor() {
|
|
45
|
+
_defineProperty(this, "handlerMap", /* @__PURE__ */ new Map());
|
|
46
|
+
}
|
|
47
|
+
on(name, handler) {
|
|
48
|
+
const handlers = this.handlerMap.get(name);
|
|
49
|
+
if (handlers) handlers.push(handler);
|
|
50
|
+
else this.handlerMap.set(name, [handler]);
|
|
51
|
+
}
|
|
52
|
+
once(name, handler) {
|
|
53
|
+
const self = this;
|
|
54
|
+
function onceHandler(payload) {
|
|
55
|
+
self.off(name, onceHandler);
|
|
56
|
+
handler.apply(self, payload);
|
|
57
|
+
}
|
|
58
|
+
this.on(name, onceHandler);
|
|
59
|
+
}
|
|
60
|
+
off(name, handler) {
|
|
61
|
+
const handlers = this.handlerMap.get(name);
|
|
62
|
+
if (!handlers) return;
|
|
63
|
+
if (handler) {
|
|
64
|
+
const listenerIndex = handlers.indexOf(handler);
|
|
65
|
+
if (listenerIndex >= 0) handlers.splice(listenerIndex, 1);
|
|
66
|
+
} else this.handlerMap.set(name, []);
|
|
67
|
+
}
|
|
68
|
+
clear() {
|
|
69
|
+
this.handlerMap.clear();
|
|
70
|
+
}
|
|
71
|
+
emit(name, payload) {
|
|
72
|
+
const handlers = this.handlerMap.get(name);
|
|
73
|
+
if (!handlers) return;
|
|
74
|
+
handlers.slice().map((h) => h(payload));
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
//#endregion
|
|
78
|
+
//#region ../shared/src/errors.ts
|
|
79
|
+
var ConfigDirectorConnectionError = class ConfigDirectorConnectionError extends Error {
|
|
80
|
+
constructor(message, status) {
|
|
81
|
+
super(message);
|
|
82
|
+
_defineProperty(this, "name", "ConfigDirectorConnectionError");
|
|
83
|
+
_defineProperty(this, "status", void 0);
|
|
84
|
+
this.status = status;
|
|
85
|
+
Object.setPrototypeOf(this, ConfigDirectorConnectionError.prototype);
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
var ConfigDirectorValidationError = class ConfigDirectorValidationError extends Error {
|
|
89
|
+
constructor(message) {
|
|
90
|
+
super(message);
|
|
91
|
+
_defineProperty(this, "name", "ConfigDirectorValidationError");
|
|
92
|
+
Object.setPrototypeOf(this, ConfigDirectorValidationError.prototype);
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
//#endregion
|
|
96
|
+
//#region ../js-client-core/src/errors.ts
|
|
97
|
+
const isFetchErrorFatal = (fetchError) => {
|
|
98
|
+
if ((fetchError === null || fetchError === void 0 ? void 0 : fetchError.name) === "NotAllowedError") return true;
|
|
99
|
+
else if (fetchError instanceof TypeError) return true;
|
|
100
|
+
return false;
|
|
101
|
+
};
|
|
102
|
+
//#endregion
|
|
103
|
+
//#region ../eventsource/src/errors.ts
|
|
104
|
+
var StreamClosedError = class StreamClosedError extends Error {
|
|
105
|
+
constructor(message) {
|
|
106
|
+
super(message);
|
|
107
|
+
_defineProperty(this, "name", "StreamClosedError");
|
|
108
|
+
Object.setPrototypeOf(this, StreamClosedError.prototype);
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
var MissingResponseBodyError = class MissingResponseBodyError extends Error {
|
|
112
|
+
constructor(message) {
|
|
113
|
+
super(message);
|
|
114
|
+
_defineProperty(this, "name", "MissingResponseBodyError");
|
|
115
|
+
Object.setPrototypeOf(this, MissingResponseBodyError.prototype);
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
var InvalidOptionError = class InvalidOptionError extends Error {
|
|
119
|
+
constructor(message) {
|
|
120
|
+
super(message);
|
|
121
|
+
_defineProperty(this, "name", "InvalidOptionError");
|
|
122
|
+
Object.setPrototypeOf(this, InvalidOptionError.prototype);
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
var ValueOutOfRangeError = class ValueOutOfRangeError extends Error {
|
|
126
|
+
constructor(message) {
|
|
127
|
+
super(message);
|
|
128
|
+
_defineProperty(this, "name", "ValueOutOfRangeError");
|
|
129
|
+
Object.setPrototypeOf(this, ValueOutOfRangeError.prototype);
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
//#endregion
|
|
133
|
+
//#region ../eventsource/src/types.ts
|
|
134
|
+
let ReadyState = /* @__PURE__ */ function(ReadyState) {
|
|
135
|
+
ReadyState["OPEN"] = "open";
|
|
136
|
+
ReadyState["CLOSED"] = "closed";
|
|
137
|
+
ReadyState["CONNECTING"] = "connecting";
|
|
138
|
+
return ReadyState;
|
|
139
|
+
}({});
|
|
140
|
+
//#endregion
|
|
141
|
+
//#region ../eventsource/src/EventSourceParser.ts
|
|
142
|
+
var EventSourceParser = class {
|
|
143
|
+
constructor(options) {
|
|
144
|
+
var _options$onEvent;
|
|
145
|
+
_defineProperty(this, "isFirstChunk", true);
|
|
146
|
+
_defineProperty(this, "previousIncompleteLine", "");
|
|
147
|
+
_defineProperty(this, "currentEvent", void 0);
|
|
148
|
+
_defineProperty(this, "lastEventId", void 0);
|
|
149
|
+
_defineProperty(this, "onEvent", void 0);
|
|
150
|
+
_defineProperty(this, "onRetry", void 0);
|
|
151
|
+
_defineProperty(this, "onComment", void 0);
|
|
152
|
+
this.currentEvent = { data: "" };
|
|
153
|
+
this.onEvent = (_options$onEvent = options.onEvent) !== null && _options$onEvent !== void 0 ? _options$onEvent : (() => {});
|
|
154
|
+
this.onRetry = options.onRetry;
|
|
155
|
+
this.onComment = options.onComment;
|
|
156
|
+
}
|
|
157
|
+
parse(input) {
|
|
158
|
+
const chunk = this.prepareChunk(input);
|
|
159
|
+
const lines = this.splitLines(chunk);
|
|
160
|
+
for (const line of lines) this.parseAndProcessLine(line);
|
|
161
|
+
}
|
|
162
|
+
finish() {
|
|
163
|
+
if (this.previousIncompleteLine !== "") this.parseAndProcessLine(this.previousIncompleteLine);
|
|
164
|
+
}
|
|
165
|
+
parseAndProcessLine(line) {
|
|
166
|
+
if (line.startsWith(":")) {
|
|
167
|
+
var _this$onComment;
|
|
168
|
+
(_this$onComment = this.onComment) === null || _this$onComment === void 0 || _this$onComment.call(this, this.getValueFromLine(0, line));
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
if (line === "") {
|
|
172
|
+
this.publishEvent();
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
const content = this.parseContentLine(line);
|
|
176
|
+
this.processContent(content);
|
|
177
|
+
}
|
|
178
|
+
publishEvent() {
|
|
179
|
+
const data = this.currentEvent.data.endsWith("\n") ? this.currentEvent.data.slice(0, -1) : this.currentEvent.data;
|
|
180
|
+
if (data) this.onEvent({
|
|
181
|
+
...this.currentEvent,
|
|
182
|
+
id: this.lastEventId,
|
|
183
|
+
data
|
|
184
|
+
});
|
|
185
|
+
this.currentEvent = { data: "" };
|
|
186
|
+
}
|
|
187
|
+
processContent(content) {
|
|
188
|
+
switch (content.field) {
|
|
189
|
+
case "event":
|
|
190
|
+
this.currentEvent.type = content.value;
|
|
191
|
+
break;
|
|
192
|
+
case "data":
|
|
193
|
+
this.currentEvent.data += content.value + "\n";
|
|
194
|
+
break;
|
|
195
|
+
case "id":
|
|
196
|
+
if (!content.value.includes("\0")) this.lastEventId = content.value;
|
|
197
|
+
break;
|
|
198
|
+
case "retry":
|
|
199
|
+
if (/^\d+$/.test(content.value)) {
|
|
200
|
+
var _this$onRetry;
|
|
201
|
+
(_this$onRetry = this.onRetry) === null || _this$onRetry === void 0 || _this$onRetry.call(this, Number.parseInt(content.value, 10));
|
|
202
|
+
}
|
|
203
|
+
break;
|
|
204
|
+
default: break;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
parseContentLine(line) {
|
|
208
|
+
const delimiterIndex = line.indexOf(":");
|
|
209
|
+
if (delimiterIndex === -1) return {
|
|
210
|
+
field: line,
|
|
211
|
+
value: ""
|
|
212
|
+
};
|
|
213
|
+
return {
|
|
214
|
+
field: line.slice(0, delimiterIndex),
|
|
215
|
+
value: this.getValueFromLine(delimiterIndex, line)
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
splitLines(chunk) {
|
|
219
|
+
var _lines$pop;
|
|
220
|
+
const lines = chunk.split(/\r\n|\r|\n/);
|
|
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;
|
|
234
|
+
}
|
|
235
|
+
};
|
|
236
|
+
//#endregion
|
|
237
|
+
//#region ../eventsource/src/EventSourceClient.ts
|
|
238
|
+
var EventSourceClient = class {
|
|
239
|
+
constructor(options) {
|
|
240
|
+
var _options$method, _options$fetch;
|
|
241
|
+
_defineProperty(this, "DEFAULT_SHOULD_RECONNECT", () => true);
|
|
242
|
+
_defineProperty(this, "DEFAULT_CALCULATE_RECONNECT_DELAY", () => this.serverReconnectionTime);
|
|
243
|
+
_defineProperty(this, "url", void 0);
|
|
244
|
+
_defineProperty(this, "headers", void 0);
|
|
245
|
+
_defineProperty(this, "requestOptions", void 0);
|
|
246
|
+
_defineProperty(this, "lastEventId", void 0);
|
|
247
|
+
_defineProperty(this, "reconnectAttempt", 0);
|
|
248
|
+
_defineProperty(this, "abortController", new AbortController());
|
|
249
|
+
_defineProperty(this, "reconnectTimeout", void 0);
|
|
250
|
+
_defineProperty(this, "_readyState", ReadyState.CLOSED);
|
|
251
|
+
_defineProperty(this, "serverReconnectionTime", 2e3);
|
|
252
|
+
_defineProperty(this, "_fetch", void 0);
|
|
253
|
+
_defineProperty(this, "_onError", void 0);
|
|
254
|
+
_defineProperty(this, "_onMessage", void 0);
|
|
255
|
+
_defineProperty(this, "_onComment", void 0);
|
|
256
|
+
_defineProperty(this, "_onConnect", void 0);
|
|
257
|
+
_defineProperty(this, "_onDisconnect", void 0);
|
|
258
|
+
_defineProperty(this, "_shouldReconnect", void 0);
|
|
259
|
+
_defineProperty(this, "_calculateReconnectDelay", void 0);
|
|
260
|
+
this.url = options.url.toString();
|
|
261
|
+
this.lastEventId = options.lastEventId;
|
|
262
|
+
this.headers = {
|
|
263
|
+
Accept: "text/event-stream",
|
|
264
|
+
...options.headers
|
|
265
|
+
};
|
|
266
|
+
this.requestOptions = {
|
|
267
|
+
body: options.body,
|
|
268
|
+
method: (_options$method = options.method) !== null && _options$method !== void 0 ? _options$method : "GET",
|
|
269
|
+
mode: options.mode,
|
|
270
|
+
credentials: options.credentials,
|
|
271
|
+
redirect: options.redirect,
|
|
272
|
+
referrer: options.referrer,
|
|
273
|
+
referrerPolicy: options.referrerPolicy
|
|
274
|
+
};
|
|
275
|
+
this._fetch = (_options$fetch = options.fetch) !== null && _options$fetch !== void 0 ? _options$fetch : ((url, init) => fetch(url, init));
|
|
276
|
+
this._onError = this.getValidatedFunctionOrNoOp(options.onError, "onError");
|
|
277
|
+
this._onConnect = this.getValidatedFunctionOrNoOp(options.onConnect, "onConnect");
|
|
278
|
+
this._onDisconnect = this.getValidatedFunctionOrNoOp(options.onDisconnect, "onDisconnect");
|
|
279
|
+
this._onMessage = this.getValidatedFunctionOrNoOp(options.onMessage, "onMessage");
|
|
280
|
+
this._onComment = this.getValidatedFunctionOrNoOp(options.onComment, "onComment");
|
|
281
|
+
this._shouldReconnect = this.getValidatedFunctionOr(options.shouldReconnect, this.DEFAULT_SHOULD_RECONNECT, "shouldReconnect");
|
|
282
|
+
this._calculateReconnectDelay = this.getValidatedFunctionOr(options.calculateReconnectDelay, this.DEFAULT_CALCULATE_RECONNECT_DELAY, "calculateReconnectDelay");
|
|
283
|
+
}
|
|
284
|
+
set onError(value) {
|
|
285
|
+
this._onError = this.getValidatedFunctionOrNoOp(value, "onError");
|
|
286
|
+
}
|
|
287
|
+
set onMessage(value) {
|
|
288
|
+
this._onMessage = this.getValidatedFunctionOrNoOp(value, "onMessage");
|
|
289
|
+
}
|
|
290
|
+
set onComment(value) {
|
|
291
|
+
this._onComment = this.getValidatedFunctionOrNoOp(value, "onComment");
|
|
292
|
+
}
|
|
293
|
+
set onConnect(value) {
|
|
294
|
+
this._onConnect = this.getValidatedFunctionOrNoOp(value, "onConnect");
|
|
295
|
+
}
|
|
296
|
+
set onDisconnect(value) {
|
|
297
|
+
this._onDisconnect = this.getValidatedFunctionOrNoOp(value, "onDisconnect");
|
|
298
|
+
}
|
|
299
|
+
set shouldReconnect(value) {
|
|
300
|
+
this._shouldReconnect = this.getValidatedFunctionOr(value, this.DEFAULT_SHOULD_RECONNECT, "shouldReconnect");
|
|
301
|
+
}
|
|
302
|
+
set calculateReconnectDelay(value) {
|
|
303
|
+
this._calculateReconnectDelay = this.getValidatedFunctionOr(value, this.DEFAULT_CALCULATE_RECONNECT_DELAY, "shouldReconnect");
|
|
304
|
+
}
|
|
305
|
+
get readyState() {
|
|
306
|
+
return this._readyState;
|
|
307
|
+
}
|
|
308
|
+
connect() {
|
|
309
|
+
if (this._readyState !== ReadyState.CLOSED) return;
|
|
310
|
+
this.reconnectAttempt = 0;
|
|
311
|
+
this.initiateConnection();
|
|
312
|
+
}
|
|
313
|
+
initiateConnection() {
|
|
314
|
+
this.abortController = new AbortController();
|
|
315
|
+
this._readyState = ReadyState.CONNECTING;
|
|
316
|
+
this._fetch(this.url, {
|
|
317
|
+
...this.requestOptions,
|
|
318
|
+
headers: this.buildHeaders(),
|
|
319
|
+
signal: this.abortController.signal,
|
|
320
|
+
cache: "no-store"
|
|
321
|
+
}).then(this.handleFetchResponse.bind(this)).catch((error) => {
|
|
322
|
+
if (error.name === "AbortError" || this.abortController.signal.aborted) {
|
|
323
|
+
this.close();
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
this._onError(error);
|
|
327
|
+
this.scheduleReconnect({ error });
|
|
328
|
+
});
|
|
329
|
+
}
|
|
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
|
+
async handleFetchResponse(response) {
|
|
348
|
+
const { body, status } = response;
|
|
349
|
+
if (status === 204) {
|
|
350
|
+
this.disconnected();
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
if (status >= 400) {
|
|
354
|
+
this.scheduleReconnect({ status });
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
if (!body) throw new MissingResponseBodyError("The server response did not have a body");
|
|
358
|
+
this._readyState = ReadyState.OPEN;
|
|
359
|
+
this._onConnect();
|
|
360
|
+
this.reconnectAttempt = 0;
|
|
361
|
+
const reader = body.getReader();
|
|
362
|
+
const decoder = new TextDecoder();
|
|
363
|
+
const parser = new EventSourceParser({
|
|
364
|
+
onEvent: (event) => {
|
|
365
|
+
if (typeof event.id === "string") this.lastEventId = event.id;
|
|
366
|
+
this._onMessage(event);
|
|
367
|
+
},
|
|
368
|
+
onComment: (comment) => this._onComment(comment),
|
|
369
|
+
onRetry: (retryDelay) => this.serverReconnectionTime = retryDelay
|
|
370
|
+
});
|
|
371
|
+
let done = false;
|
|
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();
|
|
378
|
+
this.scheduleReconnect({
|
|
379
|
+
status,
|
|
380
|
+
error: new StreamClosedError("The server response stream was closed")
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
scheduleReconnect(state) {
|
|
384
|
+
if (this.abortController.signal.aborted) return;
|
|
385
|
+
clearTimeout(this.reconnectTimeout);
|
|
386
|
+
this.reconnectAttempt += 1;
|
|
387
|
+
const reconnectionState = {
|
|
388
|
+
attempt: this.reconnectAttempt,
|
|
389
|
+
status: state.status,
|
|
390
|
+
error: state.error,
|
|
391
|
+
serverReconnectionTime: this.serverReconnectionTime
|
|
392
|
+
};
|
|
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 {
|
|
402
|
+
this.disconnected();
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
getValidatedFunctionOrNoOp(func, name) {
|
|
407
|
+
return this.getValidatedFunctionOr(func, () => {}, name);
|
|
408
|
+
}
|
|
409
|
+
getValidatedFunctionOr(func, defaultFunc, name) {
|
|
410
|
+
if (func == null) return defaultFunc;
|
|
411
|
+
else if (typeof func === "function") return func;
|
|
412
|
+
else throw new InvalidOptionError(`${name} must be a function`);
|
|
413
|
+
}
|
|
414
|
+
};
|
|
415
|
+
//#endregion
|
|
416
|
+
//#region ../eventsource/src/index.ts
|
|
417
|
+
const createEventSourceClient = (options) => {
|
|
418
|
+
return new EventSourceClient(options);
|
|
419
|
+
};
|
|
420
|
+
//#endregion
|
|
421
|
+
//#region ../js-client-core/src/StreamingTransport.ts
|
|
422
|
+
var StreamingTransport = class {
|
|
423
|
+
constructor(options) {
|
|
424
|
+
this.options = options;
|
|
425
|
+
_defineProperty(this, "logger", void 0);
|
|
426
|
+
_defineProperty(this, "eventSource", void 0);
|
|
427
|
+
_defineProperty(this, "eventEmitter", new Emitter());
|
|
428
|
+
_defineProperty(this, "url", void 0);
|
|
429
|
+
_defineProperty(this, "timeoutTimer", void 0);
|
|
430
|
+
this.options = options;
|
|
431
|
+
this.logger = options.logger;
|
|
432
|
+
this.url = options.resolveUrl("client/sse/v1", options.baseUrl);
|
|
433
|
+
}
|
|
434
|
+
async connect(context, timeout) {
|
|
435
|
+
if (this.eventSource) this.close();
|
|
436
|
+
const eventSourcePromise = new Promise((resolve, reject) => {
|
|
437
|
+
this.eventSource = createEventSourceClient({
|
|
438
|
+
url: this.url,
|
|
439
|
+
method: "POST",
|
|
440
|
+
headers: { "Content-Type": "application/json" },
|
|
441
|
+
fetch: this.options.fetch,
|
|
442
|
+
body: JSON.stringify({
|
|
443
|
+
givenContext: context,
|
|
444
|
+
metaContext: this.options.metaContext,
|
|
445
|
+
clientSdkKey: this.options.clientSdkKey
|
|
446
|
+
}),
|
|
447
|
+
onMessage: ({ data }) => {
|
|
448
|
+
this.dispatchMessage(data);
|
|
449
|
+
},
|
|
450
|
+
onConnect: () => {
|
|
451
|
+
this.logger.debug("[EventSourceTransport] Connected");
|
|
452
|
+
resolve(this);
|
|
453
|
+
},
|
|
454
|
+
shouldReconnect: (state) => {
|
|
455
|
+
const reconnect = !this.isStatusFatal(state.status);
|
|
456
|
+
if (!reconnect) reject(this.prepareFatalError(state.status, state.error));
|
|
457
|
+
return reconnect;
|
|
458
|
+
},
|
|
459
|
+
calculateReconnectDelay: (state) => {
|
|
460
|
+
const delay = this.options.connectionRetryDelay(state.attempt);
|
|
461
|
+
this.logger.warn(`[EventSourceTransport] Scheduling reconnect in ${delay}ms.`);
|
|
462
|
+
return delay;
|
|
463
|
+
},
|
|
464
|
+
onError: (error) => {
|
|
465
|
+
this.logger.debug("[EventSourceTransport] Error: ", error);
|
|
466
|
+
},
|
|
467
|
+
onDisconnect: () => {
|
|
468
|
+
this.logger.debug("[EventSourceTransport] Disconnected");
|
|
469
|
+
}
|
|
470
|
+
});
|
|
471
|
+
this.eventSource.connect();
|
|
472
|
+
});
|
|
473
|
+
clearTimeout(this.timeoutTimer);
|
|
474
|
+
this.timeoutTimer = void 0;
|
|
475
|
+
return Promise.race([eventSourcePromise, new Promise((resolve) => {
|
|
476
|
+
this.timeoutTimer = setTimeout(() => resolve(this), timeout);
|
|
477
|
+
})]).finally(() => clearTimeout(this.timeoutTimer));
|
|
478
|
+
}
|
|
479
|
+
dispatchMessage(data) {
|
|
480
|
+
try {
|
|
481
|
+
const json = JSON.parse(data);
|
|
482
|
+
this.eventEmitter.emit("configSetReceived", json);
|
|
483
|
+
} catch (error) {
|
|
484
|
+
this.logger.error("[EventSourceTransport] Error parsing and dispatching config data update: ", error);
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
prepareFatalError(responseStatus, error) {
|
|
488
|
+
const status = responseStatus !== null && responseStatus !== void 0 ? responseStatus : 0;
|
|
489
|
+
return new ConfigDirectorConnectionError(`${`Connection failed with status: ${responseStatus !== null && responseStatus !== void 0 ? responseStatus : "unknown"}.`}${error ? ` Error: ${error}` : ""}. This is an unrecoverable error, will not attempt to reconnect.`, status);
|
|
490
|
+
}
|
|
491
|
+
isStatusFatal(status) {
|
|
492
|
+
return !!status && status >= 400 && status < 500;
|
|
493
|
+
}
|
|
494
|
+
on(name, handler) {
|
|
495
|
+
this.eventEmitter.on(name, handler);
|
|
496
|
+
}
|
|
497
|
+
off(name, handler) {
|
|
498
|
+
this.eventEmitter.off(name, handler);
|
|
499
|
+
}
|
|
500
|
+
clear() {
|
|
501
|
+
this.eventEmitter.clear();
|
|
502
|
+
}
|
|
503
|
+
close() {
|
|
504
|
+
var _this$eventSource;
|
|
505
|
+
(_this$eventSource = this.eventSource) === null || _this$eventSource === void 0 || _this$eventSource.close();
|
|
506
|
+
clearTimeout(this.timeoutTimer);
|
|
507
|
+
}
|
|
508
|
+
dispose() {
|
|
509
|
+
this.close();
|
|
510
|
+
this.clear();
|
|
511
|
+
}
|
|
512
|
+
};
|
|
513
|
+
//#endregion
|
|
514
|
+
//#region ../shared/src/value-parser.ts
|
|
515
|
+
const getRequestedType = (defaultValue) => {
|
|
516
|
+
const baseType = typeof defaultValue;
|
|
517
|
+
if (baseType === "object") try {
|
|
518
|
+
var _constructor$name, _constructor;
|
|
519
|
+
return (_constructor$name = (_constructor = defaultValue.constructor) === null || _constructor === void 0 ? void 0 : _constructor.name) !== null && _constructor$name !== void 0 ? _constructor$name : baseType;
|
|
520
|
+
} catch {
|
|
521
|
+
return baseType;
|
|
522
|
+
}
|
|
523
|
+
else if (baseType === "function") try {
|
|
524
|
+
const functionName = defaultValue.name;
|
|
525
|
+
return functionName ? `function: ${functionName}` : baseType;
|
|
526
|
+
} catch {
|
|
527
|
+
return baseType;
|
|
528
|
+
}
|
|
529
|
+
return baseType;
|
|
530
|
+
};
|
|
531
|
+
const isNumericNativeType = (requestedType) => {
|
|
532
|
+
return requestedType === "number" || requestedType === "bigint";
|
|
533
|
+
};
|
|
534
|
+
const parseConfigValue = (configState, defaultValue) => {
|
|
535
|
+
const value = configState.value;
|
|
536
|
+
const requestedType = getRequestedType(defaultValue);
|
|
537
|
+
if (!value) return {
|
|
538
|
+
parsedValue: defaultValue,
|
|
539
|
+
requestedType,
|
|
540
|
+
usedDefault: true,
|
|
541
|
+
reason: "value-missing"
|
|
542
|
+
};
|
|
543
|
+
if (typeof defaultValue === "string") return {
|
|
544
|
+
parsedValue: value,
|
|
545
|
+
requestedType,
|
|
546
|
+
usedDefault: false,
|
|
547
|
+
reason: "found-match"
|
|
548
|
+
};
|
|
549
|
+
if (typeof defaultValue === "boolean" && configState.type === "boolean") {
|
|
550
|
+
const boolValue = parseConfigBoolean(value);
|
|
551
|
+
const hasBoolean = typeof boolValue === "boolean";
|
|
552
|
+
return {
|
|
553
|
+
parsedValue: hasBoolean ? boolValue : defaultValue,
|
|
554
|
+
requestedType,
|
|
555
|
+
usedDefault: !hasBoolean,
|
|
556
|
+
reason: hasBoolean ? "found-match" : "invalid-boolean"
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
if (isNumericNativeType(typeof defaultValue) && configState.type === "integer") {
|
|
560
|
+
const numValue = parseConfigInteger(value);
|
|
561
|
+
const hasNumber = typeof numValue === "number";
|
|
562
|
+
return {
|
|
563
|
+
parsedValue: hasNumber ? numValue : defaultValue,
|
|
564
|
+
requestedType,
|
|
565
|
+
usedDefault: !hasNumber,
|
|
566
|
+
reason: hasNumber ? "found-match" : "invalid-number"
|
|
567
|
+
};
|
|
568
|
+
}
|
|
569
|
+
if (isNumericNativeType(typeof defaultValue) && (configState.type === "float" || configState.type === "enum")) {
|
|
570
|
+
const numValue = parseConfigFloat(value);
|
|
571
|
+
const hasNumber = typeof numValue === "number";
|
|
572
|
+
return {
|
|
573
|
+
parsedValue: hasNumber ? numValue : defaultValue,
|
|
574
|
+
requestedType,
|
|
575
|
+
usedDefault: !hasNumber,
|
|
576
|
+
reason: hasNumber ? "found-match" : "invalid-number"
|
|
577
|
+
};
|
|
578
|
+
}
|
|
579
|
+
return {
|
|
580
|
+
parsedValue: value,
|
|
581
|
+
requestedType,
|
|
582
|
+
usedDefault: false,
|
|
583
|
+
reason: "found-match"
|
|
584
|
+
};
|
|
585
|
+
};
|
|
586
|
+
const parseConfigBoolean = (value) => {
|
|
587
|
+
if (!value) return;
|
|
588
|
+
const lowerValue = value.toLowerCase();
|
|
589
|
+
if (lowerValue != "true" && lowerValue != "false") return;
|
|
590
|
+
return lowerValue === "true";
|
|
591
|
+
};
|
|
592
|
+
const parseConfigInteger = (value) => {
|
|
593
|
+
if (!value) return;
|
|
594
|
+
const num = Number.parseInt(value);
|
|
595
|
+
if (isNaN(num) || !isFinite(num)) return;
|
|
596
|
+
return num;
|
|
597
|
+
};
|
|
598
|
+
const parseConfigFloat = (value) => {
|
|
599
|
+
if (!value) return;
|
|
600
|
+
const num = Number.parseFloat(value);
|
|
601
|
+
if (isNaN(num) || !isFinite(num)) return;
|
|
602
|
+
return num;
|
|
603
|
+
};
|
|
604
|
+
//#endregion
|
|
605
|
+
//#region ../shared/src/logger.ts
|
|
606
|
+
const LEVELS = {
|
|
607
|
+
off: -1,
|
|
608
|
+
error: 0,
|
|
609
|
+
warn: 1,
|
|
610
|
+
info: 2,
|
|
611
|
+
debug: 3
|
|
612
|
+
};
|
|
613
|
+
const buildDateFormatter = () => {
|
|
614
|
+
const baseOptions = {
|
|
615
|
+
year: "numeric",
|
|
616
|
+
month: "2-digit",
|
|
617
|
+
day: "2-digit",
|
|
618
|
+
hour: "2-digit",
|
|
619
|
+
minute: "2-digit",
|
|
620
|
+
second: "2-digit",
|
|
621
|
+
timeZoneName: "short"
|
|
622
|
+
};
|
|
623
|
+
try {
|
|
624
|
+
return new Intl.DateTimeFormat(void 0, {
|
|
625
|
+
...baseOptions,
|
|
626
|
+
fractionalSecondDigits: 3
|
|
627
|
+
});
|
|
628
|
+
} catch {
|
|
629
|
+
return new Intl.DateTimeFormat(void 0, baseOptions);
|
|
630
|
+
}
|
|
631
|
+
};
|
|
632
|
+
var DefaultConsoleLogger = class {
|
|
633
|
+
constructor(level, decorator) {
|
|
634
|
+
this.level = level;
|
|
635
|
+
this.decorator = decorator;
|
|
636
|
+
_defineProperty(this, "dateFormatter", void 0);
|
|
637
|
+
this.level = level;
|
|
638
|
+
this.decorator = decorator;
|
|
639
|
+
this.dateFormatter = buildDateFormatter();
|
|
640
|
+
}
|
|
641
|
+
debug(message, ...args) {
|
|
642
|
+
this.log(console.debug, "debug", message, ...args);
|
|
643
|
+
}
|
|
644
|
+
info(message, ...args) {
|
|
645
|
+
this.log(console.info, "info", message, ...args);
|
|
646
|
+
}
|
|
647
|
+
warn(message, ...args) {
|
|
648
|
+
this.log(console.warn, "warn", message, ...args);
|
|
649
|
+
}
|
|
650
|
+
error(message, ...args) {
|
|
651
|
+
this.log(console.error, "error", message, ...args);
|
|
652
|
+
}
|
|
653
|
+
log(loggerFunction, level, message, ...args) {
|
|
654
|
+
if (LEVELS[this.level] >= LEVELS[level]) {
|
|
655
|
+
var _this$decorator;
|
|
656
|
+
loggerFunction(`[${this.dateFormatter.format(/* @__PURE__ */ new Date())}] ${(_this$decorator = this.decorator) === null || _this$decorator === void 0 ? void 0 : _this$decorator.decorateMessage(message)}`, ...args);
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
};
|
|
660
|
+
//#endregion
|
|
661
|
+
//#region ../js-client-core/src/logger.ts
|
|
662
|
+
var LogMessageDecorator = class {
|
|
663
|
+
decorateMessage(message) {
|
|
664
|
+
return `[ConfigDirector:js-client-sdk] ${message}`;
|
|
665
|
+
}
|
|
666
|
+
};
|
|
667
|
+
const createDefaultLogger = (level, messageDecorator) => {
|
|
668
|
+
return new DefaultConsoleLogger(level !== null && level !== void 0 ? level : "warn", messageDecorator !== null && messageDecorator !== void 0 ? messageDecorator : new LogMessageDecorator());
|
|
669
|
+
};
|
|
670
|
+
//#endregion
|
|
671
|
+
//#region ../shared/src/fetchWithTimeout.ts
|
|
672
|
+
const fetchWithTimeout = async (timeout, resource, options, logger) => {
|
|
673
|
+
const abortController = new AbortController();
|
|
674
|
+
const abortTimeoutId = setTimeout(() => {
|
|
675
|
+
logger.debug("[fetchWithTimeout] Reached timeout, aborting request");
|
|
676
|
+
abortController.abort();
|
|
677
|
+
}, timeout);
|
|
678
|
+
try {
|
|
679
|
+
const response = await fetch(typeof resource === "string" || resource instanceof Request ? resource : resource.toString(), {
|
|
680
|
+
...options,
|
|
681
|
+
signal: abortController.signal
|
|
682
|
+
});
|
|
683
|
+
clearTimeout(abortTimeoutId);
|
|
684
|
+
return response;
|
|
685
|
+
} catch (error) {
|
|
686
|
+
logger.warn("[fetchWithTimeout] Fetch error: ", error);
|
|
687
|
+
clearTimeout(abortTimeoutId);
|
|
688
|
+
throw error;
|
|
689
|
+
}
|
|
690
|
+
};
|
|
691
|
+
//#endregion
|
|
692
|
+
//#region ../js-client-core/src/PullTransport.ts
|
|
693
|
+
var PullTransport = class {
|
|
694
|
+
constructor(options) {
|
|
695
|
+
this.options = options;
|
|
696
|
+
_defineProperty(this, "logger", void 0);
|
|
697
|
+
_defineProperty(this, "eventEmitter", new Emitter());
|
|
698
|
+
_defineProperty(this, "url", void 0);
|
|
699
|
+
_defineProperty(this, "fatalError", false);
|
|
700
|
+
this.options = options;
|
|
701
|
+
this.logger = options.logger;
|
|
702
|
+
this.url = options.resolveUrl("client/pull/v1", options.baseUrl);
|
|
703
|
+
}
|
|
704
|
+
async connect(context, timeout) {
|
|
705
|
+
if (this.fatalError) {
|
|
706
|
+
this.logger.warn("[PullTransport] There was a prior unrecoverable error. Ignoring attempt to reconnect.");
|
|
707
|
+
return this;
|
|
708
|
+
}
|
|
709
|
+
try {
|
|
710
|
+
const response = await fetchWithTimeout(timeout, this.url, {
|
|
711
|
+
method: "POST",
|
|
712
|
+
headers: { "Content-Type": "application/json" },
|
|
713
|
+
body: JSON.stringify({
|
|
714
|
+
givenContext: context,
|
|
715
|
+
metaContext: this.options.metaContext,
|
|
716
|
+
clientSdkKey: this.options.clientSdkKey
|
|
717
|
+
})
|
|
718
|
+
}, this.logger);
|
|
719
|
+
if (!response.ok) if (this.isStatusFatal(response.status)) {
|
|
720
|
+
this.fatalError = true;
|
|
721
|
+
throw this.prepareFatalResponseStatusError(response.status, await response.text());
|
|
722
|
+
} else throw new ConfigDirectorConnectionError(`Connection failed with status: ${response.status}`, response.status);
|
|
723
|
+
const json = JSON.parse(await response.text());
|
|
724
|
+
this.eventEmitter.emit("configSetReceived", json);
|
|
725
|
+
return this;
|
|
726
|
+
} catch (fetchError) {
|
|
727
|
+
if (isFetchErrorFatal(fetchError)) {
|
|
728
|
+
this.fatalError = true;
|
|
729
|
+
throw new ConfigDirectorConnectionError(`Connection failed with fatal error: ${fetchError}. This is an unrecoverable error, retry attempts will be ignored.`);
|
|
730
|
+
} else if (fetchError instanceof SyntaxError) throw new ConfigDirectorConnectionError(`Failed to parse the response from the server: ${fetchError}`);
|
|
731
|
+
else throw new ConfigDirectorConnectionError(`Connection failed with error: ${fetchError}.`);
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
prepareFatalResponseStatusError(responseStatus, errorBody) {
|
|
735
|
+
var _errorBody$trim$lengt, _errorBody$trim;
|
|
736
|
+
const status = responseStatus !== null && responseStatus !== void 0 ? responseStatus : 0;
|
|
737
|
+
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);
|
|
738
|
+
}
|
|
739
|
+
isStatusFatal(status) {
|
|
740
|
+
return !!status && status >= 400 && status < 500;
|
|
741
|
+
}
|
|
742
|
+
on(name, handler) {
|
|
743
|
+
this.eventEmitter.on(name, handler);
|
|
744
|
+
}
|
|
745
|
+
off(name, handler) {
|
|
746
|
+
this.eventEmitter.off(name, handler);
|
|
747
|
+
}
|
|
748
|
+
clear() {
|
|
749
|
+
this.eventEmitter.clear();
|
|
750
|
+
}
|
|
751
|
+
close() {}
|
|
752
|
+
dispose() {
|
|
753
|
+
this.close();
|
|
754
|
+
this.clear();
|
|
755
|
+
}
|
|
756
|
+
};
|
|
757
|
+
//#endregion
|
|
758
|
+
//#region ../shared/src/url.ts
|
|
759
|
+
const defaultUrlFactory = (input, base) => new URL(input, base === null || base === void 0 ? void 0 : base.toString());
|
|
760
|
+
//#endregion
|
|
761
|
+
//#region ../shared/src/constants.ts
|
|
762
|
+
const CLIENT_BASE_URL = "https://client-sdk-api.configdirector.com";
|
|
763
|
+
//#endregion
|
|
764
|
+
//#region ../js-client-core/src/DefaultConfigDirectorClient.ts
|
|
765
|
+
const MAX_EXPONENTIAL_DELAY = 9;
|
|
766
|
+
var DefaultConfigDirectorClient = class {
|
|
767
|
+
constructor(telemetryClient, clientSdkKey, sdkOptions, clientOptions, internalClientOptions) {
|
|
768
|
+
var _clientOptions$logger, _clientOptions$connec, _clientOptions$connec2, _internalClientOption, _this$parseUrl, _clientOptions$connec3, _clientOptions$connec4, _navigator, _internalClientOption2;
|
|
769
|
+
_defineProperty(this, "logger", void 0);
|
|
770
|
+
_defineProperty(this, "telemetryClient", void 0);
|
|
771
|
+
_defineProperty(this, "configSet", void 0);
|
|
772
|
+
_defineProperty(this, "handlersMap", /* @__PURE__ */ new Map());
|
|
773
|
+
_defineProperty(this, "transport", void 0);
|
|
774
|
+
_defineProperty(this, "eventEmitter", new Emitter());
|
|
775
|
+
_defineProperty(this, "timeout", void 0);
|
|
776
|
+
_defineProperty(this, "timeoutTimer", void 0);
|
|
777
|
+
_defineProperty(this, "ready", false);
|
|
778
|
+
_defineProperty(this, "readyPromise", void 0);
|
|
779
|
+
_defineProperty(this, "readyResolve", void 0);
|
|
780
|
+
_defineProperty(this, "currentContext", void 0);
|
|
781
|
+
_defineProperty(this, "streaming", void 0);
|
|
782
|
+
this.logger = (_clientOptions$logger = clientOptions === null || clientOptions === void 0 ? void 0 : clientOptions.logger) !== null && _clientOptions$logger !== void 0 ? _clientOptions$logger : createDefaultLogger();
|
|
783
|
+
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;
|
|
784
|
+
const urlFactory = (_internalClientOption = internalClientOptions === null || internalClientOptions === void 0 ? void 0 : internalClientOptions.urlFactory) !== null && _internalClientOption !== void 0 ? _internalClientOption : defaultUrlFactory;
|
|
785
|
+
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;
|
|
786
|
+
this.streaming = (clientOptions === null || clientOptions === void 0 || (_clientOptions$connec4 = clientOptions.connection) === null || _clientOptions$connec4 === void 0 ? void 0 : _clientOptions$connec4.streaming) === false ? false : true;
|
|
787
|
+
const transportConstructor = this.streaming ? StreamingTransport : PullTransport;
|
|
788
|
+
this.telemetryClient = telemetryClient;
|
|
789
|
+
this.transport = new transportConstructor({
|
|
790
|
+
clientSdkKey,
|
|
791
|
+
baseUrl,
|
|
792
|
+
resolveUrl: urlFactory,
|
|
793
|
+
metaContext: {
|
|
794
|
+
...clientOptions === null || clientOptions === void 0 ? void 0 : clientOptions.metadata,
|
|
795
|
+
sdkName: sdkOptions.sdkName,
|
|
796
|
+
sdkVersion: sdkOptions.sdkVersion,
|
|
797
|
+
userAgent: (_navigator = navigator) === null || _navigator === void 0 ? void 0 : _navigator.userAgent
|
|
798
|
+
},
|
|
799
|
+
logger: this.logger,
|
|
800
|
+
fetch: internalClientOptions === null || internalClientOptions === void 0 ? void 0 : internalClientOptions.fetch,
|
|
801
|
+
connectionRetryDelay: (_internalClientOption2 = internalClientOptions === null || internalClientOptions === void 0 ? void 0 : internalClientOptions.connectionRetryDelay) !== null && _internalClientOption2 !== void 0 ? _internalClientOption2 : ((attempt) => {
|
|
802
|
+
return Math.pow(2, Math.min(attempt, MAX_EXPONENTIAL_DELAY)) * 1e3;
|
|
803
|
+
})
|
|
804
|
+
});
|
|
805
|
+
this.transport.on("configSetReceived", (configSet) => {
|
|
806
|
+
var _this$readyResolve;
|
|
807
|
+
(_this$readyResolve = this.readyResolve) === null || _this$readyResolve === void 0 || _this$readyResolve.call(this);
|
|
808
|
+
const configKeys = Object.keys(configSet.configs);
|
|
809
|
+
if (!this.configSet || configSet.kind == "full") {
|
|
810
|
+
this.configSet = configSet;
|
|
811
|
+
this.eventEmitter.emit("configsUpdated", { keys: configKeys });
|
|
812
|
+
this.updateWatchers(configSet.configs);
|
|
813
|
+
} else {
|
|
814
|
+
this.configSet.configs = {
|
|
815
|
+
...this.configSet.configs,
|
|
816
|
+
...configSet.configs
|
|
817
|
+
};
|
|
818
|
+
this.eventEmitter.emit("configsUpdated", { keys: configKeys });
|
|
819
|
+
this.updateWatchers(configSet.configs);
|
|
820
|
+
}
|
|
821
|
+
this.logger.debug("[ConfigDirectorClient] ConfigSet updated from server:", { keys: configKeys });
|
|
822
|
+
});
|
|
823
|
+
}
|
|
824
|
+
async initialize(context) {
|
|
825
|
+
await this.connectToTransport(context, "initialization");
|
|
826
|
+
}
|
|
827
|
+
async updateContext(context) {
|
|
828
|
+
await this.connectToTransport(context, "context update");
|
|
829
|
+
}
|
|
830
|
+
async connectToTransport(context, caller) {
|
|
831
|
+
try {
|
|
832
|
+
this.ready = false;
|
|
833
|
+
this.readyPromise = new Promise((resolve) => {
|
|
834
|
+
this.readyResolve = resolve;
|
|
835
|
+
}).then(() => {
|
|
836
|
+
this.ready = true;
|
|
837
|
+
this.eventEmitter.emit("clientReady", { action: caller });
|
|
838
|
+
this.logger.debug("[ConfigDirectorClient] Received initial payload from the server, client is ready");
|
|
839
|
+
});
|
|
840
|
+
const startTime = (/* @__PURE__ */ new Date()).getTime();
|
|
841
|
+
await this.transport.connect(context !== null && context !== void 0 ? context : {}, this.timeout);
|
|
842
|
+
this.currentContext = context;
|
|
843
|
+
this.telemetryClient.updateContext(context);
|
|
844
|
+
const elapsedTime = (/* @__PURE__ */ new Date()).getTime() - startTime;
|
|
845
|
+
const remainingTimeout = this.timeout - elapsedTime;
|
|
846
|
+
clearTimeout(this.timeoutTimer);
|
|
847
|
+
this.timeoutTimer = void 0;
|
|
848
|
+
if (remainingTimeout > 0) await Promise.race([this.readyPromise, new Promise((resolve) => {
|
|
849
|
+
this.timeoutTimer = setTimeout(() => resolve(), remainingTimeout);
|
|
850
|
+
})]).finally(() => clearTimeout(this.timeoutTimer));
|
|
851
|
+
if (!this.ready) {
|
|
852
|
+
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.";
|
|
853
|
+
this.logger.warn(`[ConfigDirectorClient] Timed out waiting for ${caller} after ${this.timeout}ms. ${warningDetails}`);
|
|
854
|
+
}
|
|
855
|
+
} catch (error) {
|
|
856
|
+
this.logger.error(`[ConfigDirectorClient] An error occurred during ${caller}: `, error);
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
updateWatchers(configsMap) {
|
|
860
|
+
Object.values(configsMap).forEach((v) => this.updateWatchersForConfig(v));
|
|
861
|
+
}
|
|
862
|
+
updateWatchersForConfig(configState) {
|
|
863
|
+
var _this$handlersMap$get;
|
|
864
|
+
(_this$handlersMap$get = this.handlersMap.get(configState.key)) === null || _this$handlersMap$get === void 0 || _this$handlersMap$get.forEach((h) => {
|
|
865
|
+
const value = this.getValueFromConfigState(configState.key, configState, h.defaultValue);
|
|
866
|
+
h.handler(value);
|
|
867
|
+
});
|
|
868
|
+
}
|
|
869
|
+
watch(configKey, defaultValue, callback) {
|
|
870
|
+
this.validateDefaultValue(defaultValue);
|
|
871
|
+
const handlers = this.handlersMap.get(configKey);
|
|
872
|
+
const handlerWithOptions = {
|
|
873
|
+
handler: callback,
|
|
874
|
+
defaultValue,
|
|
875
|
+
requestedType: typeof defaultValue
|
|
876
|
+
};
|
|
877
|
+
if (handlers) handlers.push(handlerWithOptions);
|
|
878
|
+
else this.handlersMap.set(configKey, [handlerWithOptions]);
|
|
879
|
+
return () => this.unwatch(configKey, callback);
|
|
880
|
+
}
|
|
881
|
+
unwatch(configKey, callback) {
|
|
882
|
+
const handlers = this.handlersMap.get(configKey);
|
|
883
|
+
if (!handlers) return;
|
|
884
|
+
if (callback) {
|
|
885
|
+
const index = handlers.findIndex((h) => h.handler == callback);
|
|
886
|
+
if (index >= 0) handlers === null || handlers === void 0 || handlers.splice(index, 1);
|
|
887
|
+
} else handlers.splice(0);
|
|
888
|
+
}
|
|
889
|
+
getValue(configKey, defaultValue) {
|
|
890
|
+
var _this$configSet;
|
|
891
|
+
this.validateDefaultValue(defaultValue);
|
|
892
|
+
const configState = (_this$configSet = this.configSet) === null || _this$configSet === void 0 ? void 0 : _this$configSet.configs[configKey];
|
|
893
|
+
return this.getValueFromConfigState(configKey, configState, defaultValue);
|
|
894
|
+
}
|
|
895
|
+
getValueFromConfigState(configKey, configState, defaultValue) {
|
|
896
|
+
var _this$currentContext2;
|
|
897
|
+
if (!configState) {
|
|
898
|
+
var _this$currentContext;
|
|
899
|
+
this.logger.debug(`[ConfigDirectorClient] No config state found for '${configKey}', returning default value '${defaultValue}'`);
|
|
900
|
+
this.telemetryClient.evaluatedConfig({
|
|
901
|
+
contextId: (_this$currentContext = this.currentContext) === null || _this$currentContext === void 0 ? void 0 : _this$currentContext.id,
|
|
902
|
+
key: configKey,
|
|
903
|
+
defaultValue,
|
|
904
|
+
requestedType: getRequestedType(defaultValue),
|
|
905
|
+
evaluatedValue: defaultValue,
|
|
906
|
+
usedDefault: true,
|
|
907
|
+
evaluationReason: "config-state-missing"
|
|
908
|
+
});
|
|
909
|
+
return defaultValue;
|
|
910
|
+
}
|
|
911
|
+
const parseResult = parseConfigValue(configState, defaultValue);
|
|
912
|
+
this.telemetryClient.evaluatedConfig({
|
|
913
|
+
contextId: (_this$currentContext2 = this.currentContext) === null || _this$currentContext2 === void 0 ? void 0 : _this$currentContext2.id,
|
|
914
|
+
key: configKey,
|
|
915
|
+
defaultValue,
|
|
916
|
+
requestedType: parseResult.requestedType,
|
|
917
|
+
evaluatedValue: parseResult.parsedValue,
|
|
918
|
+
usedDefault: parseResult.usedDefault,
|
|
919
|
+
evaluationReason: parseResult.reason
|
|
920
|
+
});
|
|
921
|
+
this.logger.debug(`[ConfigDirectorClient] Evaluated '${configKey}' to '${parseResult.parsedValue}'`);
|
|
922
|
+
return parseResult.parsedValue;
|
|
923
|
+
}
|
|
924
|
+
parseUrl(url, urlFactory) {
|
|
925
|
+
if (!url) return;
|
|
926
|
+
try {
|
|
927
|
+
return urlFactory(url);
|
|
928
|
+
} catch (error) {
|
|
929
|
+
throw new ConfigDirectorValidationError(`Invalid base URL '${url}'. Parsing failed: ${error}`);
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
validateDefaultValue(defaultValue) {
|
|
933
|
+
if (defaultValue === void 0 || defaultValue === null) throw new ConfigDirectorValidationError("Invalid default value. The default value for a config must be defined and non-null.");
|
|
934
|
+
if (typeof defaultValue === "function") throw new ConfigDirectorValidationError("Invalid default value. The default value for a config cannot be a function.");
|
|
935
|
+
}
|
|
936
|
+
get isReady() {
|
|
937
|
+
return this.ready;
|
|
938
|
+
}
|
|
939
|
+
on(eventName, handler) {
|
|
940
|
+
this.eventEmitter.on(eventName, handler);
|
|
941
|
+
}
|
|
942
|
+
off(eventName, handler) {
|
|
943
|
+
this.eventEmitter.off(eventName, handler);
|
|
944
|
+
}
|
|
945
|
+
clear() {
|
|
946
|
+
this.logger.debug("[ConfigDirectorClient] clear() has been called, removing all observers");
|
|
947
|
+
this.eventEmitter.clear();
|
|
948
|
+
this.handlersMap.clear();
|
|
949
|
+
}
|
|
950
|
+
unwatchAll() {
|
|
951
|
+
this.handlersMap.clear();
|
|
952
|
+
}
|
|
953
|
+
pauseNetwork() {
|
|
954
|
+
this.logger.debug("[ConfigDirectorClient] pauseNetwork() called, pausing transport connection");
|
|
955
|
+
clearTimeout(this.timeoutTimer);
|
|
956
|
+
this.timeoutTimer = void 0;
|
|
957
|
+
this.transport.close();
|
|
958
|
+
this.ready = false;
|
|
959
|
+
}
|
|
960
|
+
async resumeNetwork() {
|
|
961
|
+
await this.connectToTransport(this.currentContext, "network resume");
|
|
962
|
+
}
|
|
963
|
+
async close() {
|
|
964
|
+
var _this$readyResolve2;
|
|
965
|
+
this.logger.debug("[ConfigDirectorClient] close() has been called, closing connection to server");
|
|
966
|
+
clearTimeout(this.timeoutTimer);
|
|
967
|
+
(_this$readyResolve2 = this.readyResolve) === null || _this$readyResolve2 === void 0 || _this$readyResolve2.call(this);
|
|
968
|
+
this.telemetryClient.close();
|
|
969
|
+
this.transport.close();
|
|
970
|
+
this.ready = false;
|
|
971
|
+
}
|
|
972
|
+
dispose() {
|
|
973
|
+
this.clear();
|
|
974
|
+
this.close();
|
|
975
|
+
}
|
|
976
|
+
};
|
|
977
|
+
//#endregion
|
|
978
|
+
//#region ../shared/src/telemetry/utils.ts
|
|
979
|
+
const CONFIG_VALUE_MAX_LENGTH = 500;
|
|
980
|
+
const sanitizeValue = (value, type) => {
|
|
981
|
+
if (type === "json") try {
|
|
982
|
+
return djb2Hash(JSON.stringify(value));
|
|
983
|
+
} catch {
|
|
984
|
+
return value.toString().slice(0, CONFIG_VALUE_MAX_LENGTH);
|
|
985
|
+
}
|
|
986
|
+
return value.toString().slice(0, CONFIG_VALUE_MAX_LENGTH);
|
|
987
|
+
};
|
|
988
|
+
const djb2Hash = (data) => {
|
|
989
|
+
return djb2(new TextEncoder().encode(data)).toString(16).padStart(8, "0");
|
|
990
|
+
};
|
|
991
|
+
const djb2 = (bytes) => {
|
|
992
|
+
let hash = 5381;
|
|
993
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
994
|
+
var _bytes$i;
|
|
995
|
+
hash = (hash << 5) + hash + ((_bytes$i = bytes[i]) !== null && _bytes$i !== void 0 ? _bytes$i : 0);
|
|
996
|
+
hash = hash >>> 0;
|
|
997
|
+
}
|
|
998
|
+
return hash;
|
|
999
|
+
};
|
|
1000
|
+
const isEventListEmpty = (eventList) => {
|
|
1001
|
+
const keys = Object.keys(eventList);
|
|
1002
|
+
if (keys.length == 0) return true;
|
|
1003
|
+
for (const key of keys) {
|
|
1004
|
+
var _eventList$key$length, _eventList$key;
|
|
1005
|
+
if (((_eventList$key$length = (_eventList$key = eventList[key]) === null || _eventList$key === void 0 ? void 0 : _eventList$key.length) !== null && _eventList$key$length !== void 0 ? _eventList$key$length : 0) > 0) return false;
|
|
1006
|
+
}
|
|
1007
|
+
return true;
|
|
1008
|
+
};
|
|
1009
|
+
const isDroppedEventsEmpty = (droppedEvents) => {
|
|
1010
|
+
if (!droppedEvents) return true;
|
|
1011
|
+
const keys = Object.keys(droppedEvents);
|
|
1012
|
+
if (keys.length == 0) return true;
|
|
1013
|
+
for (const key of keys) {
|
|
1014
|
+
var _droppedEvents$key;
|
|
1015
|
+
if (((_droppedEvents$key = droppedEvents[key]) !== null && _droppedEvents$key !== void 0 ? _droppedEvents$key : 0) > 0) return false;
|
|
1016
|
+
}
|
|
1017
|
+
return true;
|
|
1018
|
+
};
|
|
1019
|
+
//#endregion
|
|
1020
|
+
//#region ../js-client-core/src/telemetry/ClientEventReporter.ts
|
|
1021
|
+
var ClientEventReporter = class {
|
|
1022
|
+
constructor(options) {
|
|
1023
|
+
_defineProperty(this, "sdkKey", void 0);
|
|
1024
|
+
_defineProperty(this, "logger", void 0);
|
|
1025
|
+
_defineProperty(this, "url", void 0);
|
|
1026
|
+
_defineProperty(this, "executeRequests", true);
|
|
1027
|
+
this.sdkKey = options.sdkKey;
|
|
1028
|
+
this.logger = options.logger;
|
|
1029
|
+
this.url = options.urlFactory("client/telemetry/v1", options.baseUrl);
|
|
1030
|
+
}
|
|
1031
|
+
async report({ context, discreteEvents, aggregatedEvents, droppedEvents }) {
|
|
1032
|
+
if (!this.executeRequests) return {
|
|
1033
|
+
success: false,
|
|
1034
|
+
fatalError: true
|
|
1035
|
+
};
|
|
1036
|
+
const eventReport = {
|
|
1037
|
+
clientSdkKey: this.sdkKey,
|
|
1038
|
+
context,
|
|
1039
|
+
discreteEvents,
|
|
1040
|
+
aggregatedEvents,
|
|
1041
|
+
droppedEvents
|
|
1042
|
+
};
|
|
1043
|
+
if (this.isReportEmpty(eventReport)) return {
|
|
1044
|
+
success: true,
|
|
1045
|
+
fatalError: false
|
|
1046
|
+
};
|
|
1047
|
+
const response = await this.sendReport(eventReport);
|
|
1048
|
+
if (response.fatalError) this.executeRequests = false;
|
|
1049
|
+
return response;
|
|
1050
|
+
}
|
|
1051
|
+
isReportEmpty(eventReport) {
|
|
1052
|
+
return isEventListEmpty(eventReport.discreteEvents) && isEventListEmpty(eventReport.aggregatedEvents) && isDroppedEventsEmpty(eventReport.droppedEvents);
|
|
1053
|
+
}
|
|
1054
|
+
async sendReport(eventReport) {
|
|
1055
|
+
try {
|
|
1056
|
+
const response = await fetchWithTimeout(5e3, this.url, {
|
|
1057
|
+
method: "POST",
|
|
1058
|
+
body: JSON.stringify(eventReport),
|
|
1059
|
+
keepalive: true
|
|
1060
|
+
}, this.logger);
|
|
1061
|
+
const isFatalError = this.isStatusFatal(response.status);
|
|
1062
|
+
if (isFatalError) this.logger.warn(`[EventReporter] Received a fatal status response (${response.status}) from the telemetry endpoint. No more telemetry data will be sent.`);
|
|
1063
|
+
return {
|
|
1064
|
+
success: response.ok,
|
|
1065
|
+
fatalError: isFatalError
|
|
1066
|
+
};
|
|
1067
|
+
} catch (error) {
|
|
1068
|
+
const isFatal = isFetchErrorFatal(error);
|
|
1069
|
+
this.logger.warn(`[EventReporter] Error attempting to send telemetry data: ${error}`, { error });
|
|
1070
|
+
return {
|
|
1071
|
+
success: false,
|
|
1072
|
+
fatalError: isFatal
|
|
1073
|
+
};
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
isStatusFatal(status) {
|
|
1077
|
+
return !!status && status >= 400 && status < 500;
|
|
1078
|
+
}
|
|
1079
|
+
};
|
|
1080
|
+
//#endregion
|
|
1081
|
+
//#region ../shared/src/telemetry/EventAggregator.ts
|
|
1082
|
+
var EventAggregator = class {
|
|
1083
|
+
aggregate(snapshot) {
|
|
1084
|
+
if (snapshot.events.length == 0) return [];
|
|
1085
|
+
const map = /* @__PURE__ */ new Map();
|
|
1086
|
+
for (const event of snapshot.events) {
|
|
1087
|
+
var _map$get;
|
|
1088
|
+
const serializedEvent = JSON.stringify(event);
|
|
1089
|
+
const count = ((_map$get = map.get(serializedEvent)) === null || _map$get === void 0 ? void 0 : _map$get.count) || 0;
|
|
1090
|
+
map.set(serializedEvent, {
|
|
1091
|
+
event,
|
|
1092
|
+
count: count + 1
|
|
1093
|
+
});
|
|
1094
|
+
}
|
|
1095
|
+
return Array.from(map).map(([, v]) => {
|
|
1096
|
+
return {
|
|
1097
|
+
startTime: snapshot.startTime,
|
|
1098
|
+
endTime: snapshot.endTime,
|
|
1099
|
+
count: v.count,
|
|
1100
|
+
event: v.event
|
|
1101
|
+
};
|
|
1102
|
+
});
|
|
1103
|
+
}
|
|
1104
|
+
};
|
|
1105
|
+
//#endregion
|
|
1106
|
+
//#region ../shared/src/telemetry/EventQueue.ts
|
|
1107
|
+
var EventQueue = class {
|
|
1108
|
+
constructor(limit) {
|
|
1109
|
+
_defineProperty(this, "limit", void 0);
|
|
1110
|
+
_defineProperty(this, "startTime", void 0);
|
|
1111
|
+
_defineProperty(this, "_events", []);
|
|
1112
|
+
_defineProperty(this, "_droppedEventCount", 0);
|
|
1113
|
+
this.limit = limit !== null && limit !== void 0 ? limit : 1e3;
|
|
1114
|
+
}
|
|
1115
|
+
get events() {
|
|
1116
|
+
return this._events;
|
|
1117
|
+
}
|
|
1118
|
+
get reachedLimit() {
|
|
1119
|
+
return this._events.length >= this.limit;
|
|
1120
|
+
}
|
|
1121
|
+
get droppedEventCount() {
|
|
1122
|
+
return this._droppedEventCount;
|
|
1123
|
+
}
|
|
1124
|
+
push(...newEvents) {
|
|
1125
|
+
if (!this.startTime) this.startTime = /* @__PURE__ */ new Date();
|
|
1126
|
+
const newEventsCountToDrop = Math.max(0, newEvents.length - this.limit);
|
|
1127
|
+
const eventsToPush = newEvents.slice(newEventsCountToDrop, newEvents.length);
|
|
1128
|
+
this._droppedEventCount += newEventsCountToDrop;
|
|
1129
|
+
if (this._events.length + eventsToPush.length > this.limit) {
|
|
1130
|
+
const eventCountToDrop = this._events.length + eventsToPush.length - this.limit;
|
|
1131
|
+
this._events.splice(0, eventCountToDrop);
|
|
1132
|
+
this._droppedEventCount += eventCountToDrop;
|
|
1133
|
+
}
|
|
1134
|
+
return this._events.push(...eventsToPush);
|
|
1135
|
+
}
|
|
1136
|
+
takeSnapshot() {
|
|
1137
|
+
var _this$startTime;
|
|
1138
|
+
const endTime = /* @__PURE__ */ new Date();
|
|
1139
|
+
const startTime = (_this$startTime = this.startTime) !== null && _this$startTime !== void 0 ? _this$startTime : endTime;
|
|
1140
|
+
const eventsSnapshot = this._events.splice(0);
|
|
1141
|
+
const droppedCount = this._droppedEventCount;
|
|
1142
|
+
this.startTime = void 0;
|
|
1143
|
+
this._droppedEventCount = 0;
|
|
1144
|
+
return {
|
|
1145
|
+
startTime,
|
|
1146
|
+
endTime,
|
|
1147
|
+
events: eventsSnapshot,
|
|
1148
|
+
droppedCount
|
|
1149
|
+
};
|
|
1150
|
+
}
|
|
1151
|
+
clear() {
|
|
1152
|
+
this._events = [];
|
|
1153
|
+
this.startTime = void 0;
|
|
1154
|
+
this._droppedEventCount = 0;
|
|
1155
|
+
}
|
|
1156
|
+
};
|
|
1157
|
+
//#endregion
|
|
1158
|
+
//#region ../shared/src/telemetry/TelemetryEventCollector.ts
|
|
1159
|
+
var TelemetryEventCollector = class {
|
|
1160
|
+
constructor(options) {
|
|
1161
|
+
var _options$evaluationQu, _options$flushInterva, _options$initialFlush;
|
|
1162
|
+
_defineProperty(this, "logger", void 0);
|
|
1163
|
+
_defineProperty(this, "evaluationEventQueue", void 0);
|
|
1164
|
+
_defineProperty(this, "aggregator", new EventAggregator());
|
|
1165
|
+
_defineProperty(this, "flushIntervalDelay", void 0);
|
|
1166
|
+
_defineProperty(this, "flushTimeout", void 0);
|
|
1167
|
+
_defineProperty(this, "collectEvents", true);
|
|
1168
|
+
this.logger = options.logger;
|
|
1169
|
+
this.evaluationEventQueue = new EventQueue((_options$evaluationQu = options.evaluationQueueLimit) !== null && _options$evaluationQu !== void 0 ? _options$evaluationQu : 1e3);
|
|
1170
|
+
this.flushIntervalDelay = (_options$flushInterva = options.flushIntervalDelay) !== null && _options$flushInterva !== void 0 ? _options$flushInterva : 3e4;
|
|
1171
|
+
const initialDelay = (_options$initialFlush = options.initialFlushIntervalDelay) !== null && _options$initialFlush !== void 0 ? _options$initialFlush : 5e3;
|
|
1172
|
+
this.flushTimeout = setTimeout(() => this.flushAndScheduleNext(), initialDelay);
|
|
1173
|
+
}
|
|
1174
|
+
sanitizeValue(value, type) {
|
|
1175
|
+
return sanitizeValue(value, type);
|
|
1176
|
+
}
|
|
1177
|
+
async flushAndScheduleNext() {
|
|
1178
|
+
if ((await this.flush()).fatalError) {
|
|
1179
|
+
this.collectEvents = false;
|
|
1180
|
+
this.close();
|
|
1181
|
+
this.logger.warn("[TelemetryEventCollector] Received a fatal error from telemetry collection. No longer collecting events.");
|
|
1182
|
+
} else this.flushTimeout = setTimeout(() => this.flushAndScheduleNext(), this.flushIntervalDelay);
|
|
1183
|
+
}
|
|
1184
|
+
async flush() {
|
|
1185
|
+
var _this$reporter;
|
|
1186
|
+
if (!this.reporter) return {
|
|
1187
|
+
success: false,
|
|
1188
|
+
fatalError: false
|
|
1189
|
+
};
|
|
1190
|
+
const evaluationSnapshot = this.evaluationEventQueue.takeSnapshot();
|
|
1191
|
+
return await ((_this$reporter = this.reporter) === null || _this$reporter === void 0 ? void 0 : _this$reporter.report({
|
|
1192
|
+
context: this._context,
|
|
1193
|
+
discreteEvents: {},
|
|
1194
|
+
aggregatedEvents: { evaluatedConfig: this.aggregator.aggregate(evaluationSnapshot) },
|
|
1195
|
+
droppedEvents: { evaluatedConfig: evaluationSnapshot.droppedCount }
|
|
1196
|
+
}));
|
|
1197
|
+
}
|
|
1198
|
+
cancelScheduledFlush() {
|
|
1199
|
+
clearTimeout(this.flushTimeout);
|
|
1200
|
+
}
|
|
1201
|
+
async close() {
|
|
1202
|
+
this.collectEvents = false;
|
|
1203
|
+
clearTimeout(this.flushTimeout);
|
|
1204
|
+
await this.flush();
|
|
1205
|
+
this.evaluationEventQueue.clear();
|
|
1206
|
+
}
|
|
1207
|
+
dispose() {
|
|
1208
|
+
this.close();
|
|
1209
|
+
}
|
|
1210
|
+
};
|
|
1211
|
+
//#endregion
|
|
1212
|
+
//#region ../js-client-core/src/telemetry/ClientTelemetryEventCollector.ts
|
|
1213
|
+
var ClientTelemetryEventCollector = class extends TelemetryEventCollector {
|
|
1214
|
+
constructor(options) {
|
|
1215
|
+
super(options);
|
|
1216
|
+
_defineProperty(this, "reporter", void 0);
|
|
1217
|
+
_defineProperty(this, "_context", void 0);
|
|
1218
|
+
this.reporter = new ClientEventReporter(options);
|
|
1219
|
+
}
|
|
1220
|
+
async updateContext(value) {
|
|
1221
|
+
this.cancelScheduledFlush();
|
|
1222
|
+
await this.flush();
|
|
1223
|
+
this._context = value;
|
|
1224
|
+
await this.flushAndScheduleNext();
|
|
1225
|
+
}
|
|
1226
|
+
evaluatedConfig(event) {
|
|
1227
|
+
if (!this.collectEvents) return;
|
|
1228
|
+
this.evaluationEventQueue.push(event);
|
|
1229
|
+
}
|
|
1230
|
+
async forceFlush() {
|
|
1231
|
+
this.cancelScheduledFlush();
|
|
1232
|
+
await this.flushAndScheduleNext();
|
|
1233
|
+
}
|
|
1234
|
+
};
|
|
1235
|
+
//#endregion
|
|
1236
|
+
//#region src/logger.ts
|
|
1237
|
+
var MessageDecorator = class {
|
|
1238
|
+
decorateMessage(message) {
|
|
1239
|
+
return `[ConfigDirector:react-native-sdk] ${message}`;
|
|
1240
|
+
}
|
|
1241
|
+
};
|
|
1242
|
+
const createConsoleLogger = (level) => createDefaultLogger(level, new MessageDecorator());
|
|
1243
|
+
//#endregion
|
|
1244
|
+
//#region src/utf8Encoder.ts
|
|
1245
|
+
/**
|
|
1246
|
+
* Encodes a string to UTF-8 bytes. Uses the native TextEncoder when available
|
|
1247
|
+
* (Hermes), and falls back to an encodeURIComponent-based approach for JSC.
|
|
1248
|
+
*/
|
|
1249
|
+
const encodeUtf8 = (str) => {
|
|
1250
|
+
if (typeof globalThis["TextEncoder"] === "function") return new TextEncoder().encode(str);
|
|
1251
|
+
const uri = encodeURIComponent(str);
|
|
1252
|
+
const bytes = [];
|
|
1253
|
+
for (let i = 0; i < uri.length;) if (uri[i] === "%") {
|
|
1254
|
+
bytes.push(parseInt(uri.slice(i + 1, i + 3), 16));
|
|
1255
|
+
i += 3;
|
|
1256
|
+
} else {
|
|
1257
|
+
bytes.push(uri.charCodeAt(i));
|
|
1258
|
+
i++;
|
|
1259
|
+
}
|
|
1260
|
+
return new Uint8Array(bytes);
|
|
1261
|
+
};
|
|
1262
|
+
//#endregion
|
|
1263
|
+
//#region src/reactNativeStreamingFetch.ts
|
|
1264
|
+
/**
|
|
1265
|
+
* A fetch-compatible function for React Native that uses XMLHttpRequest to support
|
|
1266
|
+
* streaming SSE responses. React Native's built-in fetch buffers the entire response
|
|
1267
|
+
* body before resolving, which means it never resolves for persistent SSE connections.
|
|
1268
|
+
* XHR's onprogress callback fires incrementally as data arrives.
|
|
1269
|
+
*/
|
|
1270
|
+
const reactNativeStreamingFetch = (url, init) => {
|
|
1271
|
+
var _init$signal;
|
|
1272
|
+
if ((_init$signal = init.signal) === null || _init$signal === void 0 ? void 0 : _init$signal.aborted) return Promise.reject(new DOMException("The operation was aborted.", "AbortError"));
|
|
1273
|
+
return new Promise((resolve, reject) => {
|
|
1274
|
+
var _init$method, _ref;
|
|
1275
|
+
const xhr = new XMLHttpRequest();
|
|
1276
|
+
xhr.open((_init$method = init.method) !== null && _init$method !== void 0 ? _init$method : "GET", url);
|
|
1277
|
+
const headers = init.headers;
|
|
1278
|
+
if (headers) for (const [key, value] of Object.entries(headers)) xhr.setRequestHeader(key, value);
|
|
1279
|
+
let resolved = false;
|
|
1280
|
+
let lastLength = 0;
|
|
1281
|
+
let controller;
|
|
1282
|
+
const stream = new ReadableStream({ start(c) {
|
|
1283
|
+
controller = c;
|
|
1284
|
+
} });
|
|
1285
|
+
xhr.onreadystatechange = () => {
|
|
1286
|
+
if (xhr.readyState === XMLHttpRequest.HEADERS_RECEIVED && !resolved) {
|
|
1287
|
+
resolved = true;
|
|
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
|
+
});
|
|
1293
|
+
resolve({
|
|
1294
|
+
status: xhr.status,
|
|
1295
|
+
headers,
|
|
1296
|
+
body: stream
|
|
1297
|
+
});
|
|
1298
|
+
}
|
|
1299
|
+
};
|
|
1300
|
+
xhr.onprogress = () => {
|
|
1301
|
+
const newText = xhr.responseText.slice(lastLength);
|
|
1302
|
+
lastLength = xhr.responseText.length;
|
|
1303
|
+
if (newText) controller.enqueue(encodeUtf8(newText));
|
|
1304
|
+
};
|
|
1305
|
+
xhr.onload = () => {
|
|
1306
|
+
const remaining = xhr.responseText.slice(lastLength);
|
|
1307
|
+
if (remaining) controller.enqueue(encodeUtf8(remaining));
|
|
1308
|
+
controller.close();
|
|
1309
|
+
};
|
|
1310
|
+
xhr.onerror = () => {
|
|
1311
|
+
const error = /* @__PURE__ */ new TypeError("Network request failed");
|
|
1312
|
+
if (!resolved) reject(error);
|
|
1313
|
+
else controller.error(error);
|
|
1314
|
+
xhr.abort();
|
|
1315
|
+
};
|
|
1316
|
+
if (init.signal) init.signal.addEventListener("abort", () => {
|
|
1317
|
+
xhr.abort();
|
|
1318
|
+
if (!resolved) {
|
|
1319
|
+
const abortError = /* @__PURE__ */ new Error("The operation was aborted.");
|
|
1320
|
+
abortError.name = "AbortError";
|
|
1321
|
+
reject(abortError);
|
|
1322
|
+
} else try {
|
|
1323
|
+
controller.close();
|
|
1324
|
+
} catch {}
|
|
1325
|
+
});
|
|
1326
|
+
xhr.send((_ref = init.body) !== null && _ref !== void 0 ? _ref : null);
|
|
1327
|
+
});
|
|
1328
|
+
};
|
|
1329
|
+
//#endregion
|
|
1330
|
+
//#region src/ReactNativeTelemetryClient.ts
|
|
1331
|
+
var ReactNativeTelemetryClient = class {
|
|
1332
|
+
constructor(sdkKey, baseUrl, logger, urlFactory) {
|
|
1333
|
+
_defineProperty(this, "collector", void 0);
|
|
1334
|
+
this.collector = new ClientTelemetryEventCollector({
|
|
1335
|
+
sdkKey,
|
|
1336
|
+
logger,
|
|
1337
|
+
baseUrl,
|
|
1338
|
+
urlFactory
|
|
1339
|
+
});
|
|
1340
|
+
}
|
|
1341
|
+
async updateContext(value) {
|
|
1342
|
+
return await this.collector.updateContext(value);
|
|
1343
|
+
}
|
|
1344
|
+
evaluatedConfig(event) {
|
|
1345
|
+
this.collector.evaluatedConfig(this.sanitizeEvaluatedConfigEvent(event));
|
|
1346
|
+
}
|
|
1347
|
+
async close() {
|
|
1348
|
+
await this.collector.close();
|
|
1349
|
+
}
|
|
1350
|
+
sanitizeEvaluatedConfigEvent(event) {
|
|
1351
|
+
return {
|
|
1352
|
+
...event,
|
|
1353
|
+
defaultValue: sanitizeValue(event.defaultValue, event.type),
|
|
1354
|
+
evaluatedValue: sanitizeValue(event.evaluatedValue, event.type)
|
|
1355
|
+
};
|
|
1356
|
+
}
|
|
1357
|
+
};
|
|
1358
|
+
//#endregion
|
|
1359
|
+
//#region src/url.ts
|
|
1360
|
+
var MinimalUrl = class {
|
|
1361
|
+
constructor(input, base) {
|
|
1362
|
+
_defineProperty(this, "href", void 0);
|
|
1363
|
+
if (base) {
|
|
1364
|
+
const b = base.toString();
|
|
1365
|
+
this.href = (b.endsWith("/") ? b : b + "/") + input;
|
|
1366
|
+
} else this.href = input;
|
|
1367
|
+
}
|
|
1368
|
+
toString() {
|
|
1369
|
+
return this.href;
|
|
1370
|
+
}
|
|
1371
|
+
};
|
|
1372
|
+
const urlFactory = (input, base) => new MinimalUrl(input, base);
|
|
1373
|
+
//#endregion
|
|
1374
|
+
//#region src/client.ts
|
|
1375
|
+
const createClient = (clientSdkKey, clientOptions) => {
|
|
1376
|
+
var _clientOptions$logger, _clientOptions$connec;
|
|
1377
|
+
const logger = (_clientOptions$logger = clientOptions === null || clientOptions === void 0 ? void 0 : clientOptions.logger) !== null && _clientOptions$logger !== void 0 ? _clientOptions$logger : createConsoleLogger("warn");
|
|
1378
|
+
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
|
+
sdkName: "react-native-sdk",
|
|
1380
|
+
sdkVersion: "0.1.0"
|
|
1381
|
+
}, {
|
|
1382
|
+
...clientOptions,
|
|
1383
|
+
logger
|
|
1384
|
+
}, {
|
|
1385
|
+
fetch: reactNativeStreamingFetch,
|
|
1386
|
+
urlFactory
|
|
1387
|
+
});
|
|
1388
|
+
};
|
|
1389
|
+
//#endregion
|
|
1390
|
+
//#region src/context.tsx
|
|
1391
|
+
const createReactContext = () => React.createContext({ status: "loading" });
|
|
1392
|
+
const reactContext = createReactContext();
|
|
1393
|
+
//#endregion
|
|
1394
|
+
//#region src/provider.tsx
|
|
1395
|
+
var ConfigDirectorProvider = class extends Component {
|
|
1396
|
+
constructor(props) {
|
|
1397
|
+
var _props$logger;
|
|
1398
|
+
super(props);
|
|
1399
|
+
_defineProperty(this, "appStateSubscription", null);
|
|
1400
|
+
_defineProperty(this, "netInfoUnsubscribe", null);
|
|
1401
|
+
_defineProperty(this, "wasOffline", false);
|
|
1402
|
+
_defineProperty(this, "handleAppStateChange", async (nextState) => {
|
|
1403
|
+
if (nextState === "active") await this.reconnect();
|
|
1404
|
+
else if (nextState === "background") {
|
|
1405
|
+
var _this$state$client;
|
|
1406
|
+
(_this$state$client = this.state.client) === null || _this$state$client === void 0 || _this$state$client.pauseNetwork();
|
|
1407
|
+
}
|
|
1408
|
+
});
|
|
1409
|
+
_defineProperty(this, "handleConnectivityChange", ({ isConnected }) => {
|
|
1410
|
+
if (!isConnected) this.wasOffline = true;
|
|
1411
|
+
else if (this.wasOffline && AppState.currentState !== "background") {
|
|
1412
|
+
this.wasOffline = false;
|
|
1413
|
+
this.reconnect();
|
|
1414
|
+
}
|
|
1415
|
+
});
|
|
1416
|
+
_defineProperty(this, "reconnect", async () => {
|
|
1417
|
+
var _this$state$client2;
|
|
1418
|
+
await ((_this$state$client2 = this.state.client) === null || _this$state$client2 === void 0 ? void 0 : _this$state$client2.resumeNetwork());
|
|
1419
|
+
});
|
|
1420
|
+
this.state = {
|
|
1421
|
+
client: createClient(props.sdkKey, {
|
|
1422
|
+
connection: {
|
|
1423
|
+
url: props.url,
|
|
1424
|
+
timeout: props.timeout
|
|
1425
|
+
},
|
|
1426
|
+
metadata: {
|
|
1427
|
+
appName: props.appName,
|
|
1428
|
+
appVersion: props.appVersion
|
|
1429
|
+
},
|
|
1430
|
+
logger: (_props$logger = props.logger) !== null && _props$logger !== void 0 ? _props$logger : createConsoleLogger("warn")
|
|
1431
|
+
}),
|
|
1432
|
+
status: "loading"
|
|
1433
|
+
};
|
|
1434
|
+
}
|
|
1435
|
+
async componentDidMount() {
|
|
1436
|
+
var _this$state$client3, _this$state$client4, _this$state$client5, _this$state$client6;
|
|
1437
|
+
(_this$state$client3 = this.state.client) === null || _this$state$client3 === void 0 || _this$state$client3.on("configsUpdated", () => {
|
|
1438
|
+
this.setState({ updatedAt: /* @__PURE__ */ new Date() });
|
|
1439
|
+
});
|
|
1440
|
+
(_this$state$client4 = this.state.client) === null || _this$state$client4 === void 0 || _this$state$client4.on("clientReady", () => {
|
|
1441
|
+
this.setState({ status: "ready" });
|
|
1442
|
+
});
|
|
1443
|
+
await ((_this$state$client5 = this.state.client) === null || _this$state$client5 === void 0 ? void 0 : _this$state$client5.initialize(this.props.context));
|
|
1444
|
+
if (!((_this$state$client6 = this.state.client) === null || _this$state$client6 === void 0 ? void 0 : _this$state$client6.isReady)) this.setState({ status: "default" });
|
|
1445
|
+
this.appStateSubscription = AppState.addEventListener("change", this.handleAppStateChange);
|
|
1446
|
+
if (this.props.netInfoSubscribe) this.netInfoUnsubscribe = this.props.netInfoSubscribe(this.handleConnectivityChange);
|
|
1447
|
+
}
|
|
1448
|
+
async componentDidUpdate(prevProps) {
|
|
1449
|
+
if (prevProps.context !== this.props.context) {
|
|
1450
|
+
var _this$state$client7, _this$props$context, _this$state$client8;
|
|
1451
|
+
this.setState({ status: "loading" });
|
|
1452
|
+
await ((_this$state$client7 = this.state.client) === null || _this$state$client7 === void 0 ? void 0 : _this$state$client7.updateContext((_this$props$context = this.props.context) !== null && _this$props$context !== void 0 ? _this$props$context : {}));
|
|
1453
|
+
if (!((_this$state$client8 = this.state.client) === null || _this$state$client8 === void 0 ? void 0 : _this$state$client8.isReady)) this.setState({ status: "default" });
|
|
1454
|
+
}
|
|
1455
|
+
}
|
|
1456
|
+
componentWillUnmount() {
|
|
1457
|
+
var _this$appStateSubscri, _this$netInfoUnsubscr, _this$state$client9;
|
|
1458
|
+
(_this$appStateSubscri = this.appStateSubscription) === null || _this$appStateSubscri === void 0 || _this$appStateSubscri.remove();
|
|
1459
|
+
(_this$netInfoUnsubscr = this.netInfoUnsubscribe) === null || _this$netInfoUnsubscr === void 0 || _this$netInfoUnsubscr.call(this);
|
|
1460
|
+
(_this$state$client9 = this.state.client) === null || _this$state$client9 === void 0 || _this$state$client9.dispose();
|
|
1461
|
+
}
|
|
1462
|
+
render() {
|
|
1463
|
+
return /* @__PURE__ */ jsx(reactContext.Provider, {
|
|
1464
|
+
value: this.state,
|
|
1465
|
+
children: this.props.children
|
|
1466
|
+
});
|
|
1467
|
+
}
|
|
1468
|
+
};
|
|
1469
|
+
//#endregion
|
|
1470
|
+
//#region src/errors.ts
|
|
1471
|
+
var ConfigDirectorReactContextError = class ConfigDirectorReactContextError extends Error {
|
|
1472
|
+
constructor(message) {
|
|
1473
|
+
super(message);
|
|
1474
|
+
_defineProperty(this, "name", "ConfigDirectorReactContextError");
|
|
1475
|
+
Object.setPrototypeOf(this, ConfigDirectorReactContextError.prototype);
|
|
1476
|
+
}
|
|
1477
|
+
};
|
|
1478
|
+
//#endregion
|
|
1479
|
+
//#region src/hooks.tsx
|
|
1480
|
+
const noContextError = () => {
|
|
1481
|
+
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
|
+
};
|
|
1483
|
+
const useConfigValue = (key, defaultValue) => {
|
|
1484
|
+
const configDirectorReactContext = useContext(reactContext);
|
|
1485
|
+
if (!(configDirectorReactContext === null || configDirectorReactContext === void 0 ? void 0 : configDirectorReactContext.client)) throw noContextError();
|
|
1486
|
+
return {
|
|
1487
|
+
value: configDirectorReactContext.client.getValue(key, defaultValue),
|
|
1488
|
+
readyStatus: configDirectorReactContext.status,
|
|
1489
|
+
loading: configDirectorReactContext.status === "loading"
|
|
1490
|
+
};
|
|
1491
|
+
};
|
|
1492
|
+
const useConfigDirectorContext = () => {
|
|
1493
|
+
const configDirectorReactContext = useContext(reactContext);
|
|
1494
|
+
if (!(configDirectorReactContext === null || configDirectorReactContext === void 0 ? void 0 : configDirectorReactContext.client)) throw noContextError();
|
|
1495
|
+
const updateContext = async (context) => {
|
|
1496
|
+
var _configDirectorReactC;
|
|
1497
|
+
await ((_configDirectorReactC = configDirectorReactContext.client) === null || _configDirectorReactC === void 0 ? void 0 : _configDirectorReactC.updateContext(context));
|
|
1498
|
+
};
|
|
1499
|
+
return { updateContext };
|
|
1500
|
+
};
|
|
1501
|
+
const useConfigDirectorClient = () => {
|
|
1502
|
+
const configDirectorReactContext = useContext(reactContext);
|
|
1503
|
+
if (!(configDirectorReactContext === null || configDirectorReactContext === void 0 ? void 0 : configDirectorReactContext.client)) throw noContextError();
|
|
1504
|
+
return { client: configDirectorReactContext.client };
|
|
1505
|
+
};
|
|
1506
|
+
//#endregion
|
|
1507
|
+
export { ConfigDirectorProvider, ConfigDirectorReactContextError, createClient, createConsoleLogger, useConfigDirectorClient, useConfigDirectorContext, useConfigValue };
|