@configdirector/react-native-sdk 1.8.0 → 1.9.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 +6 -4
- package/dist/configdirector-react-native-testing.d.mts +1 -1
- package/dist/configdirector-react-native-testing.mjs +2 -2
- package/dist/configdirector-react-native.d.mts +8 -2
- package/dist/configdirector-react-native.mjs +50 -23
- package/dist/{installedTestClient-CKjaEilj.mjs → installedTestClient-BluKT8Qj.mjs} +291 -38
- package/dist/{types-DoORnGTD.d.mts → types-BBQmjjf5.d.mts} +54 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -10,9 +10,11 @@ React Native SDK for [ConfigDirector](https://www.configdirector.com), remote co
|
|
|
10
10
|
npm install --save @configdirector/react-native-sdk
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
Install `@react-native-async-storage/async-storage` as well, so the SDK
|
|
14
|
-
|
|
15
|
-
|
|
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.
|
|
16
18
|
|
|
17
19
|
```bash
|
|
18
20
|
npm install --save @react-native-async-storage/async-storage
|
|
@@ -86,7 +88,7 @@ transform, so a Jest setup must let `transformIgnorePatterns` transform both pac
|
|
|
86
88
|
`jest-expo` requires `@react-native/jest-preset` as a peer dependency, so install it in either case.
|
|
87
89
|
The test client never touches AsyncStorage. A test that builds a real client instead, through
|
|
88
90
|
`createClient` or a provider given an `sdkKey`, logs the warning that AsyncStorage is missing and
|
|
89
|
-
|
|
91
|
+
connects as a new install under either preset; add `@babel/plugin-transform-dynamic-import` to the
|
|
90
92
|
Babel test environment for that client to use the AsyncStorage Jest mock instead.
|
|
91
93
|
|
|
92
94
|
## Documentation
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { c as
|
|
1
|
+
import { c as NativeJsonValue, d as ConfigDirectorContext, i as ConfigDirectorClient, l as NativeValue, p as ConfigDirectorLogger, u as NativeValues } from "./types-BBQmjjf5.mjs";
|
|
2
2
|
|
|
3
3
|
//#region ../shared/src/errors.d.ts
|
|
4
4
|
declare class ConfigDirectorConnectionError extends Error {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
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";
|
|
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.
|
|
298
|
+
sdkVersion: "1.9.0"
|
|
299
299
|
}, options);
|
|
300
300
|
};
|
|
301
301
|
//#endregion
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as
|
|
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";
|
|
2
2
|
import { Component, PropsWithChildren } from "react";
|
|
3
3
|
import * as _$react_jsx_runtime0 from "react/jsx-runtime";
|
|
4
4
|
|
|
@@ -74,6 +74,12 @@ type ConfigDirectorProviderOptions = {
|
|
|
74
74
|
context?: ConfigDirectorContext;
|
|
75
75
|
logger?: ConfigDirectorLogger;
|
|
76
76
|
hooks?: ClientHooks;
|
|
77
|
+
/**
|
|
78
|
+
* Options for the values the client keeps on the device between launches. The last values received
|
|
79
|
+
* are kept by default and served, with the evaluation reason `cached`, on the next launch of the same
|
|
80
|
+
* context until live values arrive.
|
|
81
|
+
*/
|
|
82
|
+
caching?: CachingOptions;
|
|
77
83
|
netInfoSubscribe?: NetInfoSubscribe;
|
|
78
84
|
};
|
|
79
85
|
/**
|
|
@@ -145,4 +151,4 @@ declare class ConfigDirectorReactContextError extends Error {
|
|
|
145
151
|
//#region src/logger.d.ts
|
|
146
152
|
declare const createConsoleLogger: (level?: ConfigDirectorLoggingLevel) => DefaultConsoleLogger;
|
|
147
153
|
//#endregion
|
|
148
|
-
export { 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 ConnectionMode, type NetInfoSubscribe, type OfflineValue, type OfflineValues, createClient, createConsoleLogger, useClient, useConfigValue, useContext };
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import {
|
|
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";
|
|
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";
|
|
5
|
+
import { bytesToHex } from "@noble/hashes/utils.js";
|
|
5
6
|
import { jsx } from "react/jsx-runtime";
|
|
6
7
|
//#region ../shared/src/telemetry/utils.ts
|
|
7
8
|
const CONFIG_VALUE_MAX_LENGTH = 500;
|
|
@@ -520,8 +521,8 @@ const resolveMetadata = (provided, logger) => {
|
|
|
520
521
|
return metadata;
|
|
521
522
|
};
|
|
522
523
|
//#endregion
|
|
523
|
-
//#region src/
|
|
524
|
-
const
|
|
524
|
+
//#region src/asyncStorage.ts
|
|
525
|
+
const importAsyncStorage = async () => {
|
|
525
526
|
try {
|
|
526
527
|
const { default: storage } = await import("@react-native-async-storage/async-storage");
|
|
527
528
|
return {
|
|
@@ -532,23 +533,10 @@ const lookUpAsyncStorage = async () => {
|
|
|
532
533
|
return { outcome: "not installed" };
|
|
533
534
|
}
|
|
534
535
|
};
|
|
535
|
-
const
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
});
|
|
540
|
-
const installedAsyncStorage = async () => {
|
|
541
|
-
const lookup = await asyncStorageLookup;
|
|
542
|
-
switch (lookup.outcome) {
|
|
543
|
-
case "installed": return lookup.storage;
|
|
544
|
-
case "not installed": throw new Error("@react-native-async-storage/async-storage is not installed");
|
|
545
|
-
}
|
|
546
|
-
};
|
|
547
|
-
return {
|
|
548
|
-
getItem: async (key) => (await installedAsyncStorage()).getItem(key),
|
|
549
|
-
setItem: async (key, value) => (await installedAsyncStorage()).setItem(key, value)
|
|
550
|
-
};
|
|
551
|
-
};
|
|
536
|
+
const findAsyncStorage = (logger) => importAsyncStorage().then((lookup) => {
|
|
537
|
+
if (lookup.outcome === "not installed") logger.warn("The ConfigDirector SDK could not find @react-native-async-storage/async-storage, so every app start looks like a new install to ConfigDirector, a context without an id gets a new identifier each time, and no values are kept on the device between app starts. Install it for the SDK to recognize the install across app starts and to serve the last values when the app starts without a network.");
|
|
538
|
+
return lookup;
|
|
539
|
+
});
|
|
552
540
|
//#endregion
|
|
553
541
|
//#region src/utf8Encoder.ts
|
|
554
542
|
/**
|
|
@@ -590,6 +578,42 @@ const encodeManually = (str) => {
|
|
|
590
578
|
return new Uint8Array(bytes);
|
|
591
579
|
};
|
|
592
580
|
//#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
|
|
593
617
|
//#region src/reactNativeStreamingFetch.ts
|
|
594
618
|
const abortError = (message) => {
|
|
595
619
|
const err = new Error(message);
|
|
@@ -747,10 +771,11 @@ const createReactNativeClient = (clientSdkKey, clientOptions) => {
|
|
|
747
771
|
var _clientOptions$logger;
|
|
748
772
|
const sdkIdentity = {
|
|
749
773
|
sdkName: "react-native-sdk",
|
|
750
|
-
sdkVersion: "1.
|
|
774
|
+
sdkVersion: "1.9.0"
|
|
751
775
|
};
|
|
752
776
|
const logger = (_clientOptions$logger = clientOptions === null || clientOptions === void 0 ? void 0 : clientOptions.logger) !== null && _clientOptions$logger !== void 0 ? _clientOptions$logger : createConsoleLogger("warn");
|
|
753
777
|
const metadata = resolveMetadata(clientOptions === null || clientOptions === void 0 ? void 0 : clientOptions.metadata, logger);
|
|
778
|
+
const asyncStorage = findAsyncStorage(logger);
|
|
754
779
|
return new DefaultConfigDirectorClient((liveTelemetry) => new ReactNativeTelemetryClient({
|
|
755
780
|
sdkKey: liveTelemetry.clientSdkKey,
|
|
756
781
|
sdkIdentity,
|
|
@@ -765,7 +790,8 @@ const createReactNativeClient = (clientSdkKey, clientOptions) => {
|
|
|
765
790
|
}, {
|
|
766
791
|
fetch: reactNativeStreamingFetch,
|
|
767
792
|
urlFactory,
|
|
768
|
-
instanceIdStorage: asyncStorageInstanceIdStorage(
|
|
793
|
+
instanceIdStorage: asyncStorageInstanceIdStorage(asyncStorage),
|
|
794
|
+
platformCachingProvider: asyncStorageCachingProvider(asyncStorage)
|
|
769
795
|
});
|
|
770
796
|
};
|
|
771
797
|
//#endregion
|
|
@@ -785,7 +811,8 @@ const buildClient = (options) => {
|
|
|
785
811
|
appVersion: options.appVersion
|
|
786
812
|
},
|
|
787
813
|
logger: (_options$logger = options.logger) !== null && _options$logger !== void 0 ? _options$logger : createConsoleLogger("warn"),
|
|
788
|
-
hooks: options.hooks
|
|
814
|
+
hooks: options.hooks,
|
|
815
|
+
caching: options.caching
|
|
789
816
|
});
|
|
790
817
|
};
|
|
791
818
|
const hasGivenClient = (props) => "client" in props && props.client !== void 0;
|
|
@@ -982,10 +982,34 @@ const generateInstanceId = () => {
|
|
|
982
982
|
});
|
|
983
983
|
};
|
|
984
984
|
//#endregion
|
|
985
|
+
//#region ../js-client-core/src/browser-globals.ts
|
|
986
|
+
const read = (readValue) => {
|
|
987
|
+
try {
|
|
988
|
+
return readValue(globalThis);
|
|
989
|
+
} catch {
|
|
990
|
+
return;
|
|
991
|
+
}
|
|
992
|
+
};
|
|
993
|
+
const readUserAgent = () => read((globals) => {
|
|
994
|
+
var _globals$navigator;
|
|
995
|
+
return (_globals$navigator = globals.navigator) === null || _globals$navigator === void 0 ? void 0 : _globals$navigator.userAgent;
|
|
996
|
+
});
|
|
997
|
+
const readHost = () => read((globals) => {
|
|
998
|
+
var _globals$location;
|
|
999
|
+
return (_globals$location = globals.location) === null || _globals$location === void 0 ? void 0 : _globals$location.host;
|
|
1000
|
+
});
|
|
1001
|
+
const localStorageIfAvailable = () => {
|
|
1002
|
+
try {
|
|
1003
|
+
var _localStorage;
|
|
1004
|
+
return (_localStorage = globalThis.localStorage) !== null && _localStorage !== void 0 ? _localStorage : void 0;
|
|
1005
|
+
} catch {
|
|
1006
|
+
return;
|
|
1007
|
+
}
|
|
1008
|
+
};
|
|
1009
|
+
//#endregion
|
|
985
1010
|
//#region ../js-client-core/src/client-session.ts
|
|
986
1011
|
const STORAGE_KEY = "configdirector-sdk:instance-id";
|
|
987
1012
|
const RECORD_VERSION = 1;
|
|
988
|
-
const MAX_AGE_MS = 360 * 60 * 1e3;
|
|
989
1013
|
const STORAGE_DEADLINE_MS = 1e3;
|
|
990
1014
|
const CHECKSUM_SALT = "configdirector.instanceId.v1";
|
|
991
1015
|
const UUID_V4_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
|
|
@@ -1002,14 +1026,6 @@ const checksumFor = (id, createdAt) => {
|
|
|
1002
1026
|
const low = fnv1a(payload, 2166136261);
|
|
1003
1027
|
return fnv1a(`${low}|${payload}`, 16777619).toString(16).padStart(8, "0") + low.toString(16).padStart(8, "0");
|
|
1004
1028
|
};
|
|
1005
|
-
const localStorageIfAvailable = () => {
|
|
1006
|
-
try {
|
|
1007
|
-
var _localStorage;
|
|
1008
|
-
return (_localStorage = globalThis.localStorage) !== null && _localStorage !== void 0 ? _localStorage : void 0;
|
|
1009
|
-
} catch {
|
|
1010
|
-
return;
|
|
1011
|
-
}
|
|
1012
|
-
};
|
|
1013
1029
|
const isUsableRecord = (record) => {
|
|
1014
1030
|
if (!record || typeof record !== "object") return false;
|
|
1015
1031
|
const { v, id, createdAt, checksum } = record;
|
|
@@ -1038,11 +1054,6 @@ const readStoredInstanceId = async (storage, logger) => {
|
|
|
1038
1054
|
logger.debug("[ConfigDirectorClient] The stored instance id failed validation, generating a new one");
|
|
1039
1055
|
return { outcome: "nothing usable" };
|
|
1040
1056
|
}
|
|
1041
|
-
const age = Date.now() - record.createdAt;
|
|
1042
|
-
if (age < 0 || age >= MAX_AGE_MS) {
|
|
1043
|
-
logger.debug("[ConfigDirectorClient] The stored instance id has expired, generating a new one");
|
|
1044
|
-
return { outcome: "nothing usable" };
|
|
1045
|
-
}
|
|
1046
1057
|
return {
|
|
1047
1058
|
outcome: "stored",
|
|
1048
1059
|
instanceId: record.id
|
|
@@ -1262,23 +1273,6 @@ var PollingTransport = class extends AbstractPollingTransport {
|
|
|
1262
1273
|
}
|
|
1263
1274
|
};
|
|
1264
1275
|
//#endregion
|
|
1265
|
-
//#region ../js-client-core/src/browser-globals.ts
|
|
1266
|
-
const read = (readValue) => {
|
|
1267
|
-
try {
|
|
1268
|
-
return readValue(globalThis);
|
|
1269
|
-
} catch {
|
|
1270
|
-
return;
|
|
1271
|
-
}
|
|
1272
|
-
};
|
|
1273
|
-
const readUserAgent = () => read((globals) => {
|
|
1274
|
-
var _globals$navigator;
|
|
1275
|
-
return (_globals$navigator = globals.navigator) === null || _globals$navigator === void 0 ? void 0 : _globals$navigator.userAgent;
|
|
1276
|
-
});
|
|
1277
|
-
const readHost = () => read((globals) => {
|
|
1278
|
-
var _globals$location;
|
|
1279
|
-
return (_globals$location = globals.location) === null || _globals$location === void 0 ? void 0 : _globals$location.host;
|
|
1280
|
-
});
|
|
1281
|
-
//#endregion
|
|
1282
1276
|
//#region ../shared/src/context-kind.ts
|
|
1283
1277
|
const CONTEXT_KIND_MAXIMUM_LENGTH = 64;
|
|
1284
1278
|
const KIND_SAMPLE_MAXIMUM_CHARACTERS = 128;
|
|
@@ -1319,15 +1313,190 @@ const admitContext = (context) => {
|
|
|
1319
1313
|
};
|
|
1320
1314
|
};
|
|
1321
1315
|
//#endregion
|
|
1316
|
+
//#region ../shared/src/utf8-byte-length.ts
|
|
1317
|
+
const isHighSurrogate = (codeUnit) => codeUnit >= 55296 && codeUnit <= 56319;
|
|
1318
|
+
const isLowSurrogate = (codeUnit) => codeUnit >= 56320 && codeUnit <= 57343;
|
|
1319
|
+
const utf8ByteLength = (text) => {
|
|
1320
|
+
let bytes = 0;
|
|
1321
|
+
for (let i = 0; i < text.length; i++) {
|
|
1322
|
+
const codeUnit = text.charCodeAt(i);
|
|
1323
|
+
if (codeUnit <= 127) bytes += 1;
|
|
1324
|
+
else if (codeUnit <= 2047) bytes += 2;
|
|
1325
|
+
else if (isHighSurrogate(codeUnit) && i + 1 < text.length && isLowSurrogate(text.charCodeAt(i + 1))) {
|
|
1326
|
+
bytes += 4;
|
|
1327
|
+
i++;
|
|
1328
|
+
} else bytes += 3;
|
|
1329
|
+
}
|
|
1330
|
+
return bytes;
|
|
1331
|
+
};
|
|
1332
|
+
//#endregion
|
|
1333
|
+
//#region ../js-client-core/src/cached-values.ts
|
|
1334
|
+
const ENTRY_FORMAT_VERSION = 1;
|
|
1335
|
+
const DEFAULT_CONTEXT_KIND = "user";
|
|
1336
|
+
const CACHED_VALUES_STORAGE_KEY_PREFIX = "configdirector-sdk:cached-values:";
|
|
1337
|
+
const DEFAULT_CACHING_SIZE_LIMIT_BYTES = 256 * 1024;
|
|
1338
|
+
const planCaching = (connectionPlan, caching, platformProvider) => {
|
|
1339
|
+
var _caching$provider;
|
|
1340
|
+
if (connectionPlan.kind === "stay-offline") return {
|
|
1341
|
+
kind: "no cache",
|
|
1342
|
+
reason: "offline mode"
|
|
1343
|
+
};
|
|
1344
|
+
if ((caching === null || caching === void 0 ? void 0 : caching.enabled) === false) return {
|
|
1345
|
+
kind: "no cache",
|
|
1346
|
+
reason: "turned off"
|
|
1347
|
+
};
|
|
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);
|
|
1349
|
+
if (!provider) return {
|
|
1350
|
+
kind: "no cache",
|
|
1351
|
+
reason: "no provider available"
|
|
1352
|
+
};
|
|
1353
|
+
return {
|
|
1354
|
+
kind: "cache",
|
|
1355
|
+
provider,
|
|
1356
|
+
clientSdkKey: connectionPlan.clientSdkKey
|
|
1357
|
+
};
|
|
1358
|
+
};
|
|
1359
|
+
const contextTagFor = (context, instanceId) => {
|
|
1360
|
+
var _context$kind;
|
|
1361
|
+
const contextKind = (_context$kind = context === null || context === void 0 ? void 0 : context.kind) !== null && _context$kind !== void 0 ? _context$kind : DEFAULT_CONTEXT_KIND;
|
|
1362
|
+
if ((context === null || context === void 0 ? void 0 : context.id) != null) return {
|
|
1363
|
+
contextKind,
|
|
1364
|
+
contextId: context.id
|
|
1365
|
+
};
|
|
1366
|
+
return {
|
|
1367
|
+
contextKind,
|
|
1368
|
+
anonymousInstanceId: instanceId
|
|
1369
|
+
};
|
|
1370
|
+
};
|
|
1371
|
+
const sameContextTag = (stored, launch) => {
|
|
1372
|
+
if (stored.contextKind !== launch.contextKind) return false;
|
|
1373
|
+
if ("contextId" in stored && "contextId" in launch) return stored.contextId === launch.contextId;
|
|
1374
|
+
if ("anonymousInstanceId" in stored && "anonymousInstanceId" in launch) return stored.anonymousInstanceId === launch.anonymousInstanceId;
|
|
1375
|
+
return false;
|
|
1376
|
+
};
|
|
1377
|
+
const decideCachedValuesAdoption = (cachedValues, launchContextTag, hasReceivedLiveValues) => {
|
|
1378
|
+
if (hasReceivedLiveValues) return "live values already arrived";
|
|
1379
|
+
if (!sameContextTag(cachedValues.contextTag, launchContextTag)) return "context mismatch";
|
|
1380
|
+
return "adopt";
|
|
1381
|
+
};
|
|
1382
|
+
const isContextTag = (candidate) => {
|
|
1383
|
+
if (!candidate || typeof candidate !== "object") return false;
|
|
1384
|
+
const { contextKind, contextId, anonymousInstanceId } = candidate;
|
|
1385
|
+
if (typeof contextKind !== "string") return false;
|
|
1386
|
+
return typeof contextId === "string" || typeof anonymousInstanceId === "string";
|
|
1387
|
+
};
|
|
1388
|
+
const isConfigStateMap = (candidate) => {
|
|
1389
|
+
if (!candidate || typeof candidate !== "object" || Array.isArray(candidate)) return false;
|
|
1390
|
+
return Object.values(candidate).every((config) => config && typeof config === "object" && typeof config.id === "string" && typeof config.key === "string");
|
|
1391
|
+
};
|
|
1392
|
+
const isEntryFor = (clientSdkKey, candidate) => {
|
|
1393
|
+
if (!candidate || typeof candidate !== "object") return false;
|
|
1394
|
+
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";
|
|
1396
|
+
};
|
|
1397
|
+
var CachedValuesStore = class {
|
|
1398
|
+
constructor(provider, clientSdkKey, logger) {
|
|
1399
|
+
this.provider = provider;
|
|
1400
|
+
this.clientSdkKey = clientSdkKey;
|
|
1401
|
+
this.logger = logger;
|
|
1402
|
+
_defineProperty(this, "warned", /* @__PURE__ */ new Set());
|
|
1403
|
+
_defineProperty(this, "pendingRead", void 0);
|
|
1404
|
+
_defineProperty(this, "queuedCachedValues", void 0);
|
|
1405
|
+
_defineProperty(this, "writing", false);
|
|
1406
|
+
}
|
|
1407
|
+
read() {
|
|
1408
|
+
var _this$pendingRead;
|
|
1409
|
+
(_this$pendingRead = this.pendingRead) !== null && _this$pendingRead !== void 0 || (this.pendingRead = this.readEntry());
|
|
1410
|
+
return this.pendingRead;
|
|
1411
|
+
}
|
|
1412
|
+
replace(cachedValues) {
|
|
1413
|
+
this.queuedCachedValues = cachedValues;
|
|
1414
|
+
this.writeQueued();
|
|
1415
|
+
}
|
|
1416
|
+
async readEntry() {
|
|
1417
|
+
let raw;
|
|
1418
|
+
try {
|
|
1419
|
+
raw = await this.provider.read();
|
|
1420
|
+
} catch (error) {
|
|
1421
|
+
this.warnOnce("read failed", "Could not read the values kept on the device, starting without them:", error);
|
|
1422
|
+
return { outcome: "failed" };
|
|
1423
|
+
}
|
|
1424
|
+
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;
|
|
1442
|
+
}
|
|
1443
|
+
}
|
|
1444
|
+
async writeQueued() {
|
|
1445
|
+
if (this.writing) return;
|
|
1446
|
+
this.writing = true;
|
|
1447
|
+
try {
|
|
1448
|
+
while (this.queuedCachedValues) {
|
|
1449
|
+
const cachedValues = this.queuedCachedValues;
|
|
1450
|
+
this.queuedCachedValues = void 0;
|
|
1451
|
+
await this.writeEntry(cachedValues);
|
|
1452
|
+
}
|
|
1453
|
+
} finally {
|
|
1454
|
+
this.writing = false;
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1457
|
+
async writeEntry(cachedValues) {
|
|
1458
|
+
const entry = {
|
|
1459
|
+
formatVersion: ENTRY_FORMAT_VERSION,
|
|
1460
|
+
clientSdkKey: this.clientSdkKey,
|
|
1461
|
+
...cachedValues
|
|
1462
|
+
};
|
|
1463
|
+
const serialized = JSON.stringify(entry);
|
|
1464
|
+
const size = utf8ByteLength(serialized);
|
|
1465
|
+
const limit = this.provider.sizeLimit();
|
|
1466
|
+
if (size > limit) {
|
|
1467
|
+
this.warnOnce("over the size limit", `The values received (${size} bytes) exceed the caching provider's limit of ${limit} bytes, so no values are kept on the device`);
|
|
1468
|
+
await this.removeEntry();
|
|
1469
|
+
return;
|
|
1470
|
+
}
|
|
1471
|
+
try {
|
|
1472
|
+
await this.provider.write(serialized);
|
|
1473
|
+
} catch (error) {
|
|
1474
|
+
this.warnOnce("write failed", "Could not keep the values on the device:", error);
|
|
1475
|
+
}
|
|
1476
|
+
}
|
|
1477
|
+
async removeEntry() {
|
|
1478
|
+
try {
|
|
1479
|
+
await this.provider.remove();
|
|
1480
|
+
} catch (error) {
|
|
1481
|
+
this.warnOnce("remove failed", "Could not remove the values kept on the device:", error);
|
|
1482
|
+
}
|
|
1483
|
+
}
|
|
1484
|
+
warnOnce(warning, message, ...details) {
|
|
1485
|
+
if (this.warned.has(warning)) return;
|
|
1486
|
+
this.warned.add(warning);
|
|
1487
|
+
this.logger.warn(`[ConfigDirectorClient] ${message}`, ...details);
|
|
1488
|
+
}
|
|
1489
|
+
};
|
|
1490
|
+
//#endregion
|
|
1322
1491
|
//#region ../js-client-core/src/DefaultConfigDirectorClient.ts
|
|
1323
1492
|
const MAX_EXPONENTIAL_DELAY = 9;
|
|
1324
1493
|
var DefaultConfigDirectorClient = class DefaultConfigDirectorClient {
|
|
1325
|
-
static from(liveTelemetryClient, clientSdkKeyOrOptions, sdkOptions, clientOptions) {
|
|
1326
|
-
if (typeof clientSdkKeyOrOptions === "object") return new DefaultConfigDirectorClient(liveTelemetryClient, void 0, sdkOptions, clientSdkKeyOrOptions);
|
|
1327
|
-
return new DefaultConfigDirectorClient(liveTelemetryClient, clientSdkKeyOrOptions, sdkOptions, clientOptions);
|
|
1494
|
+
static from(liveTelemetryClient, clientSdkKeyOrOptions, sdkOptions, clientOptions, internalClientOptions) {
|
|
1495
|
+
if (typeof clientSdkKeyOrOptions === "object") return new DefaultConfigDirectorClient(liveTelemetryClient, void 0, sdkOptions, clientSdkKeyOrOptions, internalClientOptions);
|
|
1496
|
+
return new DefaultConfigDirectorClient(liveTelemetryClient, clientSdkKeyOrOptions, sdkOptions, clientOptions, internalClientOptions);
|
|
1328
1497
|
}
|
|
1329
1498
|
constructor(liveTelemetryClient, clientSdkKey, sdkOptions, clientOptions, internalClientOptions) {
|
|
1330
|
-
var _clientOptions$logger, _clientOptions$connec, _clientOptions$connec2, _internalClientOption, _internalClientOption2, _clientOptions$hooks, _clientOptions$hooks2, _clientOptions$hooks3, _clientOptions$hooks4, _clientOptions$hooks5;
|
|
1499
|
+
var _clientOptions$logger, _clientOptions$connec, _clientOptions$connec2, _internalClientOption, _internalClientOption2, _clientOptions$hooks, _clientOptions$hooks2, _clientOptions$hooks3, _clientOptions$hooks4, _clientOptions$hooks5, _clientOptions$hooks6;
|
|
1331
1500
|
_defineProperty(this, "logger", void 0);
|
|
1332
1501
|
_defineProperty(this, "telemetryClient", void 0);
|
|
1333
1502
|
_defineProperty(this, "configSet", void 0);
|
|
@@ -1342,18 +1511,24 @@ var DefaultConfigDirectorClient = class DefaultConfigDirectorClient {
|
|
|
1342
1511
|
_defineProperty(this, "readyPromise", void 0);
|
|
1343
1512
|
_defineProperty(this, "readyResolve", void 0);
|
|
1344
1513
|
_defineProperty(this, "currentContext", void 0);
|
|
1514
|
+
_defineProperty(this, "submittedContext", void 0);
|
|
1345
1515
|
_defineProperty(this, "connectionPlan", void 0);
|
|
1346
1516
|
_defineProperty(this, "clientSession", void 0);
|
|
1517
|
+
_defineProperty(this, "cachedValuesStore", void 0);
|
|
1518
|
+
_defineProperty(this, "servingCachedValues", false);
|
|
1519
|
+
_defineProperty(this, "cachedValuesAdoptionStarted", false);
|
|
1347
1520
|
this.logger = (_clientOptions$logger = clientOptions === null || clientOptions === void 0 ? void 0 : clientOptions.logger) !== null && _clientOptions$logger !== void 0 ? _clientOptions$logger : createDefaultLogger();
|
|
1348
1521
|
this.clientSession = resolveClientSession(this.logger, internalClientOptions === null || internalClientOptions === void 0 ? void 0 : internalClientOptions.instanceIdStorage);
|
|
1349
1522
|
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;
|
|
1350
1523
|
const urlFactory = (_internalClientOption = internalClientOptions === null || internalClientOptions === void 0 ? void 0 : internalClientOptions.urlFactory) !== null && _internalClientOption !== void 0 ? _internalClientOption : defaultUrlFactory;
|
|
1351
1524
|
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));
|
|
1352
1526
|
this.telemetryClient = this.buildTelemetryClient(liveTelemetryClient, clientOptions);
|
|
1353
1527
|
this.transport = (_internalClientOption2 = internalClientOptions === null || internalClientOptions === void 0 ? void 0 : internalClientOptions.transport) !== null && _internalClientOption2 !== void 0 ? _internalClientOption2 : this.buildTransport(sdkOptions, clientOptions, urlFactory, internalClientOptions);
|
|
1354
1528
|
this.transport.on("configSetReceived", (configSet) => {
|
|
1355
1529
|
var _this$readyResolve;
|
|
1356
1530
|
this.hasReceivedConfigSet = true;
|
|
1531
|
+
this.servingCachedValues = false;
|
|
1357
1532
|
(_this$readyResolve = this.readyResolve) === null || _this$readyResolve === void 0 || _this$readyResolve.call(this);
|
|
1358
1533
|
const removedKeys = this.findKeysRemovedBy(configSet);
|
|
1359
1534
|
if (!this.configSet || configSet.kind == "full") this.configSet = configSet;
|
|
@@ -1361,6 +1536,7 @@ var DefaultConfigDirectorClient = class DefaultConfigDirectorClient {
|
|
|
1361
1536
|
...this.configSet.configs,
|
|
1362
1537
|
...configSet.configs
|
|
1363
1538
|
};
|
|
1539
|
+
this.keepValuesOnTheDevice(this.configSet);
|
|
1364
1540
|
const keys = Object.keys(configSet.configs);
|
|
1365
1541
|
this.emit("configsUpdated", {
|
|
1366
1542
|
keys,
|
|
@@ -1382,6 +1558,80 @@ var DefaultConfigDirectorClient = class DefaultConfigDirectorClient {
|
|
|
1382
1558
|
this.registerHandler("configsUpdated", clientOptions === null || clientOptions === void 0 || (_clientOptions$hooks3 = clientOptions.hooks) === null || _clientOptions$hooks3 === void 0 ? void 0 : _clientOptions$hooks3.configsUpdated);
|
|
1383
1559
|
this.registerHandler("contextUpdated", clientOptions === null || clientOptions === void 0 || (_clientOptions$hooks4 = clientOptions.hooks) === null || _clientOptions$hooks4 === void 0 ? void 0 : _clientOptions$hooks4.contextUpdated);
|
|
1384
1560
|
this.registerHandler("connectionError", clientOptions === null || clientOptions === void 0 || (_clientOptions$hooks5 = clientOptions.hooks) === null || _clientOptions$hooks5 === void 0 ? void 0 : _clientOptions$hooks5.connectionError);
|
|
1561
|
+
this.registerHandler("cachedValuesLoaded", clientOptions === null || clientOptions === void 0 || (_clientOptions$hooks6 = clientOptions.hooks) === null || _clientOptions$hooks6 === void 0 ? void 0 : _clientOptions$hooks6.cachedValuesLoaded);
|
|
1562
|
+
}
|
|
1563
|
+
buildCachedValuesStore(cachingPlan) {
|
|
1564
|
+
switch (cachingPlan.kind) {
|
|
1565
|
+
case "no cache":
|
|
1566
|
+
this.logger.debug(`[ConfigDirectorClient] No values are kept on the device: ${cachingPlan.reason}`);
|
|
1567
|
+
return;
|
|
1568
|
+
case "cache": {
|
|
1569
|
+
const store = new CachedValuesStore(cachingPlan.provider, cachingPlan.clientSdkKey, this.logger);
|
|
1570
|
+
store.read();
|
|
1571
|
+
return store;
|
|
1572
|
+
}
|
|
1573
|
+
}
|
|
1574
|
+
}
|
|
1575
|
+
startCachedValuesAdoption() {
|
|
1576
|
+
const store = this.cachedValuesStore;
|
|
1577
|
+
if (!store || this.cachedValuesAdoptionStarted) return;
|
|
1578
|
+
this.cachedValuesAdoptionStarted = true;
|
|
1579
|
+
this.adoptCachedValues(store);
|
|
1580
|
+
}
|
|
1581
|
+
async adoptCachedValues(store) {
|
|
1582
|
+
const read = await store.read();
|
|
1583
|
+
switch (read.outcome) {
|
|
1584
|
+
case "nothing stored":
|
|
1585
|
+
case "unreadable":
|
|
1586
|
+
case "failed": return;
|
|
1587
|
+
case "stored": break;
|
|
1588
|
+
}
|
|
1589
|
+
const { instanceId } = await this.clientSession;
|
|
1590
|
+
const launchContextTag = contextTagFor(this.submittedContext, instanceId);
|
|
1591
|
+
const adoption = decideCachedValuesAdoption(read.cachedValues, launchContextTag, this.hasReceivedConfigSet);
|
|
1592
|
+
switch (adoption) {
|
|
1593
|
+
case "adopt":
|
|
1594
|
+
this.serveCachedValues(read.cachedValues);
|
|
1595
|
+
return;
|
|
1596
|
+
case "live values already arrived":
|
|
1597
|
+
case "context mismatch":
|
|
1598
|
+
this.logger.debug(`[ConfigDirectorClient] The values kept on the device were not adopted: ${adoption}`);
|
|
1599
|
+
return;
|
|
1600
|
+
}
|
|
1601
|
+
}
|
|
1602
|
+
serveCachedValues(cachedValues) {
|
|
1603
|
+
const { environmentId, projectId, configs } = cachedValues;
|
|
1604
|
+
this.configSet = {
|
|
1605
|
+
environmentId,
|
|
1606
|
+
projectId,
|
|
1607
|
+
configs,
|
|
1608
|
+
kind: "full"
|
|
1609
|
+
};
|
|
1610
|
+
this.servingCachedValues = true;
|
|
1611
|
+
const keys = Object.keys(configs);
|
|
1612
|
+
this.emit("cachedValuesLoaded", {});
|
|
1613
|
+
this.emit("configsUpdated", {
|
|
1614
|
+
keys,
|
|
1615
|
+
removedKeys: []
|
|
1616
|
+
});
|
|
1617
|
+
this.updateWatchers(keys);
|
|
1618
|
+
this.logger.debug("[ConfigDirectorClient] Serving the values kept on the device until live values arrive:", { keys });
|
|
1619
|
+
}
|
|
1620
|
+
keepValuesOnTheDevice(configSet) {
|
|
1621
|
+
const store = this.cachedValuesStore;
|
|
1622
|
+
if (!store) return;
|
|
1623
|
+
const context = this.submittedContext;
|
|
1624
|
+
const receivedAt = Date.now();
|
|
1625
|
+
this.clientSession.then(({ instanceId }) => store.replace({
|
|
1626
|
+
contextTag: contextTagFor(context, instanceId),
|
|
1627
|
+
environmentId: configSet.environmentId,
|
|
1628
|
+
projectId: configSet.projectId,
|
|
1629
|
+
configs: configSet.configs,
|
|
1630
|
+
receivedAt
|
|
1631
|
+
}));
|
|
1632
|
+
}
|
|
1633
|
+
servedReason(reason) {
|
|
1634
|
+
return this.servingCachedValues && reason === "found-match" ? "cached" : reason;
|
|
1385
1635
|
}
|
|
1386
1636
|
registerHandler(event, handler) {
|
|
1387
1637
|
if (!handler) return;
|
|
@@ -1477,6 +1727,8 @@ var DefaultConfigDirectorClient = class DefaultConfigDirectorClient {
|
|
|
1477
1727
|
return this.currentContext;
|
|
1478
1728
|
}
|
|
1479
1729
|
async connectToTransport(context, caller) {
|
|
1730
|
+
this.submittedContext = context;
|
|
1731
|
+
if (caller === "initialization") this.startCachedValuesAdoption();
|
|
1480
1732
|
try {
|
|
1481
1733
|
this.ready = false;
|
|
1482
1734
|
this.readyPromise = new Promise((resolve) => {
|
|
@@ -1581,6 +1833,7 @@ var DefaultConfigDirectorClient = class DefaultConfigDirectorClient {
|
|
|
1581
1833
|
return defaultValue;
|
|
1582
1834
|
}
|
|
1583
1835
|
const parseResult = parseConfigValue(configState, defaultValue);
|
|
1836
|
+
const reason = this.servedReason(parseResult.reason);
|
|
1584
1837
|
this.telemetryClient.evaluatedConfig({
|
|
1585
1838
|
contextId: (_this$currentContext2 = this.currentContext) === null || _this$currentContext2 === void 0 ? void 0 : _this$currentContext2.id,
|
|
1586
1839
|
type: configState.type,
|
|
@@ -1590,14 +1843,14 @@ var DefaultConfigDirectorClient = class DefaultConfigDirectorClient {
|
|
|
1590
1843
|
evaluatedValue: parseResult.parsedValue,
|
|
1591
1844
|
evaluatedValueId: parseResult.parsedValueId,
|
|
1592
1845
|
usedDefault: parseResult.usedDefault,
|
|
1593
|
-
evaluationReason:
|
|
1846
|
+
evaluationReason: reason
|
|
1594
1847
|
});
|
|
1595
1848
|
this.dispatchEvaluationEvent({
|
|
1596
1849
|
key: configKey,
|
|
1597
1850
|
value: parseResult.parsedValue,
|
|
1598
1851
|
valueId: (_parseResult$parsedVa = parseResult.parsedValueId) !== null && _parseResult$parsedVa !== void 0 ? _parseResult$parsedVa : void 0,
|
|
1599
1852
|
isDefaultValue: parseResult.usedDefault,
|
|
1600
|
-
reason
|
|
1853
|
+
reason,
|
|
1601
1854
|
context: this.context
|
|
1602
1855
|
});
|
|
1603
1856
|
this.logger.debug(`[ConfigDirectorClient] Evaluated '${configKey}' to '${parseResult.parsedValue}'`);
|
|
@@ -1677,4 +1930,4 @@ var DefaultConfigDirectorClient = class DefaultConfigDirectorClient {
|
|
|
1677
1930
|
const INSTALLED_TEST_CLIENT_SLOT = Symbol.for("@configdirector/installed-test-client");
|
|
1678
1931
|
const readInstalledTestClient = () => globalThis[INSTALLED_TEST_CLIENT_SLOT];
|
|
1679
1932
|
//#endregion
|
|
1680
|
-
export {
|
|
1933
|
+
export { DEFAULT_CACHING_SIZE_LIMIT_BYTES as a, createDefaultLogger as c, ConfigDirectorConnectionError as d, ConfigDirectorValidationError as f, CACHED_VALUES_STORAGE_KEY_PREFIX as i, nativeValueConfigState as l, _defineProperty as m, readInstalledTestClient as n, fetchWithTimeout as o, Emitter as p, DefaultConfigDirectorClient as r, discardingTelemetryClient as s, INSTALLED_TEST_CLIENT_SLOT as t, isFetchErrorFatal as u };
|
|
@@ -23,7 +23,7 @@ interface ConfigDirectorLogger {
|
|
|
23
23
|
interface ConfigDirectorLogMessageDecorator {
|
|
24
24
|
decorateMessage(message: string): string;
|
|
25
25
|
}
|
|
26
|
-
type EvaluationReason = "found-match" | "config-state-missing" | "client-not-ready" | "type-mismatch" | "value-missing" | "invalid-number" | "invalid-json" | "invalid-boolean";
|
|
26
|
+
type EvaluationReason = "found-match" | "config-state-missing" | "client-not-ready" | "type-mismatch" | "value-missing" | "invalid-number" | "invalid-json" | "invalid-boolean" | "cached";
|
|
27
27
|
type ConfigEvaluation = {
|
|
28
28
|
key: string;
|
|
29
29
|
value: ConfigValueType;
|
|
@@ -42,9 +42,11 @@ type ConfigDirectorContext = {
|
|
|
42
42
|
* The context's identifier. This should be a value that uniquely identifies the user or other
|
|
43
43
|
* entity among contexts of the same {@link kind}.
|
|
44
44
|
* In the case of anonymous users, you could generate a UUID or alternatively not provide
|
|
45
|
-
* the {@link id}
|
|
46
|
-
*
|
|
47
|
-
*
|
|
45
|
+
* the {@link id}. In a client SDK, ConfigDirector then derives an identifier from an ID the SDK
|
|
46
|
+
* stores for the app install or the website, so it stays the same until the app is uninstalled
|
|
47
|
+
* or the site's data is cleared. However, keep in mind that this value is used for segmenting
|
|
48
|
+
* contexts in percentage rollouts, and changes to the {@link id} could result in the context
|
|
49
|
+
* being assigned to a different percentile.
|
|
48
50
|
*/
|
|
49
51
|
id?: string;
|
|
50
52
|
/**
|
|
@@ -100,6 +102,45 @@ type NativeValue = boolean | number | string | NativeJsonValue[] | {
|
|
|
100
102
|
type NativeValues = Record<string, NativeValue>;
|
|
101
103
|
//#endregion
|
|
102
104
|
//#region ../js-client-core/src/types.d.ts
|
|
105
|
+
/**
|
|
106
|
+
* A store for the values the client keeps on the device between launches. One provider instance
|
|
107
|
+
* serves one client: the provider decides where the entry lives, and the SDK owns the entry's
|
|
108
|
+
* format, which may change between SDK versions.
|
|
109
|
+
*/
|
|
110
|
+
type CachingProvider = {
|
|
111
|
+
/**
|
|
112
|
+
* Returns the stored entry, or `null` or `undefined` when nothing is stored.
|
|
113
|
+
*/
|
|
114
|
+
read(): string | null | undefined | Promise<string | null | undefined>;
|
|
115
|
+
/**
|
|
116
|
+
* Replaces the stored entry. The SDK never writes an entry larger than {@link sizeLimit}.
|
|
117
|
+
*/
|
|
118
|
+
write(value: string): void | Promise<void>;
|
|
119
|
+
/**
|
|
120
|
+
* Removes the stored entry.
|
|
121
|
+
*/
|
|
122
|
+
remove(): void | Promise<void>;
|
|
123
|
+
/**
|
|
124
|
+
* The largest entry, in UTF-8 bytes, the provider accepts. When the values received from
|
|
125
|
+
* ConfigDirector exceed it, the SDK removes the stored entry and keeps nothing on the device.
|
|
126
|
+
*/
|
|
127
|
+
sizeLimit(): number;
|
|
128
|
+
};
|
|
129
|
+
/**
|
|
130
|
+
* Options for the values the client keeps on the device, served on the next launch of the same
|
|
131
|
+
* context until live values arrive. Ignored in `offline` mode.
|
|
132
|
+
*/
|
|
133
|
+
type CachingOptions = {
|
|
134
|
+
/**
|
|
135
|
+
* Whether to keep the last values received on the device. Defaults to `true`.
|
|
136
|
+
*/
|
|
137
|
+
enabled?: boolean;
|
|
138
|
+
/**
|
|
139
|
+
* The store for the values. Defaults to the platform's own store: `localStorage` in the
|
|
140
|
+
* browser, AsyncStorage on React Native.
|
|
141
|
+
*/
|
|
142
|
+
provider?: CachingProvider;
|
|
143
|
+
};
|
|
103
144
|
/**
|
|
104
145
|
* One value an offline client serves for a config key. The config type follows from the value: a
|
|
105
146
|
* boolean, an integral number (`integer`), any other number (`float`), a string, or a plain object
|
|
@@ -170,6 +211,12 @@ type ConfigDirectorClientOptions = {
|
|
|
170
211
|
* Connection options
|
|
171
212
|
*/
|
|
172
213
|
connection?: ConnectionOptions;
|
|
214
|
+
/**
|
|
215
|
+
* Options for the values the client keeps on the device between launches. The last values
|
|
216
|
+
* received are kept by default and served, with the evaluation reason `cached`, on the next
|
|
217
|
+
* launch of the same context until live values arrive. See {@link CachingOptions}.
|
|
218
|
+
*/
|
|
219
|
+
caching?: CachingOptions;
|
|
173
220
|
/**
|
|
174
221
|
* A logger that implements {@link ConfigDirectorLogger}. It defaults to the ConfigDirector console
|
|
175
222
|
* logger set to 'warn' level.
|
|
@@ -207,6 +254,7 @@ type ClientHooks = {
|
|
|
207
254
|
contextUpdated?: HookHandler<"contextUpdated"> | HookHandler<"contextUpdated">[];
|
|
208
255
|
configEvaluated?: HookHandler<"configEvaluated"> | HookHandler<"configEvaluated">[];
|
|
209
256
|
connectionError?: HookHandler<"connectionError"> | HookHandler<"connectionError">[];
|
|
257
|
+
cachedValuesLoaded?: HookHandler<"cachedValuesLoaded"> | HookHandler<"cachedValuesLoaded">[];
|
|
210
258
|
};
|
|
211
259
|
type ClientConnectAction = "initialization" | "context update" | "network resume";
|
|
212
260
|
type HookHandler<TEvent extends keyof ClientEvents> = (payload: ClientEvents[TEvent]) => void;
|
|
@@ -227,6 +275,7 @@ type ClientEvents = {
|
|
|
227
275
|
connectionError: {
|
|
228
276
|
error: Error;
|
|
229
277
|
};
|
|
278
|
+
cachedValuesLoaded: Record<string, never>;
|
|
230
279
|
};
|
|
231
280
|
type WatchHandler<T extends ConfigValueType> = (message: T) => void;
|
|
232
281
|
/**
|
|
@@ -348,4 +397,4 @@ interface ConfigDirectorClient extends EventProvider<ClientEvents> {
|
|
|
348
397
|
dispose(): void;
|
|
349
398
|
}
|
|
350
399
|
//#endregion
|
|
351
|
-
export {
|
|
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 };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@configdirector/react-native-sdk",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.9.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",
|