@launchdarkly/js-sdk-common 2.17.0 → 2.18.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 +7 -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/subsystem/DataSystem/DataSource.d.ts +1 -1
- package/dist/cjs/index.cjs +129 -9
- package/dist/cjs/index.cjs.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/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/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/subsystem/DataSystem/DataSource.d.ts +1 -1
- package/dist/esm/index.mjs +129 -9
- 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/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/package.json +1 -1
package/dist/esm/index.mjs
CHANGED
|
@@ -115,6 +115,9 @@ class AttributeReference {
|
|
|
115
115
|
return (this.depth === other.depth &&
|
|
116
116
|
this._components.every((value, index) => value === other.getComponent(index)));
|
|
117
117
|
}
|
|
118
|
+
get components() {
|
|
119
|
+
return [...this._components];
|
|
120
|
+
}
|
|
118
121
|
}
|
|
119
122
|
/**
|
|
120
123
|
* For use as invalid references when deserializing Flag/Segment data.
|
|
@@ -314,6 +317,41 @@ function isLegacyUser(context) {
|
|
|
314
317
|
return !('kind' in context) || context.kind === null || context.kind === undefined;
|
|
315
318
|
}
|
|
316
319
|
|
|
320
|
+
/**
|
|
321
|
+
* Given some object to serialize product a canonicalized JSON string.
|
|
322
|
+
* https://www.rfc-editor.org/rfc/rfc8785.html
|
|
323
|
+
*
|
|
324
|
+
* We do not support custom toJSON methods on objects. Objects should be limited to basic types.
|
|
325
|
+
*
|
|
326
|
+
* @param object The object to serialize.
|
|
327
|
+
*/
|
|
328
|
+
function canonicalize(object, visited = []) {
|
|
329
|
+
// For JavaScript the default JSON serialization will produce canonicalized output for basic types.
|
|
330
|
+
if (object === null || typeof object !== 'object') {
|
|
331
|
+
return JSON.stringify(object);
|
|
332
|
+
}
|
|
333
|
+
if (visited.includes(object)) {
|
|
334
|
+
throw new Error('Cycle detected');
|
|
335
|
+
}
|
|
336
|
+
if (Array.isArray(object)) {
|
|
337
|
+
const values = object
|
|
338
|
+
.map((item) => canonicalize(item, [...visited, object]))
|
|
339
|
+
.map((item) => (item === undefined ? 'null' : item));
|
|
340
|
+
return `[${values.join(',')}]`;
|
|
341
|
+
}
|
|
342
|
+
const values = Object.keys(object)
|
|
343
|
+
.sort()
|
|
344
|
+
.map((key) => {
|
|
345
|
+
const value = canonicalize(object[key], [...visited, object]);
|
|
346
|
+
if (value !== undefined) {
|
|
347
|
+
return `${JSON.stringify(key)}:${value}`;
|
|
348
|
+
}
|
|
349
|
+
return undefined;
|
|
350
|
+
})
|
|
351
|
+
.filter((item) => item !== undefined);
|
|
352
|
+
return `{${values.join(',')}}`;
|
|
353
|
+
}
|
|
354
|
+
|
|
317
355
|
// The general strategy for the context is to transform the passed in context
|
|
318
356
|
// as little as possible. We do convert the legacy users to a single kind
|
|
319
357
|
// context, but we do not translate all passed contexts into a rigid structure.
|
|
@@ -688,6 +726,28 @@ class Context {
|
|
|
688
726
|
get legacy() {
|
|
689
727
|
return this._wasLegacy;
|
|
690
728
|
}
|
|
729
|
+
/**
|
|
730
|
+
* Get the serialized canonical JSON for this context. This is not filtered for use in events.
|
|
731
|
+
*
|
|
732
|
+
* This method will cache the result.
|
|
733
|
+
*
|
|
734
|
+
* @returns The serialized canonical JSON or undefined if it cannot be serialized.
|
|
735
|
+
*/
|
|
736
|
+
canonicalUnfilteredJson() {
|
|
737
|
+
if (!this.valid) {
|
|
738
|
+
return undefined;
|
|
739
|
+
}
|
|
740
|
+
if (this._cachedCanonicalJson) {
|
|
741
|
+
return this._cachedCanonicalJson;
|
|
742
|
+
}
|
|
743
|
+
try {
|
|
744
|
+
this._cachedCanonicalJson = canonicalize(Context.toLDContext(this));
|
|
745
|
+
}
|
|
746
|
+
catch {
|
|
747
|
+
// Indicated by undefined being returned.
|
|
748
|
+
}
|
|
749
|
+
return this._cachedCanonicalJson;
|
|
750
|
+
}
|
|
691
751
|
}
|
|
692
752
|
Context.UserKind = DEFAULT_KIND;
|
|
693
753
|
|
|
@@ -2351,7 +2411,9 @@ function counterKey(event) {
|
|
|
2351
2411
|
* @internal
|
|
2352
2412
|
*/
|
|
2353
2413
|
class EventSummarizer {
|
|
2354
|
-
constructor() {
|
|
2414
|
+
constructor(_singleContext = false, _contextFilter) {
|
|
2415
|
+
this._singleContext = _singleContext;
|
|
2416
|
+
this._contextFilter = _contextFilter;
|
|
2355
2417
|
this._startDate = 0;
|
|
2356
2418
|
this._endDate = 0;
|
|
2357
2419
|
this._counters = {};
|
|
@@ -2359,6 +2421,9 @@ class EventSummarizer {
|
|
|
2359
2421
|
}
|
|
2360
2422
|
summarizeEvent(event) {
|
|
2361
2423
|
if (isFeature(event) && !event.excludeFromSummaries) {
|
|
2424
|
+
if (!this._context) {
|
|
2425
|
+
this._context = event.context;
|
|
2426
|
+
}
|
|
2362
2427
|
const countKey = counterKey(event);
|
|
2363
2428
|
const counter = this._counters[countKey];
|
|
2364
2429
|
let kinds = this._contextKinds[event.key];
|
|
@@ -2408,14 +2473,19 @@ class EventSummarizer {
|
|
|
2408
2473
|
flagSummary.counters.push(counterOut);
|
|
2409
2474
|
return acc;
|
|
2410
2475
|
}, {});
|
|
2411
|
-
|
|
2476
|
+
const event = {
|
|
2412
2477
|
startDate: this._startDate,
|
|
2413
2478
|
endDate: this._endDate,
|
|
2414
2479
|
features,
|
|
2415
2480
|
kind: 'summary',
|
|
2481
|
+
context: this._context !== undefined && this._singleContext
|
|
2482
|
+
? this._contextFilter?.filter(this._context)
|
|
2483
|
+
: undefined,
|
|
2416
2484
|
};
|
|
2485
|
+
this._clearSummary();
|
|
2486
|
+
return event;
|
|
2417
2487
|
}
|
|
2418
|
-
|
|
2488
|
+
_clearSummary() {
|
|
2419
2489
|
this._startDate = 0;
|
|
2420
2490
|
this._endDate = 0;
|
|
2421
2491
|
this._counters = {};
|
|
@@ -2430,6 +2500,38 @@ class LDInvalidSDKKeyError extends Error {
|
|
|
2430
2500
|
}
|
|
2431
2501
|
}
|
|
2432
2502
|
|
|
2503
|
+
class MultiEventSummarizer {
|
|
2504
|
+
constructor(_contextFilter, _logger) {
|
|
2505
|
+
this._contextFilter = _contextFilter;
|
|
2506
|
+
this._logger = _logger;
|
|
2507
|
+
this._summarizers = {};
|
|
2508
|
+
}
|
|
2509
|
+
summarizeEvent(event) {
|
|
2510
|
+
if (isFeature(event)) {
|
|
2511
|
+
const key = event.context.canonicalUnfilteredJson();
|
|
2512
|
+
if (!key) {
|
|
2513
|
+
if (event.context.valid) {
|
|
2514
|
+
// The context appeared valid, but it could not be hashed.
|
|
2515
|
+
// This is likely because of a cycle in the data.
|
|
2516
|
+
this._logger?.error('Unable to serialize context, likely the context contains a cycle.');
|
|
2517
|
+
}
|
|
2518
|
+
return;
|
|
2519
|
+
}
|
|
2520
|
+
let summarizer = this._summarizers[key];
|
|
2521
|
+
if (!summarizer) {
|
|
2522
|
+
this._summarizers[key] = new EventSummarizer(true, this._contextFilter);
|
|
2523
|
+
summarizer = this._summarizers[key];
|
|
2524
|
+
}
|
|
2525
|
+
summarizer.summarizeEvent(event);
|
|
2526
|
+
}
|
|
2527
|
+
}
|
|
2528
|
+
getSummaries() {
|
|
2529
|
+
const summarizersToFlush = this._summarizers;
|
|
2530
|
+
this._summarizers = {};
|
|
2531
|
+
return Object.values(summarizersToFlush).map((summarizer) => summarizer.getSummary());
|
|
2532
|
+
}
|
|
2533
|
+
}
|
|
2534
|
+
|
|
2433
2535
|
/**
|
|
2434
2536
|
* The contents of this file are for event sampling. They are not used for
|
|
2435
2537
|
* any purpose requiring cryptographic security.
|
|
@@ -2450,12 +2552,14 @@ function shouldSample(ratio) {
|
|
|
2450
2552
|
return Math.floor(Math.random() * truncated) === 0;
|
|
2451
2553
|
}
|
|
2452
2554
|
|
|
2555
|
+
function isMultiEventSummarizer(summarizer) {
|
|
2556
|
+
return summarizer.getSummaries !== undefined;
|
|
2557
|
+
}
|
|
2453
2558
|
class EventProcessor {
|
|
2454
|
-
constructor(_config, clientContext, baseHeaders, _contextDeduplicator, _diagnosticsManager, start = true) {
|
|
2559
|
+
constructor(_config, clientContext, baseHeaders, _contextDeduplicator, _diagnosticsManager, start = true, summariesPerContext = false) {
|
|
2455
2560
|
this._config = _config;
|
|
2456
2561
|
this._contextDeduplicator = _contextDeduplicator;
|
|
2457
2562
|
this._diagnosticsManager = _diagnosticsManager;
|
|
2458
|
-
this._summarizer = new EventSummarizer();
|
|
2459
2563
|
this._queue = [];
|
|
2460
2564
|
this._lastKnownPastTime = 0;
|
|
2461
2565
|
this._droppedEvents = 0;
|
|
@@ -2468,6 +2572,12 @@ class EventProcessor {
|
|
|
2468
2572
|
this._logger = clientContext.basicConfiguration.logger;
|
|
2469
2573
|
this._eventSender = new EventSender(clientContext, baseHeaders);
|
|
2470
2574
|
this._contextFilter = new ContextFilter(_config.allAttributesPrivate, _config.privateAttributes.map((ref) => new AttributeReference(ref)));
|
|
2575
|
+
if (summariesPerContext) {
|
|
2576
|
+
this._summarizer = new MultiEventSummarizer(this._contextFilter, this._logger);
|
|
2577
|
+
}
|
|
2578
|
+
else {
|
|
2579
|
+
this._summarizer = new EventSummarizer();
|
|
2580
|
+
}
|
|
2471
2581
|
if (start) {
|
|
2472
2582
|
this.start();
|
|
2473
2583
|
}
|
|
@@ -2519,10 +2629,19 @@ class EventProcessor {
|
|
|
2519
2629
|
}
|
|
2520
2630
|
const eventsToFlush = this._queue;
|
|
2521
2631
|
this._queue = [];
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2632
|
+
if (isMultiEventSummarizer(this._summarizer)) {
|
|
2633
|
+
const summaries = this._summarizer.getSummaries();
|
|
2634
|
+
summaries.forEach((summary) => {
|
|
2635
|
+
if (Object.keys(summary.features).length) {
|
|
2636
|
+
eventsToFlush.push(summary);
|
|
2637
|
+
}
|
|
2638
|
+
});
|
|
2639
|
+
}
|
|
2640
|
+
else {
|
|
2641
|
+
const summary = this._summarizer.getSummary();
|
|
2642
|
+
if (Object.keys(summary.features).length) {
|
|
2643
|
+
eventsToFlush.push(summary);
|
|
2644
|
+
}
|
|
2526
2645
|
}
|
|
2527
2646
|
if (!eventsToFlush.length) {
|
|
2528
2647
|
return;
|
|
@@ -3113,6 +3232,7 @@ var index = /*#__PURE__*/Object.freeze({
|
|
|
3113
3232
|
NullEventProcessor: NullEventProcessor,
|
|
3114
3233
|
PayloadProcessor: PayloadProcessor,
|
|
3115
3234
|
PayloadStreamReader: PayloadStreamReader,
|
|
3235
|
+
canonicalize: canonicalize,
|
|
3116
3236
|
initMetadataFromHeaders: initMetadataFromHeaders,
|
|
3117
3237
|
isLegacyUser: isLegacyUser,
|
|
3118
3238
|
isMultiKind: isMultiKind,
|