@configdirector/react-native-sdk 1.9.0 → 1.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -11,10 +11,13 @@ npm install --save @configdirector/react-native-sdk
11
11
  ```
12
12
 
13
13
  Install `@react-native-async-storage/async-storage` as well, so the SDK recognizes the app install
14
- across app starts. For a context without an `id`, ConfigDirector derives an identifier from an ID
15
- the SDK stores for the install, so the context keeps that identifier, and counts as one monthly
16
- active user, however often the app is opened. It is an optional peer dependency and works in Expo
17
- Go. Without it, every app start looks like a new install and the SDK logs a warning.
14
+ across app starts and keeps the last values it received on the device. For a context without an
15
+ `id`, ConfigDirector derives an identifier from an ID the SDK stores for the install, so the context
16
+ keeps that identifier, and counts as one monthly active user, however often the app is opened. The
17
+ last values are served on the next app start of the same context until live values arrive, so an
18
+ app opened without a network shows them instead of the in-code default values. It is an optional
19
+ peer dependency and works in Expo Go. Without it, every app start looks like a new install, nothing
20
+ is kept on the device, and the SDK logs a warning.
18
21
 
19
22
  ```bash
20
23
  npm install --save @react-native-async-storage/async-storage
@@ -1,4 +1,4 @@
1
- import { c as NativeJsonValue, d as ConfigDirectorContext, i as ConfigDirectorClient, l as NativeValue, p as ConfigDirectorLogger, u as NativeValues } from "./types-BBQmjjf5.mjs";
1
+ import { c as NativeJsonValue, d as ConfigDirectorContext, i as ConfigDirectorClient, l as NativeValue, p as ConfigDirectorLogger, u as NativeValues } from "./types-DmbDeZqL.mjs";
2
2
 
3
3
  //#region ../shared/src/errors.d.ts
4
4
  declare class ConfigDirectorConnectionError extends Error {
@@ -1,4 +1,4 @@
1
- import { c as createDefaultLogger, d as ConfigDirectorConnectionError, f as ConfigDirectorValidationError, l as nativeValueConfigState, m as _defineProperty, p as Emitter, r as DefaultConfigDirectorClient, s as discardingTelemetryClient, t as INSTALLED_TEST_CLIENT_SLOT } from "./installedTestClient-BluKT8Qj.mjs";
1
+ import { c as createDefaultLogger, d as ConfigDirectorConnectionError, f as ConfigDirectorValidationError, l as nativeValueConfigState, m as _defineProperty, p as Emitter, r as DefaultConfigDirectorClient, s as discardingTelemetryClient, t as INSTALLED_TEST_CLIENT_SLOT } from "./installedTestClient-BaCuY_Ru.mjs";
2
2
  //#region ../js-client-core/src/testing/InMemoryConnection.ts
3
3
  const TEST_ENVIRONMENT = {
4
4
  environmentId: "test-environment",
@@ -295,7 +295,7 @@ const installTestClient = (testClient) => {
295
295
  const createTestClient = (options) => {
296
296
  return createTestClient$1({
297
297
  sdkName: "react-native-sdk",
298
- sdkVersion: "1.9.0"
298
+ sdkVersion: "1.10.0"
299
299
  }, options);
300
300
  };
301
301
  //#endregion
@@ -1,4 +1,4 @@
1
- import { a as ConfigDirectorClientOptions, d as ConfigDirectorContext, f as ConfigDirectorLogMessageDecorator, g as ConnectionMode, h as ConfigValueType, i as ConfigDirectorClient, m as ConfigDirectorLoggingLevel, n as CachingProvider, o as OfflineValue, p as ConfigDirectorLogger, r as ClientHooks, s as OfflineValues, t as CachingOptions } from "./types-BBQmjjf5.mjs";
1
+ import { _ as ConnectionMode, a as ConfigDirectorClientOptions, d as ConfigDirectorContext, f as ConfigDirectorLogMessageDecorator, g as ConfigValueType, h as ConfigEvaluation, i as ConfigDirectorClient, m as ConfigDirectorLoggingLevel, n as CachingProvider, o as OfflineValue, p as ConfigDirectorLogger, r as ClientHooks, s as OfflineValues, t as CachingOptions, v as EvaluationReason } from "./types-DmbDeZqL.mjs";
2
2
  import { Component, PropsWithChildren } from "react";
3
3
  import * as _$react_jsx_runtime0 from "react/jsx-runtime";
4
4
 
@@ -151,4 +151,4 @@ declare class ConfigDirectorReactContextError extends Error {
151
151
  //#region src/logger.d.ts
152
152
  declare const createConsoleLogger: (level?: ConfigDirectorLoggingLevel) => DefaultConsoleLogger;
153
153
  //#endregion
154
- export { type CachingOptions, type CachingProvider, type ClientStatus, type ConfigDirectorClient, type ConfigDirectorClientOptions, type ConfigDirectorContext, type ConfigDirectorLogger, type ConfigDirectorLoggingLevel, ConfigDirectorProvider, type ConfigDirectorProviderClientProps, type ConfigDirectorProviderOptions, type ConfigDirectorProviderProps, ConfigDirectorReactContextError, type ConnectionMode, type NetInfoSubscribe, type OfflineValue, type OfflineValues, createClient, createConsoleLogger, useClient, useConfigValue, useContext };
154
+ export { type CachingOptions, type CachingProvider, type ClientStatus, type ConfigDirectorClient, type ConfigDirectorClientOptions, type ConfigDirectorContext, type ConfigDirectorLogger, type ConfigDirectorLoggingLevel, ConfigDirectorProvider, type ConfigDirectorProviderClientProps, type ConfigDirectorProviderOptions, type ConfigDirectorProviderProps, ConfigDirectorReactContextError, type ConfigEvaluation, type ConnectionMode, type EvaluationReason, type NetInfoSubscribe, type OfflineValue, type OfflineValues, createClient, createConsoleLogger, useClient, useConfigValue, useContext };
@@ -1,4 +1,4 @@
1
- import { a as DEFAULT_CACHING_SIZE_LIMIT_BYTES, c as createDefaultLogger, i as CACHED_VALUES_STORAGE_KEY_PREFIX, m as _defineProperty, n as readInstalledTestClient, o as fetchWithTimeout, r as DefaultConfigDirectorClient, u as isFetchErrorFatal } from "./installedTestClient-BluKT8Qj.mjs";
1
+ import { a as DEFAULT_CACHING_SIZE_LIMIT_BYTES, c as createDefaultLogger, i as CACHED_VALUES_STORAGE_KEY_PREFIX, m as _defineProperty, n as readInstalledTestClient, o as fetchWithTimeout, r as DefaultConfigDirectorClient, u as isFetchErrorFatal } from "./installedTestClient-BaCuY_Ru.mjs";
2
2
  import React, { Component, useContext as useContext$1 } from "react";
3
3
  import { AppState, NativeModules } from "react-native";
4
4
  import { sha256 } from "@noble/hashes/sha2.js";
@@ -65,20 +65,24 @@ var ClientEventReporter = class {
65
65
  _defineProperty(this, "metaContext", void 0);
66
66
  _defineProperty(this, "logger", void 0);
67
67
  _defineProperty(this, "url", void 0);
68
+ _defineProperty(this, "clientSession", void 0);
68
69
  _defineProperty(this, "executeRequests", true);
69
70
  this.sdkKey = options.sdkKey;
70
71
  this.logger = options.logger;
71
72
  this.sdkIdentity = options.sdkIdentity;
72
73
  this.metaContext = options.metaContext;
73
74
  this.url = options.urlFactory("client/telemetry/v1", options.baseUrl);
75
+ this.clientSession = options.clientSession;
74
76
  }
75
77
  async report({ context, discreteEvents, aggregatedEvents, droppedEvents }) {
76
78
  if (!this.executeRequests) return {
77
79
  success: false,
78
80
  fatalError: true
79
81
  };
82
+ const { instanceId } = await this.clientSession;
80
83
  const eventReport = {
81
84
  clientSdkKey: this.sdkKey,
85
+ instanceId,
82
86
  metaContext: {
83
87
  sdkName: this.sdkIdentity.sdkName,
84
88
  sdkVersion: this.sdkIdentity.sdkVersion,
@@ -538,6 +542,41 @@ const findAsyncStorage = (logger) => importAsyncStorage().then((lookup) => {
538
542
  return lookup;
539
543
  });
540
544
  //#endregion
545
+ //#region src/cachingProvider.ts
546
+ const asyncStorageCachingProvider = (asyncStorage) => (clientSdkKeyDigest) => {
547
+ const storageKey = clientSdkKeyDigest.then((digest) => CACHED_VALUES_STORAGE_KEY_PREFIX + digest);
548
+ return {
549
+ read: async () => {
550
+ const lookup = await asyncStorage;
551
+ return lookup.outcome === "installed" ? lookup.storage.getItem(await storageKey) : void 0;
552
+ },
553
+ write: async (value) => {
554
+ const lookup = await asyncStorage;
555
+ if (lookup.outcome === "installed") await lookup.storage.setItem(await storageKey, value);
556
+ },
557
+ remove: async () => {
558
+ const lookup = await asyncStorage;
559
+ if (lookup.outcome === "installed") await lookup.storage.removeItem(await storageKey);
560
+ },
561
+ sizeLimit: () => DEFAULT_CACHING_SIZE_LIMIT_BYTES
562
+ };
563
+ };
564
+ //#endregion
565
+ //#region src/instanceIdStorage.ts
566
+ const asyncStorageInstanceIdStorage = (asyncStorage) => {
567
+ const installedAsyncStorage = async () => {
568
+ const lookup = await asyncStorage;
569
+ switch (lookup.outcome) {
570
+ case "installed": return lookup.storage;
571
+ case "not installed": throw new Error("@react-native-async-storage/async-storage is not installed");
572
+ }
573
+ };
574
+ return {
575
+ getItem: async (key) => (await installedAsyncStorage()).getItem(key),
576
+ setItem: async (key, value) => (await installedAsyncStorage()).setItem(key, value)
577
+ };
578
+ };
579
+ //#endregion
541
580
  //#region src/utf8Encoder.ts
542
581
  /**
543
582
  * Encodes a string to a UTF-8 byte sequence.
@@ -578,42 +617,6 @@ const encodeManually = (str) => {
578
617
  return new Uint8Array(bytes);
579
618
  };
580
619
  //#endregion
581
- //#region src/cachingProvider.ts
582
- const storageKeyFor = (clientSdkKey) => CACHED_VALUES_STORAGE_KEY_PREFIX + bytesToHex(sha256(encodeUtf8(clientSdkKey)));
583
- const asyncStorageCachingProvider = (asyncStorage) => (clientSdkKey) => {
584
- const storageKey = storageKeyFor(clientSdkKey);
585
- return {
586
- read: async () => {
587
- const lookup = await asyncStorage;
588
- return lookup.outcome === "installed" ? lookup.storage.getItem(storageKey) : void 0;
589
- },
590
- write: async (value) => {
591
- const lookup = await asyncStorage;
592
- if (lookup.outcome === "installed") await lookup.storage.setItem(storageKey, value);
593
- },
594
- remove: async () => {
595
- const lookup = await asyncStorage;
596
- if (lookup.outcome === "installed") await lookup.storage.removeItem(storageKey);
597
- },
598
- sizeLimit: () => DEFAULT_CACHING_SIZE_LIMIT_BYTES
599
- };
600
- };
601
- //#endregion
602
- //#region src/instanceIdStorage.ts
603
- const asyncStorageInstanceIdStorage = (asyncStorage) => {
604
- const installedAsyncStorage = async () => {
605
- const lookup = await asyncStorage;
606
- switch (lookup.outcome) {
607
- case "installed": return lookup.storage;
608
- case "not installed": throw new Error("@react-native-async-storage/async-storage is not installed");
609
- }
610
- };
611
- return {
612
- getItem: async (key) => (await installedAsyncStorage()).getItem(key),
613
- setItem: async (key, value) => (await installedAsyncStorage()).setItem(key, value)
614
- };
615
- };
616
- //#endregion
617
620
  //#region src/reactNativeStreamingFetch.ts
618
621
  const abortError = (message) => {
619
622
  const err = new Error(message);
@@ -750,6 +753,9 @@ var ReactNativeTelemetryClient = class {
750
753
  }
751
754
  };
752
755
  //#endregion
756
+ //#region src/sha256HexDigest.ts
757
+ const sha256HexDigest = (text) => Promise.resolve(bytesToHex(sha256(encodeUtf8(text))));
758
+ //#endregion
753
759
  //#region src/url.ts
754
760
  var MinimalUrl = class {
755
761
  constructor(input, base) {
@@ -771,7 +777,7 @@ const createReactNativeClient = (clientSdkKey, clientOptions) => {
771
777
  var _clientOptions$logger;
772
778
  const sdkIdentity = {
773
779
  sdkName: "react-native-sdk",
774
- sdkVersion: "1.9.0"
780
+ sdkVersion: "1.10.0"
775
781
  };
776
782
  const logger = (_clientOptions$logger = clientOptions === null || clientOptions === void 0 ? void 0 : clientOptions.logger) !== null && _clientOptions$logger !== void 0 ? _clientOptions$logger : createConsoleLogger("warn");
777
783
  const metadata = resolveMetadata(clientOptions === null || clientOptions === void 0 ? void 0 : clientOptions.metadata, logger);
@@ -782,7 +788,8 @@ const createReactNativeClient = (clientSdkKey, clientOptions) => {
782
788
  metaContext: metadata,
783
789
  baseUrl: liveTelemetry.baseUrl,
784
790
  logger,
785
- urlFactory
791
+ urlFactory,
792
+ clientSession: liveTelemetry.clientSession
786
793
  }), clientSdkKey, sdkIdentity, {
787
794
  ...clientOptions,
788
795
  logger,
@@ -791,6 +798,7 @@ const createReactNativeClient = (clientSdkKey, clientOptions) => {
791
798
  fetch: reactNativeStreamingFetch,
792
799
  urlFactory,
793
800
  instanceIdStorage: asyncStorageInstanceIdStorage(asyncStorage),
801
+ sha256HexDigest,
794
802
  platformCachingProvider: asyncStorageCachingProvider(asyncStorage)
795
803
  });
796
804
  };
@@ -875,9 +875,12 @@ const parseJson = (jsonString, defaultValue, requestedType, valueId) => {
875
875
  };
876
876
  const isJsonTypeCompatible = (parsed, defaultValue) => {
877
877
  if (parsed === null) return false;
878
- const defaultType = typeof defaultValue;
879
- if (defaultType === "bigint") return typeof parsed === "number";
880
- return typeof parsed === defaultType;
878
+ return jsonShapeOf(parsed) === jsonShapeOf(defaultValue);
879
+ };
880
+ const jsonShapeOf = (value) => {
881
+ if (Array.isArray(value)) return "array";
882
+ const nativeType = typeof value;
883
+ return nativeType === "bigint" ? "number" : nativeType;
881
884
  };
882
885
  //#endregion
883
886
  //#region ../shared/src/logger.ts
@@ -1331,11 +1334,12 @@ const utf8ByteLength = (text) => {
1331
1334
  };
1332
1335
  //#endregion
1333
1336
  //#region ../js-client-core/src/cached-values.ts
1334
- const ENTRY_FORMAT_VERSION = 1;
1337
+ const ENTRY_FORMAT_VERSION = 2;
1338
+ const FIRST_ENTRY_FORMAT_VERSION = 1;
1335
1339
  const DEFAULT_CONTEXT_KIND = "user";
1336
1340
  const CACHED_VALUES_STORAGE_KEY_PREFIX = "configdirector-sdk:cached-values:";
1337
1341
  const DEFAULT_CACHING_SIZE_LIMIT_BYTES = 256 * 1024;
1338
- const planCaching = (connectionPlan, caching, platformProvider) => {
1342
+ const planCaching = (connectionPlan, caching, sha256HexDigest, platformCachingProvider) => {
1339
1343
  var _caching$provider;
1340
1344
  if (connectionPlan.kind === "stay-offline") return {
1341
1345
  kind: "no cache",
@@ -1345,7 +1349,12 @@ const planCaching = (connectionPlan, caching, platformProvider) => {
1345
1349
  kind: "no cache",
1346
1350
  reason: "turned off"
1347
1351
  };
1348
- const provider = (_caching$provider = caching === null || caching === void 0 ? void 0 : caching.provider) !== null && _caching$provider !== void 0 ? _caching$provider : platformProvider === null || platformProvider === void 0 ? void 0 : platformProvider(connectionPlan.clientSdkKey);
1352
+ if (!sha256HexDigest) return {
1353
+ kind: "no cache",
1354
+ reason: "no SHA-256 available"
1355
+ };
1356
+ const clientSdkKeyDigest = sha256HexDigest(connectionPlan.clientSdkKey);
1357
+ const provider = (_caching$provider = caching === null || caching === void 0 ? void 0 : caching.provider) !== null && _caching$provider !== void 0 ? _caching$provider : platformCachingProvider === null || platformCachingProvider === void 0 ? void 0 : platformCachingProvider(clientSdkKeyDigest);
1349
1358
  if (!provider) return {
1350
1359
  kind: "no cache",
1351
1360
  reason: "no provider available"
@@ -1353,7 +1362,7 @@ const planCaching = (connectionPlan, caching, platformProvider) => {
1353
1362
  return {
1354
1363
  kind: "cache",
1355
1364
  provider,
1356
- clientSdkKey: connectionPlan.clientSdkKey
1365
+ clientSdkKeyDigest
1357
1366
  };
1358
1367
  };
1359
1368
  const contextTagFor = (context, instanceId) => {
@@ -1389,15 +1398,30 @@ const isConfigStateMap = (candidate) => {
1389
1398
  if (!candidate || typeof candidate !== "object" || Array.isArray(candidate)) return false;
1390
1399
  return Object.values(candidate).every((config) => config && typeof config === "object" && typeof config.id === "string" && typeof config.key === "string");
1391
1400
  };
1392
- const isEntryFor = (clientSdkKey, candidate) => {
1401
+ const isEntryFor = (clientSdkKeyDigest, candidate) => {
1393
1402
  if (!candidate || typeof candidate !== "object") return false;
1394
1403
  const entry = candidate;
1395
- return entry.formatVersion === ENTRY_FORMAT_VERSION && entry.clientSdkKey === clientSdkKey && isContextTag(entry.contextTag) && typeof entry.environmentId === "string" && typeof entry.projectId === "string" && isConfigStateMap(entry.configs) && typeof entry.receivedAt === "number";
1404
+ return entry.formatVersion === ENTRY_FORMAT_VERSION && entry.clientSdkKeyDigest === clientSdkKeyDigest && isContextTag(entry.contextTag) && typeof entry.environmentId === "string" && typeof entry.projectId === "string" && isConfigStateMap(entry.configs) && typeof entry.receivedAt === "number";
1405
+ };
1406
+ const hasFirstFormatVersion = (candidate) => !!candidate && typeof candidate === "object" && candidate.formatVersion === FIRST_ENTRY_FORMAT_VERSION;
1407
+ const parseEntry = (raw, clientSdkKeyDigest) => {
1408
+ let candidate;
1409
+ try {
1410
+ candidate = JSON.parse(raw);
1411
+ } catch {
1412
+ return { outcome: "unreadable" };
1413
+ }
1414
+ if (isEntryFor(clientSdkKeyDigest, candidate)) return {
1415
+ outcome: "stored",
1416
+ cachedValues: candidate
1417
+ };
1418
+ if (hasFirstFormatVersion(candidate)) return { outcome: "first format" };
1419
+ return { outcome: "unreadable" };
1396
1420
  };
1397
1421
  var CachedValuesStore = class {
1398
- constructor(provider, clientSdkKey, logger) {
1422
+ constructor(provider, clientSdkKeyDigest, logger) {
1399
1423
  this.provider = provider;
1400
- this.clientSdkKey = clientSdkKey;
1424
+ this.clientSdkKeyDigest = clientSdkKeyDigest;
1401
1425
  this.logger = logger;
1402
1426
  _defineProperty(this, "warned", /* @__PURE__ */ new Set());
1403
1427
  _defineProperty(this, "pendingRead", void 0);
@@ -1422,23 +1446,16 @@ var CachedValuesStore = class {
1422
1446
  return { outcome: "failed" };
1423
1447
  }
1424
1448
  if (raw == null || raw === "") return { outcome: "nothing stored" };
1425
- const entry = this.parseEntry(raw);
1426
- if (!entry) {
1427
- this.warnOnce("unreadable", "The values kept on the device could not be read and were removed, starting without them");
1428
- await this.removeEntry();
1429
- return { outcome: "unreadable" };
1430
- }
1431
- return {
1432
- outcome: "stored",
1433
- cachedValues: entry
1434
- };
1435
- }
1436
- parseEntry(raw) {
1437
- try {
1438
- const candidate = JSON.parse(raw);
1439
- return isEntryFor(this.clientSdkKey, candidate) ? candidate : void 0;
1440
- } catch {
1441
- return;
1449
+ const entryParseOutcome = parseEntry(raw, await this.clientSdkKeyDigest);
1450
+ switch (entryParseOutcome.outcome) {
1451
+ case "stored": return entryParseOutcome;
1452
+ case "first format":
1453
+ await this.removeEntry();
1454
+ return entryParseOutcome;
1455
+ case "unreadable":
1456
+ this.warnOnce("unreadable", "The values kept on the device could not be read and were removed, starting without them");
1457
+ await this.removeEntry();
1458
+ return entryParseOutcome;
1442
1459
  }
1443
1460
  }
1444
1461
  async writeQueued() {
@@ -1457,7 +1474,7 @@ var CachedValuesStore = class {
1457
1474
  async writeEntry(cachedValues) {
1458
1475
  const entry = {
1459
1476
  formatVersion: ENTRY_FORMAT_VERSION,
1460
- clientSdkKey: this.clientSdkKey,
1477
+ clientSdkKeyDigest: await this.clientSdkKeyDigest,
1461
1478
  ...cachedValues
1462
1479
  };
1463
1480
  const serialized = JSON.stringify(entry);
@@ -1522,7 +1539,7 @@ var DefaultConfigDirectorClient = class DefaultConfigDirectorClient {
1522
1539
  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;
1523
1540
  const urlFactory = (_internalClientOption = internalClientOptions === null || internalClientOptions === void 0 ? void 0 : internalClientOptions.urlFactory) !== null && _internalClientOption !== void 0 ? _internalClientOption : defaultUrlFactory;
1524
1541
  this.connectionPlan = planConnection(clientSdkKey, clientOptions === null || clientOptions === void 0 ? void 0 : clientOptions.connection, urlFactory);
1525
- this.cachedValuesStore = this.buildCachedValuesStore(planCaching(this.connectionPlan, clientOptions === null || clientOptions === void 0 ? void 0 : clientOptions.caching, internalClientOptions === null || internalClientOptions === void 0 ? void 0 : internalClientOptions.platformCachingProvider));
1542
+ this.cachedValuesStore = this.buildCachedValuesStore(planCaching(this.connectionPlan, clientOptions === null || clientOptions === void 0 ? void 0 : clientOptions.caching, internalClientOptions === null || internalClientOptions === void 0 ? void 0 : internalClientOptions.sha256HexDigest, internalClientOptions === null || internalClientOptions === void 0 ? void 0 : internalClientOptions.platformCachingProvider));
1526
1543
  this.telemetryClient = this.buildTelemetryClient(liveTelemetryClient, clientOptions);
1527
1544
  this.transport = (_internalClientOption2 = internalClientOptions === null || internalClientOptions === void 0 ? void 0 : internalClientOptions.transport) !== null && _internalClientOption2 !== void 0 ? _internalClientOption2 : this.buildTransport(sdkOptions, clientOptions, urlFactory, internalClientOptions);
1528
1545
  this.transport.on("configSetReceived", (configSet) => {
@@ -1566,7 +1583,7 @@ var DefaultConfigDirectorClient = class DefaultConfigDirectorClient {
1566
1583
  this.logger.debug(`[ConfigDirectorClient] No values are kept on the device: ${cachingPlan.reason}`);
1567
1584
  return;
1568
1585
  case "cache": {
1569
- const store = new CachedValuesStore(cachingPlan.provider, cachingPlan.clientSdkKey, this.logger);
1586
+ const store = new CachedValuesStore(cachingPlan.provider, cachingPlan.clientSdkKeyDigest, this.logger);
1570
1587
  store.read();
1571
1588
  return store;
1572
1589
  }
@@ -1582,6 +1599,7 @@ var DefaultConfigDirectorClient = class DefaultConfigDirectorClient {
1582
1599
  const read = await store.read();
1583
1600
  switch (read.outcome) {
1584
1601
  case "nothing stored":
1602
+ case "first format":
1585
1603
  case "unreadable":
1586
1604
  case "failed": return;
1587
1605
  case "stored": break;
@@ -1655,7 +1673,8 @@ var DefaultConfigDirectorClient = class DefaultConfigDirectorClient {
1655
1673
  clientSdkKey: plan.clientSdkKey,
1656
1674
  baseUrl: plan.baseUrl,
1657
1675
  logger: this.logger,
1658
- metadata: clientOptions === null || clientOptions === void 0 ? void 0 : clientOptions.metadata
1676
+ metadata: clientOptions === null || clientOptions === void 0 ? void 0 : clientOptions.metadata,
1677
+ clientSession: this.clientSession
1659
1678
  });
1660
1679
  }
1661
1680
  }
@@ -1778,7 +1797,7 @@ var DefaultConfigDirectorClient = class DefaultConfigDirectorClient {
1778
1797
  var _this$configSet, _this$handlersMap$get;
1779
1798
  const configState = (_this$configSet = this.configSet) === null || _this$configSet === void 0 ? void 0 : _this$configSet.configs[configKey];
1780
1799
  (_this$handlersMap$get = this.handlersMap.get(configKey)) === null || _this$handlersMap$get === void 0 || _this$handlersMap$get.forEach((h) => {
1781
- const value = this.getValueFromConfigState(configKey, configState, h.defaultValue);
1800
+ const value = this.evaluationOf(configKey, configState, h.defaultValue).value;
1782
1801
  h.handler(value);
1783
1802
  });
1784
1803
  }
@@ -1803,12 +1822,15 @@ var DefaultConfigDirectorClient = class DefaultConfigDirectorClient {
1803
1822
  } else handlers.splice(0);
1804
1823
  }
1805
1824
  getValue(configKey, defaultValue) {
1825
+ return this.evaluate(configKey, defaultValue).value;
1826
+ }
1827
+ evaluate(configKey, defaultValue) {
1806
1828
  var _this$configSet2;
1807
1829
  this.validateDefaultValue(defaultValue);
1808
1830
  const configState = (_this$configSet2 = this.configSet) === null || _this$configSet2 === void 0 ? void 0 : _this$configSet2.configs[configKey];
1809
- return this.getValueFromConfigState(configKey, configState, defaultValue);
1831
+ return this.evaluationOf(configKey, configState, defaultValue);
1810
1832
  }
1811
- getValueFromConfigState(configKey, configState, defaultValue) {
1833
+ evaluationOf(configKey, configState, defaultValue) {
1812
1834
  var _this$currentContext2, _parseResult$parsedVa;
1813
1835
  if (!configState) {
1814
1836
  var _this$currentContext;
@@ -1823,14 +1845,15 @@ var DefaultConfigDirectorClient = class DefaultConfigDirectorClient {
1823
1845
  usedDefault: true,
1824
1846
  evaluationReason: reason
1825
1847
  });
1826
- this.dispatchEvaluationEvent({
1848
+ const evaluation = {
1827
1849
  key: configKey,
1828
1850
  value: defaultValue,
1829
1851
  isDefaultValue: true,
1830
1852
  reason,
1831
1853
  context: this.context
1832
- });
1833
- return defaultValue;
1854
+ };
1855
+ this.dispatchEvaluationEvent(evaluation);
1856
+ return evaluation;
1834
1857
  }
1835
1858
  const parseResult = parseConfigValue(configState, defaultValue);
1836
1859
  const reason = this.servedReason(parseResult.reason);
@@ -1845,16 +1868,17 @@ var DefaultConfigDirectorClient = class DefaultConfigDirectorClient {
1845
1868
  usedDefault: parseResult.usedDefault,
1846
1869
  evaluationReason: reason
1847
1870
  });
1848
- this.dispatchEvaluationEvent({
1871
+ const evaluation = {
1849
1872
  key: configKey,
1850
1873
  value: parseResult.parsedValue,
1851
1874
  valueId: (_parseResult$parsedVa = parseResult.parsedValueId) !== null && _parseResult$parsedVa !== void 0 ? _parseResult$parsedVa : void 0,
1852
1875
  isDefaultValue: parseResult.usedDefault,
1853
1876
  reason,
1854
1877
  context: this.context
1855
- });
1878
+ };
1879
+ this.dispatchEvaluationEvent(evaluation);
1856
1880
  this.logger.debug(`[ConfigDirectorClient] Evaluated '${configKey}' to '${parseResult.parsedValue}'`);
1857
- return parseResult.parsedValue;
1881
+ return evaluation;
1858
1882
  }
1859
1883
  dispatchEvaluationEvent(event) {
1860
1884
  setTimeout(() => this.emit("configEvaluated", { evaluation: event }), 0);
@@ -23,13 +23,24 @@ interface ConfigDirectorLogger {
23
23
  interface ConfigDirectorLogMessageDecorator {
24
24
  decorateMessage(message: string): string;
25
25
  }
26
+ /**
27
+ * Why an evaluation returned the value it did. `found-match` returns a value from ConfigDirector,
28
+ * and `cached` a value kept on the device while a client SDK waits for live values. `value-missing`
29
+ * returns the in-code default value because the config has no value. Every other reason returns the
30
+ * in-code default value because of an error: the config does not exist (`config-state-missing`), no
31
+ * values have arrived yet (`client-not-ready`), or the value cannot be read as the type of the in-code
32
+ * default value.
33
+ */
26
34
  type EvaluationReason = "found-match" | "config-state-missing" | "client-not-ready" | "type-mismatch" | "value-missing" | "invalid-number" | "invalid-json" | "invalid-boolean" | "cached";
27
- type ConfigEvaluation = {
28
- key: string;
29
- value: ConfigValueType;
30
- valueId?: string;
31
- isDefaultValue: boolean;
32
- reason: EvaluationReason;
35
+ /**
36
+ * One evaluation of a config: the value it returned, why, and the context it was evaluated for.
37
+ */
38
+ type ConfigEvaluation<T extends ConfigValueType = ConfigValueType> = {
39
+ /** The config key that was evaluated. */key: string; /** The value returned, which is the in-code default value when {@link isDefaultValue} is `true`. */
40
+ value: T; /** The ID ConfigDirector assigned to the value returned, when ConfigDirector sent one with it. */
41
+ valueId?: string; /** Whether the evaluation returned the in-code default value. */
42
+ isDefaultValue: boolean; /** Why the evaluation returned this value. */
43
+ reason: EvaluationReason; /** The context the config was evaluated for, if any. */
33
44
  context?: ConfigDirectorContext;
34
45
  };
35
46
  /**
@@ -350,6 +361,16 @@ interface ConfigDirectorClient extends EventProvider<ClientEvents> {
350
361
  * is called before initialization is done.
351
362
  */
352
363
  getValue<T extends ConfigValueType>(configKey: string, defaultValue: T): T;
364
+ /**
365
+ * Evaluates a config as {@link getValue} does and returns the evaluation: the value, the reason it
366
+ * was served and its value ID. Like every read, it fires `configEvaluated` and is counted in
367
+ * telemetry.
368
+ *
369
+ * @returns The evaluation, whose `value` is what {@link getValue} returns
370
+ * @param configKey The config key to evaluate
371
+ * @param defaultValue The in-code default value, returned when no value from ConfigDirector can be served
372
+ */
373
+ evaluate<T extends ConfigValueType>(configKey: string, defaultValue: T): ConfigEvaluation<T>;
353
374
  /**
354
375
  * Watches for changes to a a config evaluation value. Whenever the config value changes, the
355
376
  * provided callback function will be called with the new value. Changes can happen due to updates
@@ -397,4 +418,4 @@ interface ConfigDirectorClient extends EventProvider<ClientEvents> {
397
418
  dispose(): void;
398
419
  }
399
420
  //#endregion
400
- export { ConfigDirectorClientOptions as a, NativeJsonValue as c, ConfigDirectorContext as d, ConfigDirectorLogMessageDecorator as f, ConnectionMode as g, ConfigValueType as h, ConfigDirectorClient as i, NativeValue as l, ConfigDirectorLoggingLevel as m, CachingProvider as n, OfflineValue as o, ConfigDirectorLogger as p, ClientHooks as r, OfflineValues as s, CachingOptions as t, NativeValues as u };
421
+ export { ConnectionMode as _, ConfigDirectorClientOptions as a, NativeJsonValue as c, ConfigDirectorContext as d, ConfigDirectorLogMessageDecorator as f, ConfigValueType as g, ConfigEvaluation as h, ConfigDirectorClient as i, NativeValue as l, ConfigDirectorLoggingLevel as m, CachingProvider as n, OfflineValue as o, ConfigDirectorLogger as p, ClientHooks as r, OfflineValues as s, CachingOptions as t, NativeValues as u, EvaluationReason as v };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@configdirector/react-native-sdk",
3
- "version": "1.9.0",
3
+ "version": "1.10.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",