@configdirector/react-native-sdk 0.6.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.
@@ -52,6 +52,14 @@ type ConfigDirectorContext = {
52
52
  traits?: {
53
53
  [key: string]: unknown;
54
54
  };
55
+ /**
56
+ * Whether or not to treat the context as anonymous during evaluation. When `true`, the
57
+ * context values will be used for targeting rules evaluation but the context will not be
58
+ * persisted and will not appear in the dashboard.
59
+ *
60
+ * @default false
61
+ */
62
+ anonymous?: boolean;
55
63
  };
56
64
  /**
57
65
  * Metadata about your application. It is recommended you include these values when configuring
@@ -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";
@@ -453,7 +454,8 @@ var StreamingTransport = class {
453
454
  body: JSON.stringify({
454
455
  givenContext: context,
455
456
  metaContext: this.options.metaContext,
456
- clientSdkKey: this.options.clientSdkKey
457
+ clientSdkKey: this.options.clientSdkKey,
458
+ instanceId: this.options.instanceId
457
459
  }),
458
460
  onMessage: ({ data }) => {
459
461
  this.dispatchMessage(data);
@@ -549,13 +551,15 @@ const parseConfigValue = (configState, defaultValue) => {
549
551
  const requestedType = getRequestedType(defaultValue);
550
552
  if (!value) return {
551
553
  parsedValue: defaultValue,
554
+ parsedValueId: void 0,
552
555
  requestedType,
553
556
  usedDefault: true,
554
557
  reason: "value-missing"
555
558
  };
556
- if (configState.type === "json") return parseJson(value, defaultValue, requestedType);
559
+ if (configState.type === "json") return parseJson(value, defaultValue, requestedType, configState.valueId);
557
560
  if (typeof defaultValue === "string") return {
558
561
  parsedValue: value,
562
+ parsedValueId: configState.valueId,
559
563
  requestedType,
560
564
  usedDefault: false,
561
565
  reason: "found-match"
@@ -565,6 +569,7 @@ const parseConfigValue = (configState, defaultValue) => {
565
569
  const hasBoolean = typeof boolValue === "boolean";
566
570
  return {
567
571
  parsedValue: hasBoolean ? boolValue : defaultValue,
572
+ parsedValueId: hasBoolean ? configState.valueId : void 0,
568
573
  requestedType,
569
574
  usedDefault: !hasBoolean,
570
575
  reason: hasBoolean ? "found-match" : "invalid-boolean"
@@ -575,6 +580,7 @@ const parseConfigValue = (configState, defaultValue) => {
575
580
  const hasNumber = typeof numValue === "number";
576
581
  return {
577
582
  parsedValue: hasNumber ? numValue : defaultValue,
583
+ parsedValueId: hasNumber ? configState.valueId : void 0,
578
584
  requestedType,
579
585
  usedDefault: !hasNumber,
580
586
  reason: hasNumber ? "found-match" : "invalid-number"
@@ -585,6 +591,7 @@ const parseConfigValue = (configState, defaultValue) => {
585
591
  const hasNumber = typeof numValue === "number";
586
592
  return {
587
593
  parsedValue: hasNumber ? numValue : defaultValue,
594
+ parsedValueId: hasNumber ? configState.valueId : void 0,
588
595
  requestedType,
589
596
  usedDefault: !hasNumber,
590
597
  reason: hasNumber ? "found-match" : "invalid-number"
@@ -592,6 +599,7 @@ const parseConfigValue = (configState, defaultValue) => {
592
599
  }
593
600
  return {
594
601
  parsedValue: value,
602
+ parsedValueId: configState.valueId,
595
603
  requestedType,
596
604
  usedDefault: false,
597
605
  reason: "found-match"
@@ -615,9 +623,10 @@ const parseConfigFloat = (value) => {
615
623
  if (isNaN(num) || !isFinite(num)) return;
616
624
  return num;
617
625
  };
618
- const parseJson = (jsonString, defaultValue, requestedType) => {
626
+ const parseJson = (jsonString, defaultValue, requestedType, valueId) => {
619
627
  if (typeof defaultValue === "string") return {
620
628
  parsedValue: jsonString,
629
+ parsedValueId: valueId,
621
630
  requestedType,
622
631
  usedDefault: false,
623
632
  reason: "found-match"
@@ -625,6 +634,7 @@ const parseJson = (jsonString, defaultValue, requestedType) => {
625
634
  try {
626
635
  return {
627
636
  parsedValue: JSON.parse(jsonString),
637
+ parsedValueId: valueId,
628
638
  requestedType,
629
639
  usedDefault: false,
630
640
  reason: "found-match"
@@ -632,6 +642,7 @@ const parseJson = (jsonString, defaultValue, requestedType) => {
632
642
  } catch {
633
643
  return {
634
644
  parsedValue: defaultValue,
645
+ parsedValueId: void 0,
635
646
  requestedType,
636
647
  usedDefault: true,
637
648
  reason: "invalid-json"
@@ -819,6 +830,7 @@ var PollingTransport = class extends AbstractPollingTransport {
819
830
  givenContext: context,
820
831
  metaContext: this.options.metaContext,
821
832
  clientSdkKey: this.options.clientSdkKey,
833
+ instanceId: this.options.instanceId,
822
834
  lastUpdateTimestamp: this.lastUpdateTimestamp
823
835
  })
824
836
  }, this.logger);
@@ -876,6 +888,8 @@ var DefaultConfigDirectorClient = class {
876
888
  _defineProperty(this, "readyResolve", void 0);
877
889
  _defineProperty(this, "currentContext", void 0);
878
890
  _defineProperty(this, "connectionMode", void 0);
891
+ _defineProperty(this, "instanceId", void 0);
892
+ this.instanceId = crypto.randomUUID();
879
893
  this.logger = (_clientOptions$logger = clientOptions === null || clientOptions === void 0 ? void 0 : clientOptions.logger) !== null && _clientOptions$logger !== void 0 ? _clientOptions$logger : createDefaultLogger();
880
894
  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;
881
895
  const urlFactory = (_internalClientOption = internalClientOptions === null || internalClientOptions === void 0 ? void 0 : internalClientOptions.urlFactory) !== null && _internalClientOption !== void 0 ? _internalClientOption : defaultUrlFactory;
@@ -884,6 +898,7 @@ var DefaultConfigDirectorClient = class {
884
898
  const transportConstructor = this.getTransportConstructor(this.connectionMode);
885
899
  this.telemetryClient = telemetryClient;
886
900
  this.transport = new transportConstructor({
901
+ instanceId: this.instanceId,
887
902
  clientSdkKey,
888
903
  baseUrl,
889
904
  resolveUrl: urlFactory,
@@ -1024,6 +1039,7 @@ var DefaultConfigDirectorClient = class {
1024
1039
  defaultValue,
1025
1040
  requestedType: parseResult.requestedType,
1026
1041
  evaluatedValue: parseResult.parsedValue,
1042
+ evaluatedValueId: parseResult.parsedValueId,
1027
1043
  usedDefault: parseResult.usedDefault,
1028
1044
  evaluationReason: parseResult.reason
1029
1045
  });
@@ -1089,26 +1105,36 @@ var DefaultConfigDirectorClient = class {
1089
1105
  //#endregion
1090
1106
  //#region ../shared/src/telemetry/utils.ts
1091
1107
  const CONFIG_VALUE_MAX_LENGTH = 500;
1092
- const sanitizeValue = (value, type) => {
1093
- if (type === "json" || type == null && typeof value === "object") try {
1094
- return { digest: djb2Hash(JSON.stringify(value)) };
1095
- } catch {
1096
- 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) };
1097
1113
  }
1098
- if (value.toString().length > CONFIG_VALUE_MAX_LENGTH) return { digest: djb2Hash(value.toString()) };
1099
- else return { value: value.toString() };
1114
+ return tv;
1100
1115
  };
1101
- const djb2Hash = (data) => {
1102
- return djb2(new TextEncoder().encode(data)).toString(16).padStart(8, "0");
1103
- };
1104
- const djb2 = (bytes) => {
1105
- let hash = 5381;
1106
- for (let i = 0; i < bytes.length; i++) {
1107
- var _bytes$i;
1108
- hash = (hash << 5) + hash + ((_bytes$i = bytes[i]) !== null && _bytes$i !== void 0 ? _bytes$i : 0);
1109
- hash = hash >>> 0;
1110
- }
1111
- 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 };
1112
1138
  };
1113
1139
  const isEventListEmpty = (eventList) => {
1114
1140
  const keys = Object.keys(eventList);
@@ -1284,9 +1310,6 @@ var TelemetryEventCollector = class {
1284
1310
  const initialDelay = (_options$initialFlush = options.initialFlushIntervalDelay) !== null && _options$initialFlush !== void 0 ? _options$initialFlush : 5e3;
1285
1311
  this.flushTimeout = setTimeout(() => this.flushAndScheduleNext(), initialDelay);
1286
1312
  }
1287
- sanitizeValue(value, type) {
1288
- return sanitizeValue(value, type);
1289
- }
1290
1313
  async flushAndScheduleNext() {
1291
1314
  if ((await this.flush()).fatalError) {
1292
1315
  this.collectEvents = false;
@@ -1294,20 +1317,6 @@ var TelemetryEventCollector = class {
1294
1317
  this.logger.warn("[TelemetryEventCollector] Received a fatal error from telemetry collection. No longer collecting events.");
1295
1318
  } else this.flushTimeout = setTimeout(() => this.flushAndScheduleNext(), this.flushIntervalDelay);
1296
1319
  }
1297
- async flush() {
1298
- var _this$reporter;
1299
- if (!this.reporter) return {
1300
- success: false,
1301
- fatalError: false
1302
- };
1303
- const evaluationSnapshot = this.evaluationEventQueue.takeSnapshot();
1304
- return await ((_this$reporter = this.reporter) === null || _this$reporter === void 0 ? void 0 : _this$reporter.report({
1305
- context: this._context,
1306
- discreteEvents: {},
1307
- aggregatedEvents: { evaluatedConfig: this.aggregator.aggregate(evaluationSnapshot) },
1308
- droppedEvents: { evaluatedConfig: evaluationSnapshot.droppedCount }
1309
- }));
1310
- }
1311
1320
  cancelScheduledFlush() {
1312
1321
  clearTimeout(this.flushTimeout);
1313
1322
  }
@@ -1322,13 +1331,36 @@ var TelemetryEventCollector = class {
1322
1331
  }
1323
1332
  };
1324
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
1325
1355
  //#region ../js-client-core/src/telemetry/ClientTelemetryEventCollector.ts
1326
1356
  var ClientTelemetryEventCollector = class extends TelemetryEventCollector {
1327
1357
  constructor(options) {
1328
1358
  super(options);
1359
+ _defineProperty(this, "evaluationEventSnapshotPreprocessor", void 0);
1329
1360
  _defineProperty(this, "reporter", void 0);
1330
1361
  _defineProperty(this, "_context", void 0);
1331
1362
  this.reporter = new ClientEventReporter(options);
1363
+ this.evaluationEventSnapshotPreprocessor = new EvaluatedConfigEventPreprocessor(options.valueIdGenerator);
1332
1364
  }
1333
1365
  async updateContext(value) {
1334
1366
  this.cancelScheduledFlush();
@@ -1344,6 +1376,20 @@ var ClientTelemetryEventCollector = class extends TelemetryEventCollector {
1344
1376
  this.cancelScheduledFlush();
1345
1377
  await this.flushAndScheduleNext();
1346
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
+ }
1347
1393
  };
1348
1394
  //#endregion
1349
1395
  //#region src/logger.ts
@@ -1470,6 +1516,29 @@ const parseHeaders = (raw) => {
1470
1516
  return headers;
1471
1517
  };
1472
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
1473
1542
  //#region src/ReactNativeTelemetryClient.ts
1474
1543
  var ReactNativeTelemetryClient = class {
1475
1544
  constructor(sdkKey, baseUrl, logger, urlFactory) {
@@ -1478,7 +1547,8 @@ var ReactNativeTelemetryClient = class {
1478
1547
  sdkKey,
1479
1548
  logger,
1480
1549
  baseUrl,
1481
- urlFactory
1550
+ urlFactory,
1551
+ valueIdGenerator: async (v) => generateValueId(v)
1482
1552
  });
1483
1553
  }
1484
1554
  async updateContext(value) {
@@ -1493,8 +1563,15 @@ var ReactNativeTelemetryClient = class {
1493
1563
  sanitizeEvaluatedConfigEvent(event) {
1494
1564
  return {
1495
1565
  ...event,
1496
- defaultValue: sanitizeValue(event.defaultValue, event.type),
1497
- 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
+ })
1498
1575
  };
1499
1576
  }
1500
1577
  };
@@ -1520,7 +1597,7 @@ const createClient = (clientSdkKey, clientOptions) => {
1520
1597
  const logger = (_clientOptions$logger = clientOptions === null || clientOptions === void 0 ? void 0 : clientOptions.logger) !== null && _clientOptions$logger !== void 0 ? _clientOptions$logger : createConsoleLogger("warn");
1521
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, {
1522
1599
  sdkName: "react-native-sdk",
1523
- sdkVersion: "0.6.0"
1600
+ sdkVersion: "0.8.0"
1524
1601
  }, {
1525
1602
  ...clientOptions,
1526
1603
  logger
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@configdirector/react-native-sdk",
3
- "version": "0.6.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
  }