@configdirector/react-native-sdk 0.7.0 → 0.8.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.
@@ -1,3 +1,4 @@
1
+ import { sha256 } from "@noble/hashes/sha2.js";
1
2
  import React, { Component, useContext as useContext$1 } from "react";
2
3
  import { AppState } from "react-native";
3
4
  import { jsx } from "react/jsx-runtime";
@@ -550,13 +551,15 @@ const parseConfigValue = (configState, defaultValue) => {
550
551
  const requestedType = getRequestedType(defaultValue);
551
552
  if (!value) return {
552
553
  parsedValue: defaultValue,
554
+ parsedValueId: void 0,
553
555
  requestedType,
554
556
  usedDefault: true,
555
557
  reason: "value-missing"
556
558
  };
557
- if (configState.type === "json") return parseJson(value, defaultValue, requestedType);
559
+ if (configState.type === "json") return parseJson(value, defaultValue, requestedType, configState.valueId);
558
560
  if (typeof defaultValue === "string") return {
559
561
  parsedValue: value,
562
+ parsedValueId: configState.valueId,
560
563
  requestedType,
561
564
  usedDefault: false,
562
565
  reason: "found-match"
@@ -566,6 +569,7 @@ const parseConfigValue = (configState, defaultValue) => {
566
569
  const hasBoolean = typeof boolValue === "boolean";
567
570
  return {
568
571
  parsedValue: hasBoolean ? boolValue : defaultValue,
572
+ parsedValueId: hasBoolean ? configState.valueId : void 0,
569
573
  requestedType,
570
574
  usedDefault: !hasBoolean,
571
575
  reason: hasBoolean ? "found-match" : "invalid-boolean"
@@ -576,6 +580,7 @@ const parseConfigValue = (configState, defaultValue) => {
576
580
  const hasNumber = typeof numValue === "number";
577
581
  return {
578
582
  parsedValue: hasNumber ? numValue : defaultValue,
583
+ parsedValueId: hasNumber ? configState.valueId : void 0,
579
584
  requestedType,
580
585
  usedDefault: !hasNumber,
581
586
  reason: hasNumber ? "found-match" : "invalid-number"
@@ -586,6 +591,7 @@ const parseConfigValue = (configState, defaultValue) => {
586
591
  const hasNumber = typeof numValue === "number";
587
592
  return {
588
593
  parsedValue: hasNumber ? numValue : defaultValue,
594
+ parsedValueId: hasNumber ? configState.valueId : void 0,
589
595
  requestedType,
590
596
  usedDefault: !hasNumber,
591
597
  reason: hasNumber ? "found-match" : "invalid-number"
@@ -593,6 +599,7 @@ const parseConfigValue = (configState, defaultValue) => {
593
599
  }
594
600
  return {
595
601
  parsedValue: value,
602
+ parsedValueId: configState.valueId,
596
603
  requestedType,
597
604
  usedDefault: false,
598
605
  reason: "found-match"
@@ -616,9 +623,10 @@ const parseConfigFloat = (value) => {
616
623
  if (isNaN(num) || !isFinite(num)) return;
617
624
  return num;
618
625
  };
619
- const parseJson = (jsonString, defaultValue, requestedType) => {
626
+ const parseJson = (jsonString, defaultValue, requestedType, valueId) => {
620
627
  if (typeof defaultValue === "string") return {
621
628
  parsedValue: jsonString,
629
+ parsedValueId: valueId,
622
630
  requestedType,
623
631
  usedDefault: false,
624
632
  reason: "found-match"
@@ -626,6 +634,7 @@ const parseJson = (jsonString, defaultValue, requestedType) => {
626
634
  try {
627
635
  return {
628
636
  parsedValue: JSON.parse(jsonString),
637
+ parsedValueId: valueId,
629
638
  requestedType,
630
639
  usedDefault: false,
631
640
  reason: "found-match"
@@ -633,6 +642,7 @@ const parseJson = (jsonString, defaultValue, requestedType) => {
633
642
  } catch {
634
643
  return {
635
644
  parsedValue: defaultValue,
645
+ parsedValueId: void 0,
636
646
  requestedType,
637
647
  usedDefault: true,
638
648
  reason: "invalid-json"
@@ -1029,6 +1039,7 @@ var DefaultConfigDirectorClient = class {
1029
1039
  defaultValue,
1030
1040
  requestedType: parseResult.requestedType,
1031
1041
  evaluatedValue: parseResult.parsedValue,
1042
+ evaluatedValueId: parseResult.parsedValueId,
1032
1043
  usedDefault: parseResult.usedDefault,
1033
1044
  evaluationReason: parseResult.reason
1034
1045
  });
@@ -1094,26 +1105,36 @@ var DefaultConfigDirectorClient = class {
1094
1105
  //#endregion
1095
1106
  //#region ../shared/src/telemetry/utils.ts
1096
1107
  const CONFIG_VALUE_MAX_LENGTH = 500;
1097
- const sanitizeValue = (value, type) => {
1098
- if (type === "json" || type == null && typeof value === "object") try {
1099
- return { digest: djb2Hash(JSON.stringify(value)) };
1100
- } catch {
1101
- return { digest: value.toString() };
1108
+ const compactTelemetryValue = async (tv, valueIdGenerator) => {
1109
+ if (tv.valueId) return { valueId: tv.valueId };
1110
+ if (tv.value) {
1111
+ if (tv.value.length <= CONFIG_VALUE_MAX_LENGTH && tv.type != "json") return { value: tv.value };
1112
+ return { valueId: await valueIdGenerator(tv.value) };
1102
1113
  }
1103
- if (value.toString().length > CONFIG_VALUE_MAX_LENGTH) return { digest: djb2Hash(value.toString()) };
1104
- else return { value: value.toString() };
1105
- };
1106
- const djb2Hash = (data) => {
1107
- return djb2(new TextEncoder().encode(data)).toString(16).padStart(8, "0");
1114
+ return tv;
1108
1115
  };
1109
- const djb2 = (bytes) => {
1110
- let hash = 5381;
1111
- for (let i = 0; i < bytes.length; i++) {
1112
- var _bytes$i;
1113
- hash = (hash << 5) + hash + ((_bytes$i = bytes[i]) !== null && _bytes$i !== void 0 ? _bytes$i : 0);
1114
- hash = hash >>> 0;
1115
- }
1116
- return hash;
1116
+ const mapToTelemetryValue = (ev) => {
1117
+ var _ev$value;
1118
+ if (ev.type === "json" || ev.type == null && typeof ev.value === "object") {
1119
+ if (ev.valueId) return {
1120
+ valueId: ev.valueId,
1121
+ type: "json"
1122
+ };
1123
+ try {
1124
+ return {
1125
+ value: JSON.stringify(ev.value),
1126
+ type: "json"
1127
+ };
1128
+ } catch {
1129
+ return {
1130
+ value: ev.value.toString(),
1131
+ type: "json"
1132
+ };
1133
+ }
1134
+ }
1135
+ const stringValue = (_ev$value = ev.value) === null || _ev$value === void 0 ? void 0 : _ev$value.toString();
1136
+ if (stringValue && stringValue.length <= CONFIG_VALUE_MAX_LENGTH) return { value: stringValue };
1137
+ return ev.valueId ? { valueId: ev.valueId } : { value: stringValue };
1117
1138
  };
1118
1139
  const isEventListEmpty = (eventList) => {
1119
1140
  const keys = Object.keys(eventList);
@@ -1289,9 +1310,6 @@ var TelemetryEventCollector = class {
1289
1310
  const initialDelay = (_options$initialFlush = options.initialFlushIntervalDelay) !== null && _options$initialFlush !== void 0 ? _options$initialFlush : 5e3;
1290
1311
  this.flushTimeout = setTimeout(() => this.flushAndScheduleNext(), initialDelay);
1291
1312
  }
1292
- sanitizeValue(value, type) {
1293
- return sanitizeValue(value, type);
1294
- }
1295
1313
  async flushAndScheduleNext() {
1296
1314
  if ((await this.flush()).fatalError) {
1297
1315
  this.collectEvents = false;
@@ -1299,20 +1317,6 @@ var TelemetryEventCollector = class {
1299
1317
  this.logger.warn("[TelemetryEventCollector] Received a fatal error from telemetry collection. No longer collecting events.");
1300
1318
  } else this.flushTimeout = setTimeout(() => this.flushAndScheduleNext(), this.flushIntervalDelay);
1301
1319
  }
1302
- async flush() {
1303
- var _this$reporter;
1304
- if (!this.reporter) return {
1305
- success: false,
1306
- fatalError: false
1307
- };
1308
- const evaluationSnapshot = this.evaluationEventQueue.takeSnapshot();
1309
- return await ((_this$reporter = this.reporter) === null || _this$reporter === void 0 ? void 0 : _this$reporter.report({
1310
- context: this._context,
1311
- discreteEvents: {},
1312
- aggregatedEvents: { evaluatedConfig: this.aggregator.aggregate(evaluationSnapshot) },
1313
- droppedEvents: { evaluatedConfig: evaluationSnapshot.droppedCount }
1314
- }));
1315
- }
1316
1320
  cancelScheduledFlush() {
1317
1321
  clearTimeout(this.flushTimeout);
1318
1322
  }
@@ -1327,13 +1331,36 @@ var TelemetryEventCollector = class {
1327
1331
  }
1328
1332
  };
1329
1333
  //#endregion
1334
+ //#region ../shared/src/telemetry/EvaluatedConfigEventPreprocessor.ts
1335
+ var EvaluatedConfigEventPreprocessor = class {
1336
+ constructor(valueIdGenerator) {
1337
+ this.valueIdGenerator = valueIdGenerator;
1338
+ this.valueIdGenerator = valueIdGenerator;
1339
+ }
1340
+ async process(snapshot) {
1341
+ const events = await Promise.all(snapshot.events.map(async (e) => {
1342
+ return {
1343
+ ...e,
1344
+ defaultValue: await compactTelemetryValue(e.defaultValue, this.valueIdGenerator),
1345
+ evaluatedValue: await compactTelemetryValue(e.evaluatedValue, this.valueIdGenerator)
1346
+ };
1347
+ }));
1348
+ return {
1349
+ ...snapshot,
1350
+ events
1351
+ };
1352
+ }
1353
+ };
1354
+ //#endregion
1330
1355
  //#region ../js-client-core/src/telemetry/ClientTelemetryEventCollector.ts
1331
1356
  var ClientTelemetryEventCollector = class extends TelemetryEventCollector {
1332
1357
  constructor(options) {
1333
1358
  super(options);
1359
+ _defineProperty(this, "evaluationEventSnapshotPreprocessor", void 0);
1334
1360
  _defineProperty(this, "reporter", void 0);
1335
1361
  _defineProperty(this, "_context", void 0);
1336
1362
  this.reporter = new ClientEventReporter(options);
1363
+ this.evaluationEventSnapshotPreprocessor = new EvaluatedConfigEventPreprocessor(options.valueIdGenerator);
1337
1364
  }
1338
1365
  async updateContext(value) {
1339
1366
  this.cancelScheduledFlush();
@@ -1349,6 +1376,20 @@ var ClientTelemetryEventCollector = class extends TelemetryEventCollector {
1349
1376
  this.cancelScheduledFlush();
1350
1377
  await this.flushAndScheduleNext();
1351
1378
  }
1379
+ async flush() {
1380
+ var _this$reporter;
1381
+ if (!this.reporter) return {
1382
+ success: false,
1383
+ fatalError: false
1384
+ };
1385
+ const evaluationSnapshot = await this.evaluationEventSnapshotPreprocessor.process(this.evaluationEventQueue.takeSnapshot());
1386
+ return await ((_this$reporter = this.reporter) === null || _this$reporter === void 0 ? void 0 : _this$reporter.report({
1387
+ context: this._context,
1388
+ discreteEvents: {},
1389
+ aggregatedEvents: { evaluatedConfig: this.aggregator.aggregate(evaluationSnapshot) },
1390
+ droppedEvents: { evaluatedConfig: evaluationSnapshot.droppedCount }
1391
+ }));
1392
+ }
1352
1393
  };
1353
1394
  //#endregion
1354
1395
  //#region src/logger.ts
@@ -1475,6 +1516,29 @@ const parseHeaders = (raw) => {
1475
1516
  return headers;
1476
1517
  };
1477
1518
  //#endregion
1519
+ //#region ../shared/src/base62-encoder.ts
1520
+ const BASE62 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
1521
+ const toBase62 = (buf, length) => {
1522
+ const ZERO = BigInt(0);
1523
+ const BASE = BigInt(62);
1524
+ const hex = Array.from(buf).map((b) => b.toString(16).padStart(2, "0")).join("");
1525
+ let n = BigInt("0x" + hex);
1526
+ let result = "";
1527
+ while (n > ZERO) {
1528
+ result = BASE62[Number(n % BASE)] + result;
1529
+ n /= BASE;
1530
+ }
1531
+ return result.padStart(length, "0");
1532
+ };
1533
+ //#endregion
1534
+ //#region src/value-id-generator.ts
1535
+ const DIGEST_BYTES = 16;
1536
+ const BASE62_LENGTH = Math.ceil(DIGEST_BYTES * 8 / Math.log2(62));
1537
+ const generateValueId = (v) => {
1538
+ var _v$toString;
1539
+ return toBase62(sha256(encodeUtf8((_v$toString = v === null || v === void 0 ? void 0 : v.toString()) !== null && _v$toString !== void 0 ? _v$toString : "")).subarray(0, DIGEST_BYTES), BASE62_LENGTH);
1540
+ };
1541
+ //#endregion
1478
1542
  //#region src/ReactNativeTelemetryClient.ts
1479
1543
  var ReactNativeTelemetryClient = class {
1480
1544
  constructor(sdkKey, baseUrl, logger, urlFactory) {
@@ -1483,7 +1547,8 @@ var ReactNativeTelemetryClient = class {
1483
1547
  sdkKey,
1484
1548
  logger,
1485
1549
  baseUrl,
1486
- urlFactory
1550
+ urlFactory,
1551
+ valueIdGenerator: async (v) => generateValueId(v)
1487
1552
  });
1488
1553
  }
1489
1554
  async updateContext(value) {
@@ -1498,8 +1563,15 @@ var ReactNativeTelemetryClient = class {
1498
1563
  sanitizeEvaluatedConfigEvent(event) {
1499
1564
  return {
1500
1565
  ...event,
1501
- defaultValue: sanitizeValue(event.defaultValue, event.type),
1502
- evaluatedValue: sanitizeValue(event.evaluatedValue, event.type)
1566
+ defaultValue: mapToTelemetryValue({
1567
+ value: event.defaultValue,
1568
+ type: event.type
1569
+ }),
1570
+ evaluatedValue: mapToTelemetryValue({
1571
+ value: event.evaluatedValue,
1572
+ valueId: event.evaluatedValueId,
1573
+ type: event.type
1574
+ })
1503
1575
  };
1504
1576
  }
1505
1577
  };
@@ -1525,7 +1597,7 @@ const createClient = (clientSdkKey, clientOptions) => {
1525
1597
  const logger = (_clientOptions$logger = clientOptions === null || clientOptions === void 0 ? void 0 : clientOptions.logger) !== null && _clientOptions$logger !== void 0 ? _clientOptions$logger : createConsoleLogger("warn");
1526
1598
  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, {
1527
1599
  sdkName: "react-native-sdk",
1528
- sdkVersion: "0.7.0"
1600
+ sdkVersion: "0.8.0"
1529
1601
  }, {
1530
1602
  ...clientOptions,
1531
1603
  logger
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@configdirector/react-native-sdk",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "React Native client SDK for ConfigDirector. ConfigDirector is a remote configuration and feature flag service.",
5
5
  "license": "MIT",
6
6
  "author": "ConfigDirector",
@@ -52,6 +52,9 @@
52
52
  "publishConfig": {
53
53
  "registry": "https://registry.npmjs.org/"
54
54
  },
55
+ "dependencies": {
56
+ "@noble/hashes": "^2.2.0"
57
+ },
55
58
  "devDependencies": {
56
59
  "@babel/core": "^7.29.0",
57
60
  "@eslint/js": "^10.0.1",
@@ -112,7 +115,7 @@
112
115
  "^@eventsource/(.*)$": "<rootDir>/../eventsource/src/$1"
113
116
  },
114
117
  "transformIgnorePatterns": [
115
- "node_modules/(?!((jest-)?react-native|@react-native(-community)?)/)"
118
+ "node_modules/(?!((jest-)?react-native|@react-native(-community)?|@noble)/)"
116
119
  ]
117
120
  }
118
121
  }