@launchdarkly/js-sdk-common 2.17.0 → 2.19.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/CHANGELOG.md +14 -0
- package/dist/cjs/AttributeReference.d.ts +1 -0
- package/dist/cjs/AttributeReference.d.ts.map +1 -1
- package/dist/cjs/Context.d.ts +9 -0
- package/dist/cjs/Context.d.ts.map +1 -1
- package/dist/cjs/api/platform/Requests.d.ts +1 -0
- package/dist/cjs/api/platform/Requests.d.ts.map +1 -1
- package/dist/cjs/api/subsystem/DataSystem/CallbackHandler.d.ts.map +1 -1
- package/dist/cjs/api/subsystem/DataSystem/DataSource.d.ts +18 -2
- package/dist/cjs/api/subsystem/DataSystem/DataSource.d.ts.map +1 -1
- package/dist/cjs/datasource/CompositeDataSource.d.ts +7 -2
- package/dist/cjs/datasource/CompositeDataSource.d.ts.map +1 -1
- package/dist/cjs/datasource/errors.d.ts +9 -0
- package/dist/cjs/datasource/errors.d.ts.map +1 -1
- package/dist/cjs/datasource/index.d.ts +2 -2
- package/dist/cjs/datasource/index.d.ts.map +1 -1
- package/dist/cjs/index.cjs +201 -47
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/index.d.ts +2 -2
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/internal/events/EventProcessor.d.ts +1 -1
- package/dist/cjs/internal/events/EventProcessor.d.ts.map +1 -1
- package/dist/cjs/internal/events/LDEventSummarizer.d.ts +32 -0
- package/dist/cjs/internal/events/LDEventSummarizer.d.ts.map +1 -0
- package/dist/cjs/internal/events/MultiEventSummarizer.d.ts +13 -0
- package/dist/cjs/internal/events/MultiEventSummarizer.d.ts.map +1 -0
- package/dist/cjs/internal/fdv2/payloadProcessor.d.ts.map +1 -1
- package/dist/cjs/internal/fdv2/proto.d.ts +1 -1
- package/dist/cjs/internal/fdv2/proto.d.ts.map +1 -1
- package/dist/cjs/internal/index.d.ts +1 -0
- package/dist/cjs/internal/index.d.ts.map +1 -1
- package/dist/cjs/internal/json/canonicalize.d.ts +10 -0
- package/dist/cjs/internal/json/canonicalize.d.ts.map +1 -0
- package/dist/cjs/internal/json/index.d.ts +2 -0
- package/dist/cjs/internal/json/index.d.ts.map +1 -0
- package/dist/cjs/options/ServiceEndpoints.d.ts +2 -2
- package/dist/cjs/options/ServiceEndpoints.d.ts.map +1 -1
- package/dist/esm/AttributeReference.d.ts +1 -0
- package/dist/esm/AttributeReference.d.ts.map +1 -1
- package/dist/esm/Context.d.ts +9 -0
- package/dist/esm/Context.d.ts.map +1 -1
- package/dist/esm/api/platform/Requests.d.ts +1 -0
- package/dist/esm/api/platform/Requests.d.ts.map +1 -1
- package/dist/esm/api/subsystem/DataSystem/CallbackHandler.d.ts.map +1 -1
- package/dist/esm/api/subsystem/DataSystem/DataSource.d.ts +18 -2
- package/dist/esm/api/subsystem/DataSystem/DataSource.d.ts.map +1 -1
- package/dist/esm/datasource/CompositeDataSource.d.ts +7 -2
- package/dist/esm/datasource/CompositeDataSource.d.ts.map +1 -1
- package/dist/esm/datasource/errors.d.ts +9 -0
- package/dist/esm/datasource/errors.d.ts.map +1 -1
- package/dist/esm/datasource/index.d.ts +2 -2
- package/dist/esm/datasource/index.d.ts.map +1 -1
- package/dist/esm/index.d.ts +2 -2
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.mjs +201 -48
- package/dist/esm/index.mjs.map +1 -1
- package/dist/esm/internal/events/EventProcessor.d.ts +1 -1
- package/dist/esm/internal/events/EventProcessor.d.ts.map +1 -1
- package/dist/esm/internal/events/LDEventSummarizer.d.ts +32 -0
- package/dist/esm/internal/events/LDEventSummarizer.d.ts.map +1 -0
- package/dist/esm/internal/events/MultiEventSummarizer.d.ts +13 -0
- package/dist/esm/internal/events/MultiEventSummarizer.d.ts.map +1 -0
- package/dist/esm/internal/fdv2/payloadProcessor.d.ts.map +1 -1
- package/dist/esm/internal/fdv2/proto.d.ts +1 -1
- package/dist/esm/internal/fdv2/proto.d.ts.map +1 -1
- package/dist/esm/internal/index.d.ts +1 -0
- package/dist/esm/internal/index.d.ts.map +1 -1
- package/dist/esm/internal/json/canonicalize.d.ts +10 -0
- package/dist/esm/internal/json/canonicalize.d.ts.map +1 -0
- package/dist/esm/internal/json/index.d.ts +2 -0
- package/dist/esm/internal/json/index.d.ts.map +1 -0
- package/dist/esm/options/ServiceEndpoints.d.ts +2 -2
- package/dist/esm/options/ServiceEndpoints.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/cjs/index.cjs
CHANGED
|
@@ -117,6 +117,9 @@ class AttributeReference {
|
|
|
117
117
|
return (this.depth === other.depth &&
|
|
118
118
|
this._components.every((value, index) => value === other.getComponent(index)));
|
|
119
119
|
}
|
|
120
|
+
get components() {
|
|
121
|
+
return [...this._components];
|
|
122
|
+
}
|
|
120
123
|
}
|
|
121
124
|
/**
|
|
122
125
|
* For use as invalid references when deserializing Flag/Segment data.
|
|
@@ -316,6 +319,41 @@ function isLegacyUser(context) {
|
|
|
316
319
|
return !('kind' in context) || context.kind === null || context.kind === undefined;
|
|
317
320
|
}
|
|
318
321
|
|
|
322
|
+
/**
|
|
323
|
+
* Given some object to serialize product a canonicalized JSON string.
|
|
324
|
+
* https://www.rfc-editor.org/rfc/rfc8785.html
|
|
325
|
+
*
|
|
326
|
+
* We do not support custom toJSON methods on objects. Objects should be limited to basic types.
|
|
327
|
+
*
|
|
328
|
+
* @param object The object to serialize.
|
|
329
|
+
*/
|
|
330
|
+
function canonicalize(object, visited = []) {
|
|
331
|
+
// For JavaScript the default JSON serialization will produce canonicalized output for basic types.
|
|
332
|
+
if (object === null || typeof object !== 'object') {
|
|
333
|
+
return JSON.stringify(object);
|
|
334
|
+
}
|
|
335
|
+
if (visited.includes(object)) {
|
|
336
|
+
throw new Error('Cycle detected');
|
|
337
|
+
}
|
|
338
|
+
if (Array.isArray(object)) {
|
|
339
|
+
const values = object
|
|
340
|
+
.map((item) => canonicalize(item, [...visited, object]))
|
|
341
|
+
.map((item) => (item === undefined ? 'null' : item));
|
|
342
|
+
return `[${values.join(',')}]`;
|
|
343
|
+
}
|
|
344
|
+
const values = Object.keys(object)
|
|
345
|
+
.sort()
|
|
346
|
+
.map((key) => {
|
|
347
|
+
const value = canonicalize(object[key], [...visited, object]);
|
|
348
|
+
if (value !== undefined) {
|
|
349
|
+
return `${JSON.stringify(key)}:${value}`;
|
|
350
|
+
}
|
|
351
|
+
return undefined;
|
|
352
|
+
})
|
|
353
|
+
.filter((item) => item !== undefined);
|
|
354
|
+
return `{${values.join(',')}}`;
|
|
355
|
+
}
|
|
356
|
+
|
|
319
357
|
// The general strategy for the context is to transform the passed in context
|
|
320
358
|
// as little as possible. We do convert the legacy users to a single kind
|
|
321
359
|
// context, but we do not translate all passed contexts into a rigid structure.
|
|
@@ -690,6 +728,28 @@ class Context {
|
|
|
690
728
|
get legacy() {
|
|
691
729
|
return this._wasLegacy;
|
|
692
730
|
}
|
|
731
|
+
/**
|
|
732
|
+
* Get the serialized canonical JSON for this context. This is not filtered for use in events.
|
|
733
|
+
*
|
|
734
|
+
* This method will cache the result.
|
|
735
|
+
*
|
|
736
|
+
* @returns The serialized canonical JSON or undefined if it cannot be serialized.
|
|
737
|
+
*/
|
|
738
|
+
canonicalUnfilteredJson() {
|
|
739
|
+
if (!this.valid) {
|
|
740
|
+
return undefined;
|
|
741
|
+
}
|
|
742
|
+
if (this._cachedCanonicalJson) {
|
|
743
|
+
return this._cachedCanonicalJson;
|
|
744
|
+
}
|
|
745
|
+
try {
|
|
746
|
+
this._cachedCanonicalJson = canonicalize(Context.toLDContext(this));
|
|
747
|
+
}
|
|
748
|
+
catch {
|
|
749
|
+
// Indicated by undefined being returned.
|
|
750
|
+
}
|
|
751
|
+
return this._cachedCanonicalJson;
|
|
752
|
+
}
|
|
693
753
|
}
|
|
694
754
|
Context.UserKind = DEFAULT_KIND;
|
|
695
755
|
|
|
@@ -891,8 +951,6 @@ class CallbackHandler {
|
|
|
891
951
|
if (this._disabled) {
|
|
892
952
|
return;
|
|
893
953
|
}
|
|
894
|
-
// TODO: SDK-1044 track selector for future synchronizer to use
|
|
895
|
-
// report data up
|
|
896
954
|
this._dataCallback(basis, data);
|
|
897
955
|
}
|
|
898
956
|
async statusHandler(status, err) {
|
|
@@ -904,6 +962,11 @@ class CallbackHandler {
|
|
|
904
962
|
}
|
|
905
963
|
|
|
906
964
|
// TODO: refactor client-sdk to use this enum
|
|
965
|
+
/**
|
|
966
|
+
* @experimental
|
|
967
|
+
* This feature is not stable and not subject to any backwards compatibility guarantees or semantic
|
|
968
|
+
* versioning. It is not suitable for production usage.
|
|
969
|
+
*/
|
|
907
970
|
var DataSourceState;
|
|
908
971
|
(function (DataSourceState) {
|
|
909
972
|
// Positive confirmation of connection/data receipt
|
|
@@ -1004,6 +1067,43 @@ class DataSourceList {
|
|
|
1004
1067
|
}
|
|
1005
1068
|
}
|
|
1006
1069
|
|
|
1070
|
+
class LDFileDataSourceError extends Error {
|
|
1071
|
+
constructor(message) {
|
|
1072
|
+
super(message);
|
|
1073
|
+
this.name = 'LaunchDarklyFileDataSourceError';
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
class LDPollingError extends Error {
|
|
1077
|
+
constructor(kind, message, status, recoverable = true) {
|
|
1078
|
+
super(message);
|
|
1079
|
+
this.kind = kind;
|
|
1080
|
+
this.status = status;
|
|
1081
|
+
this.name = 'LaunchDarklyPollingError';
|
|
1082
|
+
this.recoverable = recoverable;
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
class LDStreamingError extends Error {
|
|
1086
|
+
constructor(kind, message, code, recoverable = true) {
|
|
1087
|
+
super(message);
|
|
1088
|
+
this.kind = kind;
|
|
1089
|
+
this.code = code;
|
|
1090
|
+
this.name = 'LaunchDarklyStreamingError';
|
|
1091
|
+
this.recoverable = recoverable;
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
/**
|
|
1095
|
+
* This is a short term error and will be removed once FDv2 adoption is sufficient.
|
|
1096
|
+
*/
|
|
1097
|
+
class LDFlagDeliveryFallbackError extends Error {
|
|
1098
|
+
constructor(kind, message, code) {
|
|
1099
|
+
super(message);
|
|
1100
|
+
this.kind = kind;
|
|
1101
|
+
this.code = code;
|
|
1102
|
+
this.name = 'LDFlagDeliveryFallbackError';
|
|
1103
|
+
this.recoverable = false;
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1007
1107
|
const DEFAULT_FALLBACK_TIME_MS = 2 * 60 * 1000;
|
|
1008
1108
|
const DEFAULT_RECOVERY_TIME_MS = 5 * 60 * 1000;
|
|
1009
1109
|
/**
|
|
@@ -1014,8 +1114,12 @@ class CompositeDataSource {
|
|
|
1014
1114
|
/**
|
|
1015
1115
|
* @param initializers factories to create {@link DataSystemInitializer}s, in priority order.
|
|
1016
1116
|
* @param synchronizers factories to create {@link DataSystemSynchronizer}s, in priority order.
|
|
1117
|
+
* @param fdv1Synchronizers factories to fallback to if we need to fallback to FDv1.
|
|
1118
|
+
* @param _logger for logging
|
|
1119
|
+
* @param _transitionConditions to control automated transition between datasources. Typically only used for testing.
|
|
1120
|
+
* @param _backoff to control delay between transitions. Typically only used for testing.
|
|
1017
1121
|
*/
|
|
1018
|
-
constructor(initializers, synchronizers, _logger, _transitionConditions = {
|
|
1122
|
+
constructor(initializers, synchronizers, fdv1Synchronizers, _logger, _transitionConditions = {
|
|
1019
1123
|
[DataSourceState.Valid]: {
|
|
1020
1124
|
durationMS: DEFAULT_RECOVERY_TIME_MS,
|
|
1021
1125
|
transition: 'recover',
|
|
@@ -1051,8 +1155,9 @@ class CompositeDataSource {
|
|
|
1051
1155
|
this._initPhaseActive = initializers.length > 0; // init phase if we have initializers
|
|
1052
1156
|
this._initFactories = new DataSourceList(false, initializers);
|
|
1053
1157
|
this._syncFactories = new DataSourceList(true, synchronizers);
|
|
1158
|
+
this._fdv1Synchronizers = new DataSourceList(true, fdv1Synchronizers);
|
|
1054
1159
|
}
|
|
1055
|
-
async start(dataCallback, statusCallback) {
|
|
1160
|
+
async start(dataCallback, statusCallback, selectorGetter) {
|
|
1056
1161
|
if (!this._stopped) {
|
|
1057
1162
|
// don't allow multiple simultaneous runs
|
|
1058
1163
|
this._logger?.info('CompositeDataSource already running. Ignoring call to start.');
|
|
@@ -1089,12 +1194,18 @@ class CompositeDataSource {
|
|
|
1089
1194
|
// When we get a status update, we want to fallback if it is an error. We also want to schedule a transition for some
|
|
1090
1195
|
// time in the future if this status remains for some duration (ex: Recover to primary synchronizer after the secondary
|
|
1091
1196
|
// synchronizer has been Valid for some time). These scheduled transitions are configurable in the constructor.
|
|
1092
|
-
this._logger?.debug(`CompositeDataSource received state ${state} from underlying data source
|
|
1197
|
+
this._logger?.debug(`CompositeDataSource received state ${state} from underlying data source. Err is ${err}`);
|
|
1093
1198
|
if (err || state === DataSourceState.Closed) {
|
|
1094
1199
|
callbackHandler.disable();
|
|
1095
|
-
if (err
|
|
1200
|
+
if (err?.recoverable === false) {
|
|
1096
1201
|
// don't use this datasource's factory again
|
|
1202
|
+
this._logger?.debug(`Culling data source due to err ${err}`);
|
|
1097
1203
|
cullDSFactory?.();
|
|
1204
|
+
// this error indicates we should fallback to only using FDv1 synchronizers
|
|
1205
|
+
if (err instanceof LDFlagDeliveryFallbackError) {
|
|
1206
|
+
this._logger?.debug(`Falling back to FDv1`);
|
|
1207
|
+
this._syncFactories = this._fdv1Synchronizers;
|
|
1208
|
+
}
|
|
1098
1209
|
}
|
|
1099
1210
|
sanitizedStatusCallback(state, err);
|
|
1100
1211
|
this._consumeCancelToken(cancelScheduledTransition);
|
|
@@ -1121,7 +1232,7 @@ class CompositeDataSource {
|
|
|
1121
1232
|
}
|
|
1122
1233
|
}
|
|
1123
1234
|
});
|
|
1124
|
-
currentDS.start((basis, data) => callbackHandler.dataHandler(basis, data), (status, err) => callbackHandler.statusHandler(status, err));
|
|
1235
|
+
currentDS.start((basis, data) => callbackHandler.dataHandler(basis, data), (status, err) => callbackHandler.statusHandler(status, err), selectorGetter);
|
|
1125
1236
|
}
|
|
1126
1237
|
else {
|
|
1127
1238
|
// we don't have a data source to use!
|
|
@@ -1142,8 +1253,8 @@ class CompositeDataSource {
|
|
|
1142
1253
|
// stop the underlying datasource before transitioning to next state
|
|
1143
1254
|
currentDS?.stop();
|
|
1144
1255
|
if (transitionRequest.err && transitionRequest.transition !== 'stop') {
|
|
1145
|
-
// if the transition was due to an error, throttle the transition
|
|
1146
|
-
const delay = this._backoff.fail();
|
|
1256
|
+
// if the transition was due to an error we're not in the initializer phase, throttle the transition. Fallback between initializers is not throttled.
|
|
1257
|
+
const delay = this._initPhaseActive ? 0 : this._backoff.fail();
|
|
1147
1258
|
const { promise, cancel: cancelDelay } = this._cancellableDelay(delay);
|
|
1148
1259
|
this._cancelTokens.push(cancelDelay);
|
|
1149
1260
|
const delayedTransition = promise.then(() => {
|
|
@@ -1178,6 +1289,7 @@ class CompositeDataSource {
|
|
|
1178
1289
|
this._initPhaseActive = this._initFactories.length() > 0; // init phase if we have initializers;
|
|
1179
1290
|
this._initFactories.reset();
|
|
1180
1291
|
this._syncFactories.reset();
|
|
1292
|
+
this._fdv1Synchronizers.reset();
|
|
1181
1293
|
this._externalTransitionPromise = new Promise((tr) => {
|
|
1182
1294
|
this._externalTransitionResolve = tr;
|
|
1183
1295
|
});
|
|
@@ -1212,6 +1324,11 @@ class CompositeDataSource {
|
|
|
1212
1324
|
break;
|
|
1213
1325
|
case 'fallback':
|
|
1214
1326
|
default:
|
|
1327
|
+
// if asked to fallback after using all init factories, switch to sync factories
|
|
1328
|
+
if (this._initPhaseActive && this._initFactories.pos() >= this._initFactories.length()) {
|
|
1329
|
+
this._initPhaseActive = false;
|
|
1330
|
+
this._syncFactories.reset();
|
|
1331
|
+
}
|
|
1215
1332
|
if (this._initPhaseActive) {
|
|
1216
1333
|
isPrimary = this._initFactories.pos() === 0;
|
|
1217
1334
|
factory = this._initFactories.next();
|
|
@@ -1303,31 +1420,6 @@ exports.DataSourceErrorKind = void 0;
|
|
|
1303
1420
|
DataSourceErrorKind["InvalidData"] = "INVALID_DATA";
|
|
1304
1421
|
})(exports.DataSourceErrorKind || (exports.DataSourceErrorKind = {}));
|
|
1305
1422
|
|
|
1306
|
-
class LDFileDataSourceError extends Error {
|
|
1307
|
-
constructor(message) {
|
|
1308
|
-
super(message);
|
|
1309
|
-
this.name = 'LaunchDarklyFileDataSourceError';
|
|
1310
|
-
}
|
|
1311
|
-
}
|
|
1312
|
-
class LDPollingError extends Error {
|
|
1313
|
-
constructor(kind, message, status, recoverable = true) {
|
|
1314
|
-
super(message);
|
|
1315
|
-
this.kind = kind;
|
|
1316
|
-
this.status = status;
|
|
1317
|
-
this.name = 'LaunchDarklyPollingError';
|
|
1318
|
-
this.recoverable = recoverable;
|
|
1319
|
-
}
|
|
1320
|
-
}
|
|
1321
|
-
class LDStreamingError extends Error {
|
|
1322
|
-
constructor(kind, message, code, recoverable = true) {
|
|
1323
|
-
super(message);
|
|
1324
|
-
this.kind = kind;
|
|
1325
|
-
this.code = code;
|
|
1326
|
-
this.name = 'LaunchDarklyStreamingError';
|
|
1327
|
-
this.recoverable = recoverable;
|
|
1328
|
-
}
|
|
1329
|
-
}
|
|
1330
|
-
|
|
1331
1423
|
/* eslint-disable import/prefer-default-export */
|
|
1332
1424
|
/**
|
|
1333
1425
|
* Enable / disable Auto environment attributes. When enabled, the SDK will automatically
|
|
@@ -1810,7 +1902,7 @@ class ServiceEndpoints {
|
|
|
1810
1902
|
}
|
|
1811
1903
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
1812
1904
|
ServiceEndpoints.DEFAULT_EVENTS = 'https://events.launchdarkly.com';
|
|
1813
|
-
function getWithParams(uri, parameters) {
|
|
1905
|
+
function getWithParams(uri, parameters = []) {
|
|
1814
1906
|
if (parameters.length === 0) {
|
|
1815
1907
|
return uri;
|
|
1816
1908
|
}
|
|
@@ -1839,7 +1931,7 @@ function getStreamingUri(endpoints, path, parameters) {
|
|
|
1839
1931
|
* @param path The path to the resource, devoid of any query parameters or hrefs.
|
|
1840
1932
|
* @param parameters The query parameters. These query parameters must already have the appropriate encoding applied. This function WILL NOT apply it for you.
|
|
1841
1933
|
*/
|
|
1842
|
-
function getPollingUri(endpoints, path, parameters) {
|
|
1934
|
+
function getPollingUri(endpoints, path, parameters = []) {
|
|
1843
1935
|
const canonicalizedPath = canonicalizePath(path);
|
|
1844
1936
|
const combinedParameters = [...parameters];
|
|
1845
1937
|
if (endpoints.payloadFilterKey) {
|
|
@@ -1854,7 +1946,7 @@ function getPollingUri(endpoints, path, parameters) {
|
|
|
1854
1946
|
* @param path The path to the resource, devoid of any query parameters or hrefs.
|
|
1855
1947
|
* @param parameters The query parameters. These query parameters must already have the appropriate encoding applied. This function WILL NOT apply it for you.
|
|
1856
1948
|
*/
|
|
1857
|
-
function getEventsUri(endpoints, path, parameters) {
|
|
1949
|
+
function getEventsUri(endpoints, path, parameters = []) {
|
|
1858
1950
|
const canonicalizedPath = canonicalizePath(path);
|
|
1859
1951
|
return getWithParams(`${endpoints.events}/${canonicalizedPath}`, parameters);
|
|
1860
1952
|
}
|
|
@@ -2353,7 +2445,9 @@ function counterKey(event) {
|
|
|
2353
2445
|
* @internal
|
|
2354
2446
|
*/
|
|
2355
2447
|
class EventSummarizer {
|
|
2356
|
-
constructor() {
|
|
2448
|
+
constructor(_singleContext = false, _contextFilter) {
|
|
2449
|
+
this._singleContext = _singleContext;
|
|
2450
|
+
this._contextFilter = _contextFilter;
|
|
2357
2451
|
this._startDate = 0;
|
|
2358
2452
|
this._endDate = 0;
|
|
2359
2453
|
this._counters = {};
|
|
@@ -2361,6 +2455,9 @@ class EventSummarizer {
|
|
|
2361
2455
|
}
|
|
2362
2456
|
summarizeEvent(event) {
|
|
2363
2457
|
if (isFeature(event) && !event.excludeFromSummaries) {
|
|
2458
|
+
if (!this._context) {
|
|
2459
|
+
this._context = event.context;
|
|
2460
|
+
}
|
|
2364
2461
|
const countKey = counterKey(event);
|
|
2365
2462
|
const counter = this._counters[countKey];
|
|
2366
2463
|
let kinds = this._contextKinds[event.key];
|
|
@@ -2410,14 +2507,19 @@ class EventSummarizer {
|
|
|
2410
2507
|
flagSummary.counters.push(counterOut);
|
|
2411
2508
|
return acc;
|
|
2412
2509
|
}, {});
|
|
2413
|
-
|
|
2510
|
+
const event = {
|
|
2414
2511
|
startDate: this._startDate,
|
|
2415
2512
|
endDate: this._endDate,
|
|
2416
2513
|
features,
|
|
2417
2514
|
kind: 'summary',
|
|
2515
|
+
context: this._context !== undefined && this._singleContext
|
|
2516
|
+
? this._contextFilter?.filter(this._context)
|
|
2517
|
+
: undefined,
|
|
2418
2518
|
};
|
|
2519
|
+
this._clearSummary();
|
|
2520
|
+
return event;
|
|
2419
2521
|
}
|
|
2420
|
-
|
|
2522
|
+
_clearSummary() {
|
|
2421
2523
|
this._startDate = 0;
|
|
2422
2524
|
this._endDate = 0;
|
|
2423
2525
|
this._counters = {};
|
|
@@ -2432,6 +2534,38 @@ class LDInvalidSDKKeyError extends Error {
|
|
|
2432
2534
|
}
|
|
2433
2535
|
}
|
|
2434
2536
|
|
|
2537
|
+
class MultiEventSummarizer {
|
|
2538
|
+
constructor(_contextFilter, _logger) {
|
|
2539
|
+
this._contextFilter = _contextFilter;
|
|
2540
|
+
this._logger = _logger;
|
|
2541
|
+
this._summarizers = {};
|
|
2542
|
+
}
|
|
2543
|
+
summarizeEvent(event) {
|
|
2544
|
+
if (isFeature(event)) {
|
|
2545
|
+
const key = event.context.canonicalUnfilteredJson();
|
|
2546
|
+
if (!key) {
|
|
2547
|
+
if (event.context.valid) {
|
|
2548
|
+
// The context appeared valid, but it could not be hashed.
|
|
2549
|
+
// This is likely because of a cycle in the data.
|
|
2550
|
+
this._logger?.error('Unable to serialize context, likely the context contains a cycle.');
|
|
2551
|
+
}
|
|
2552
|
+
return;
|
|
2553
|
+
}
|
|
2554
|
+
let summarizer = this._summarizers[key];
|
|
2555
|
+
if (!summarizer) {
|
|
2556
|
+
this._summarizers[key] = new EventSummarizer(true, this._contextFilter);
|
|
2557
|
+
summarizer = this._summarizers[key];
|
|
2558
|
+
}
|
|
2559
|
+
summarizer.summarizeEvent(event);
|
|
2560
|
+
}
|
|
2561
|
+
}
|
|
2562
|
+
getSummaries() {
|
|
2563
|
+
const summarizersToFlush = this._summarizers;
|
|
2564
|
+
this._summarizers = {};
|
|
2565
|
+
return Object.values(summarizersToFlush).map((summarizer) => summarizer.getSummary());
|
|
2566
|
+
}
|
|
2567
|
+
}
|
|
2568
|
+
|
|
2435
2569
|
/**
|
|
2436
2570
|
* The contents of this file are for event sampling. They are not used for
|
|
2437
2571
|
* any purpose requiring cryptographic security.
|
|
@@ -2452,12 +2586,14 @@ function shouldSample(ratio) {
|
|
|
2452
2586
|
return Math.floor(Math.random() * truncated) === 0;
|
|
2453
2587
|
}
|
|
2454
2588
|
|
|
2589
|
+
function isMultiEventSummarizer(summarizer) {
|
|
2590
|
+
return summarizer.getSummaries !== undefined;
|
|
2591
|
+
}
|
|
2455
2592
|
class EventProcessor {
|
|
2456
|
-
constructor(_config, clientContext, baseHeaders, _contextDeduplicator, _diagnosticsManager, start = true) {
|
|
2593
|
+
constructor(_config, clientContext, baseHeaders, _contextDeduplicator, _diagnosticsManager, start = true, summariesPerContext = false) {
|
|
2457
2594
|
this._config = _config;
|
|
2458
2595
|
this._contextDeduplicator = _contextDeduplicator;
|
|
2459
2596
|
this._diagnosticsManager = _diagnosticsManager;
|
|
2460
|
-
this._summarizer = new EventSummarizer();
|
|
2461
2597
|
this._queue = [];
|
|
2462
2598
|
this._lastKnownPastTime = 0;
|
|
2463
2599
|
this._droppedEvents = 0;
|
|
@@ -2470,6 +2606,12 @@ class EventProcessor {
|
|
|
2470
2606
|
this._logger = clientContext.basicConfiguration.logger;
|
|
2471
2607
|
this._eventSender = new EventSender(clientContext, baseHeaders);
|
|
2472
2608
|
this._contextFilter = new ContextFilter(_config.allAttributesPrivate, _config.privateAttributes.map((ref) => new AttributeReference(ref)));
|
|
2609
|
+
if (summariesPerContext) {
|
|
2610
|
+
this._summarizer = new MultiEventSummarizer(this._contextFilter, this._logger);
|
|
2611
|
+
}
|
|
2612
|
+
else {
|
|
2613
|
+
this._summarizer = new EventSummarizer();
|
|
2614
|
+
}
|
|
2473
2615
|
if (start) {
|
|
2474
2616
|
this.start();
|
|
2475
2617
|
}
|
|
@@ -2521,10 +2663,19 @@ class EventProcessor {
|
|
|
2521
2663
|
}
|
|
2522
2664
|
const eventsToFlush = this._queue;
|
|
2523
2665
|
this._queue = [];
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2666
|
+
if (isMultiEventSummarizer(this._summarizer)) {
|
|
2667
|
+
const summaries = this._summarizer.getSummaries();
|
|
2668
|
+
summaries.forEach((summary) => {
|
|
2669
|
+
if (Object.keys(summary.features).length) {
|
|
2670
|
+
eventsToFlush.push(summary);
|
|
2671
|
+
}
|
|
2672
|
+
});
|
|
2673
|
+
}
|
|
2674
|
+
else {
|
|
2675
|
+
const summary = this._summarizer.getSummary();
|
|
2676
|
+
if (Object.keys(summary.features).length) {
|
|
2677
|
+
eventsToFlush.push(summary);
|
|
2678
|
+
}
|
|
2528
2679
|
}
|
|
2529
2680
|
if (!eventsToFlush.length) {
|
|
2530
2681
|
return;
|
|
@@ -2835,7 +2986,7 @@ class PayloadProcessor {
|
|
|
2835
2986
|
}
|
|
2836
2987
|
// at the time of writing this, it was agreed upon that SDKs could assume exactly 1 element in this list. In the future, a negotiation of protocol version will be required to remove this assumption.
|
|
2837
2988
|
const payload = data.payloads[0];
|
|
2838
|
-
switch (payload?.
|
|
2989
|
+
switch (payload?.intentCode) {
|
|
2839
2990
|
case 'xfer-full':
|
|
2840
2991
|
this._tempBasis = true;
|
|
2841
2992
|
break;
|
|
@@ -2848,6 +2999,7 @@ class PayloadProcessor {
|
|
|
2848
2999
|
break;
|
|
2849
3000
|
default:
|
|
2850
3001
|
// unrecognized intent code, return
|
|
3002
|
+
this._logger?.warn(`Unable to process intent code '${payload?.intentCode}'.`);
|
|
2851
3003
|
return;
|
|
2852
3004
|
}
|
|
2853
3005
|
this._tempId = payload?.id;
|
|
@@ -3115,6 +3267,7 @@ var index = /*#__PURE__*/Object.freeze({
|
|
|
3115
3267
|
NullEventProcessor: NullEventProcessor,
|
|
3116
3268
|
PayloadProcessor: PayloadProcessor,
|
|
3117
3269
|
PayloadStreamReader: PayloadStreamReader,
|
|
3270
|
+
canonicalize: canonicalize,
|
|
3118
3271
|
initMetadataFromHeaders: initMetadataFromHeaders,
|
|
3119
3272
|
isLegacyUser: isLegacyUser,
|
|
3120
3273
|
isMultiKind: isMultiKind,
|
|
@@ -3139,6 +3292,7 @@ exports.Function = Function;
|
|
|
3139
3292
|
exports.KindValidator = KindValidator;
|
|
3140
3293
|
exports.LDClientError = LDClientError;
|
|
3141
3294
|
exports.LDFileDataSourceError = LDFileDataSourceError;
|
|
3295
|
+
exports.LDFlagDeliveryFallbackError = LDFlagDeliveryFallbackError;
|
|
3142
3296
|
exports.LDPollingError = LDPollingError;
|
|
3143
3297
|
exports.LDStreamingError = LDStreamingError;
|
|
3144
3298
|
exports.LDTimeoutError = LDTimeoutError;
|