@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.
@@ -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 };