@splitsoftware/splitio-commons 1.6.2-rc.12 → 1.6.2-rc.14

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.
Files changed (95) hide show
  1. package/cjs/listeners/browser.js +9 -11
  2. package/cjs/sdkFactory/index.js +7 -24
  3. package/cjs/storages/KeyBuilderSS.js +4 -43
  4. package/cjs/storages/inLocalStorage/index.js +14 -10
  5. package/cjs/storages/inMemory/InMemoryStorage.js +10 -7
  6. package/cjs/storages/inMemory/InMemoryStorageCS.js +10 -7
  7. package/cjs/storages/inMemory/TelemetryCacheInMemory.js +57 -34
  8. package/cjs/storages/inRedis/ImpressionCountsCacheInRedis.js +3 -3
  9. package/cjs/storages/inRedis/ImpressionsCacheInRedis.js +2 -19
  10. package/cjs/storages/inRedis/TelemetryCacheInRedis.js +4 -4
  11. package/cjs/storages/inRedis/index.js +4 -2
  12. package/cjs/storages/pluggable/ImpressionCountsCachePluggable.js +3 -3
  13. package/cjs/storages/pluggable/ImpressionsCachePluggable.js +2 -19
  14. package/cjs/storages/pluggable/TelemetryCachePluggable.js +4 -4
  15. package/cjs/storages/pluggable/inMemoryWrapper.js +8 -6
  16. package/cjs/storages/pluggable/index.js +4 -2
  17. package/cjs/storages/utils.js +73 -0
  18. package/cjs/sync/submitters/submitterManager.js +1 -1
  19. package/cjs/sync/submitters/telemetrySubmitter.js +4 -40
  20. package/cjs/trackers/impressionObserver/utils.js +1 -17
  21. package/cjs/trackers/uniqueKeysTracker.js +1 -1
  22. package/cjs/utils/settingsValidation/index.js +7 -1
  23. package/esm/listeners/browser.js +9 -11
  24. package/esm/sdkFactory/index.js +7 -24
  25. package/esm/storages/KeyBuilderSS.js +1 -37
  26. package/esm/storages/inLocalStorage/index.js +15 -11
  27. package/esm/storages/inMemory/InMemoryStorage.js +10 -7
  28. package/esm/storages/inMemory/InMemoryStorageCS.js +10 -7
  29. package/esm/storages/inMemory/TelemetryCacheInMemory.js +58 -35
  30. package/esm/storages/inRedis/ImpressionCountsCacheInRedis.js +3 -3
  31. package/esm/storages/inRedis/ImpressionsCacheInRedis.js +2 -19
  32. package/esm/storages/inRedis/TelemetryCacheInRedis.js +1 -1
  33. package/esm/storages/inRedis/index.js +4 -2
  34. package/esm/storages/pluggable/ImpressionCountsCachePluggable.js +3 -3
  35. package/esm/storages/pluggable/ImpressionsCachePluggable.js +2 -19
  36. package/esm/storages/pluggable/TelemetryCachePluggable.js +1 -1
  37. package/esm/storages/pluggable/inMemoryWrapper.js +8 -6
  38. package/esm/storages/pluggable/index.js +4 -2
  39. package/esm/storages/utils.js +65 -0
  40. package/esm/sync/submitters/submitterManager.js +1 -1
  41. package/esm/sync/submitters/telemetrySubmitter.js +4 -39
  42. package/esm/trackers/impressionObserver/utils.js +1 -15
  43. package/esm/trackers/uniqueKeysTracker.js +1 -1
  44. package/esm/utils/settingsValidation/index.js +7 -1
  45. package/package.json +2 -1
  46. package/src/listeners/browser.ts +9 -13
  47. package/src/sdkClient/sdkClient.ts +1 -1
  48. package/src/sdkFactory/index.ts +7 -29
  49. package/src/sdkFactory/types.ts +2 -2
  50. package/src/services/splitApi.ts +2 -2
  51. package/src/storages/KeyBuilderSS.ts +2 -44
  52. package/src/storages/inLocalStorage/index.ts +16 -11
  53. package/src/storages/inMemory/AttributesCacheInMemory.ts +7 -7
  54. package/src/storages/inMemory/InMemoryStorage.ts +11 -7
  55. package/src/storages/inMemory/InMemoryStorageCS.ts +11 -7
  56. package/src/storages/inMemory/TelemetryCacheInMemory.ts +66 -33
  57. package/src/storages/inRedis/ImpressionCountsCacheInRedis.ts +3 -3
  58. package/src/storages/inRedis/ImpressionsCacheInRedis.ts +2 -22
  59. package/src/storages/inRedis/TelemetryCacheInRedis.ts +3 -2
  60. package/src/storages/inRedis/index.ts +4 -1
  61. package/src/storages/pluggable/ImpressionCountsCachePluggable.ts +3 -3
  62. package/src/storages/pluggable/ImpressionsCachePluggable.ts +3 -23
  63. package/src/storages/pluggable/TelemetryCachePluggable.ts +3 -2
  64. package/src/storages/pluggable/inMemoryWrapper.ts +6 -6
  65. package/src/storages/pluggable/index.ts +4 -2
  66. package/src/storages/types.ts +45 -64
  67. package/src/storages/utils.ts +78 -0
  68. package/src/sync/submitters/submitter.ts +2 -2
  69. package/src/sync/submitters/submitterManager.ts +1 -1
  70. package/src/sync/submitters/telemetrySubmitter.ts +5 -41
  71. package/src/sync/submitters/types.ts +7 -5
  72. package/src/trackers/impressionObserver/utils.ts +1 -16
  73. package/src/trackers/impressionsTracker.ts +2 -2
  74. package/src/trackers/strategy/strategyDebug.ts +4 -4
  75. package/src/trackers/strategy/strategyNone.ts +9 -9
  76. package/src/trackers/strategy/strategyOptimized.ts +9 -9
  77. package/src/trackers/uniqueKeysTracker.ts +6 -6
  78. package/src/utils/redis/RedisMock.ts +5 -5
  79. package/src/utils/settingsValidation/index.ts +5 -1
  80. package/types/storages/KeyBuilderSS.d.ts +1 -3
  81. package/types/storages/inMemory/TelemetryCacheInMemory.d.ts +19 -8
  82. package/types/storages/inRedis/ImpressionsCacheInRedis.d.ts +0 -1
  83. package/types/storages/pluggable/ImpressionsCachePluggable.d.ts +1 -2
  84. package/types/storages/types.d.ts +35 -45
  85. package/types/storages/utils.d.ts +8 -0
  86. package/types/sync/submitters/submitter.d.ts +2 -2
  87. package/types/sync/submitters/telemetrySubmitter.d.ts +2 -10
  88. package/types/sync/submitters/types.d.ts +8 -6
  89. package/types/trackers/impressionObserver/utils.d.ts +0 -8
  90. package/types/trackers/strategy/strategyNone.d.ts +2 -2
  91. package/types/trackers/strategy/strategyOptimized.d.ts +2 -2
  92. package/types/trackers/uniqueKeysTracker.d.ts +1 -1
  93. package/cjs/storages/metadataBuilder.js +0 -12
  94. package/esm/storages/metadataBuilder.js +0 -8
  95. package/src/storages/metadataBuilder.ts +0 -11
@@ -1,7 +1,7 @@
1
1
  /* eslint-disable no-undef */
2
2
  // @TODO eventually migrate to JS-Browser-SDK package.
3
3
  import { ISignalListener } from './types';
4
- import { IRecorderCacheProducerSync, IStorageSync } from '../storages/types';
4
+ import { IRecorderCacheSync, IStorageSync } from '../storages/types';
5
5
  import { fromImpressionsCollector } from '../sync/submitters/impressionsSubmitter';
6
6
  import { fromImpressionCountsCollector } from '../sync/submitters/impressionCountsSubmitter';
7
7
  import { IResponse, ISplitApi } from '../services/types';
@@ -12,7 +12,6 @@ import { objectAssign } from '../utils/lang/objectAssign';
12
12
  import { CLEANUP_REGISTERING, CLEANUP_DEREGISTERING } from '../logger/constants';
13
13
  import { ISyncManager } from '../sync/types';
14
14
  import { isConsentGranted } from '../consent';
15
- import { telemetryCacheStatsAdapter } from '../sync/submitters/telemetrySubmitter';
16
15
 
17
16
  const VISIBILITYCHANGE_EVENT = 'visibilitychange';
18
17
  const PAGEHIDE_EVENT = 'pagehide';
@@ -84,27 +83,25 @@ export class BrowserSignalListener implements ISignalListener {
84
83
  */
85
84
  flushData() {
86
85
  if (!this.syncManager) return; // In consumer mode there is not sync manager and data to flush
86
+ const { events, telemetry } = this.settings.urls;
87
87
 
88
88
  // Flush impressions & events data if there is user consent
89
89
  if (isConsentGranted(this.settings)) {
90
- const eventsUrl = this.settings.urls.events;
91
90
  const sim = this.settings.sync.impressionsMode;
92
91
  const extraMetadata = {
93
92
  // sim stands for Sync/Split Impressions Mode
94
93
  sim: sim === OPTIMIZED ? OPTIMIZED : sim === DEBUG ? DEBUG : NONE
95
94
  };
96
95
 
97
- this._flushData(eventsUrl + '/testImpressions/beacon', this.storage.impressions, this.serviceApi.postTestImpressionsBulk, this.fromImpressionsCollector, extraMetadata);
98
- this._flushData(eventsUrl + '/events/beacon', this.storage.events, this.serviceApi.postEventsBulk);
99
- if (this.storage.impressionCounts) this._flushData(eventsUrl + '/testImpressions/count/beacon', this.storage.impressionCounts, this.serviceApi.postTestImpressionsCount, fromImpressionCountsCollector);
96
+ this._flushData(events + '/testImpressions/beacon', this.storage.impressions, this.serviceApi.postTestImpressionsBulk, this.fromImpressionsCollector, extraMetadata);
97
+ this._flushData(events + '/events/beacon', this.storage.events, this.serviceApi.postEventsBulk);
98
+ if (this.storage.impressionCounts) this._flushData(events + '/testImpressions/count/beacon', this.storage.impressionCounts, this.serviceApi.postTestImpressionsCount, fromImpressionCountsCollector);
99
+ // @ts-ignore
100
+ if (this.storage.uniqueKeys) this._flushData(telemetry + '/v1/keys/cs/beacon', this.storage.uniqueKeys, this.serviceApi.postUniqueKeysBulkCs);
100
101
  }
101
102
 
102
103
  // Flush telemetry data
103
- if (this.storage.telemetry) {
104
- const telemetryUrl = this.settings.urls.telemetry;
105
- const telemetryCacheAdapter = telemetryCacheStatsAdapter(this.storage.telemetry, this.storage.splits, this.storage.segments);
106
- this._flushData(telemetryUrl + '/v1/metrics/usage/beacon', telemetryCacheAdapter, this.serviceApi.postMetricsUsage);
107
- }
104
+ if (this.storage.telemetry) this._flushData(telemetry + '/v1/metrics/usage/beacon', this.storage.telemetry, this.serviceApi.postMetricsUsage);
108
105
  }
109
106
 
110
107
  flushDataIfHidden() {
@@ -112,14 +109,13 @@ export class BrowserSignalListener implements ISignalListener {
112
109
  if (document.visibilityState === 'hidden') this.flushData(); // On a 'visibilitychange' event, flush data if state is hidden
113
110
  }
114
111
 
115
- private _flushData<T>(url: string, cache: IRecorderCacheProducerSync<T>, postService: (body: string) => Promise<IResponse>, fromCacheToPayload?: (cacheData: T) => any, extraMetadata?: {}) {
112
+ private _flushData<T>(url: string, cache: IRecorderCacheSync<T>, postService: (body: string) => Promise<IResponse>, fromCacheToPayload?: (cacheData: T) => any, extraMetadata?: {}) {
116
113
  // if there is data in cache, send it to backend
117
114
  if (!cache.isEmpty()) {
118
115
  const dataPayload = fromCacheToPayload ? fromCacheToPayload(cache.pop()) : cache.pop();
119
116
  if (!this._sendBeacon(url, dataPayload, extraMetadata)) {
120
117
  postService(JSON.stringify(dataPayload)).catch(() => { }); // no-op just to catch a possible exception
121
118
  }
122
- cache.clear();
123
119
  }
124
120
  }
125
121
 
@@ -39,7 +39,7 @@ export function sdkClientFactory(params: ISdkFactoryContext, isSharedClient?: bo
39
39
 
40
40
  // Release the API Key if it is the main client
41
41
  if (!isSharedClient) releaseApiKey(settings.core.authorizationKey);
42
-
42
+
43
43
  if (uniqueKeysTracker) uniqueKeysTracker.stop();
44
44
 
45
45
  // Cleanup storage
@@ -3,14 +3,10 @@ import { sdkReadinessManagerFactory } from '../readiness/sdkReadinessManager';
3
3
  import { impressionsTrackerFactory } from '../trackers/impressionsTracker';
4
4
  import { eventTrackerFactory } from '../trackers/eventTracker';
5
5
  import { telemetryTrackerFactory } from '../trackers/telemetryTracker';
6
- import { IStorageFactoryParams } from '../storages/types';
7
6
  import { SplitIO } from '../types';
8
- import { getMatching } from '../utils/key';
9
- import { shouldBeOptimized } from '../trackers/impressionObserver/utils';
10
7
  import { validateAndTrackApiKey } from '../utils/inputValidation/apiKey';
11
8
  import { createLoggerAPI } from '../logger/sdkLogger';
12
9
  import { NEW_FACTORY, RETRIEVE_MANAGER } from '../logger/constants';
13
- import { metadataBuilder } from '../storages/metadataBuilder';
14
10
  import { SDK_SPLITS_ARRIVED, SDK_SEGMENTS_ARRIVED } from '../readiness/constants';
15
11
  import { objectAssign } from '../utils/lang/objectAssign';
16
12
  import { strategyDebugFactory } from '../trackers/strategy/strategyDebug';
@@ -28,7 +24,7 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ICsSDK | SplitIO.
28
24
  syncManagerFactory, SignalListener, impressionsObserverFactory,
29
25
  integrationsManagerFactory, sdkManagerFactory, sdkClientMethodFactory,
30
26
  filterAdapterFactory } = params;
31
- const log = settings.log;
27
+ const { log, sync: { impressionsMode } } = settings;
32
28
 
33
29
  // @TODO handle non-recoverable errors, such as, global `fetch` not available, invalid API Key, etc.
34
30
  // On non-recoverable errors, we should mark the SDK as destroyed and not start synchronization.
@@ -39,42 +35,24 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ICsSDK | SplitIO.
39
35
  const sdkReadinessManager = sdkReadinessManagerFactory(log, platform.EventEmitter, settings.startup.readyTimeout);
40
36
  const readiness = sdkReadinessManager.readinessManager;
41
37
 
42
- // @TODO consider passing the settings object, so that each storage access only what it needs
43
- const storageFactoryParams: IStorageFactoryParams = {
44
- impressionsQueueSize: settings.scheduler.impressionsQueueSize,
45
- eventsQueueSize: settings.scheduler.eventsQueueSize,
46
- optimize: shouldBeOptimized(settings),
47
-
48
- // ATM, only used by InLocalStorage
49
- matchingKey: getMatching(settings.core.key),
50
- splitFiltersValidation: settings.sync.__splitFiltersValidation,
51
-
52
- // ATM, only used by PluggableStorage
53
- mode: settings.mode,
54
- impressionsMode: settings.sync.impressionsMode,
55
-
56
- // Callback used to emit SDK_READY in consumer mode, where `syncManagerFactory` is undefined,
57
- // or partial consumer mode, where it only has submitters, and therefore it doesn't emit readiness events.
38
+ const storage = storageFactory({
39
+ settings,
58
40
  onReadyCb: (error) => {
59
41
  if (error) return; // Don't emit SDK_READY if storage failed to connect. Error message is logged by wrapperAdapter
60
42
  readiness.splits.emit(SDK_SPLITS_ARRIVED);
61
43
  readiness.segments.emit(SDK_SEGMENTS_ARRIVED);
62
44
  },
63
- metadata: metadataBuilder(settings),
64
- log
65
- };
66
-
67
- const storage = storageFactory(storageFactoryParams);
45
+ });
68
46
  // @TODO add support for dataloader: `if (params.dataLoader) params.dataLoader(storage);`
69
47
 
70
48
  const telemetryTracker = telemetryTrackerFactory(storage.telemetry, platform.now);
71
49
  const integrationsManager = integrationsManagerFactory && integrationsManagerFactory({ settings, storage, telemetryTracker });
72
50
 
73
51
  const observer = impressionsObserverFactory();
74
- const uniqueKeysTracker = storageFactoryParams.impressionsMode === NONE ? uniqueKeysTrackerFactory(log, storage.uniqueKeys!, filterAdapterFactory && filterAdapterFactory()) : undefined;
52
+ const uniqueKeysTracker = impressionsMode === NONE ? uniqueKeysTrackerFactory(log, storage.uniqueKeys!, filterAdapterFactory && filterAdapterFactory()) : undefined;
75
53
 
76
54
  let strategy;
77
- switch (storageFactoryParams.impressionsMode) {
55
+ switch (impressionsMode) {
78
56
  case OPTIMIZED:
79
57
  strategy = strategyOptimizedFactory(observer, storage.impressionCounts!);
80
58
  break;
@@ -120,7 +98,7 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ICsSDK | SplitIO.
120
98
  },
121
99
 
122
100
  // Logger wrapper API
123
- Logger: createLoggerAPI(settings.log),
101
+ Logger: createLoggerAPI(log),
124
102
 
125
103
  settings,
126
104
  }, extraProps && extraProps(ctx));
@@ -97,9 +97,9 @@ export interface ISdkFactoryParams {
97
97
  // It Allows to distinguish SDK clients with the client-side API (`ICsSDK`) or server-side API (`ISDK` or `IAsyncSDK`).
98
98
  sdkClientMethodFactory: (params: ISdkFactoryContext) => ({ (): SplitIO.ICsClient; (key: SplitIO.SplitKey, trafficType?: string | undefined): SplitIO.ICsClient; } | (() => SplitIO.IClient) | (() => SplitIO.IAsyncClient))
99
99
 
100
- // Impression observer factory. If provided, will be used for impressions dedupe
100
+ // Impression observer factory.
101
101
  impressionsObserverFactory: () => IImpressionObserver
102
-
102
+
103
103
  filterAdapterFactory?: () => IFilterAdapter
104
104
 
105
105
  // Optional signal listener constructor. Used to handle special app states, like shutdown, app paused or resumed.
@@ -106,7 +106,7 @@ export function splitApiFactory(
106
106
  const url = `${urls.events}/testImpressions/count`;
107
107
  return splitHttpClient(url, { method: 'POST', body, headers }, telemetryTracker.trackHttp(IMPRESSIONS_COUNT));
108
108
  },
109
-
109
+
110
110
  /**
111
111
  * Post unique keys for client side.
112
112
  *
@@ -117,7 +117,7 @@ export function splitApiFactory(
117
117
  const url = `${urls.telemetry}/v1/keys/cs`;
118
118
  return splitHttpClient(url, { method: 'POST', body, headers }, telemetryTracker.trackHttp(TELEMETRY));
119
119
  },
120
-
120
+
121
121
  /**
122
122
  * Post unique keys for server side.
123
123
  *
@@ -1,9 +1,8 @@
1
1
  import { KeyBuilder } from './KeyBuilder';
2
2
  import { IMetadata } from '../dtos/types';
3
3
  import { Method } from '../sync/submitters/types';
4
- import { MAX_LATENCY_BUCKET_COUNT } from './inMemory/TelemetryCacheInMemory';
5
4
 
6
- const METHOD_NAMES: Record<Method, string> = {
5
+ export const METHOD_NAMES: Record<Method, string> = {
7
6
  t: 'treatment',
8
7
  ts: 'treatments',
9
8
  tc: 'treatmentWithConfig',
@@ -37,7 +36,7 @@ export class KeyBuilderSS extends KeyBuilder {
37
36
  buildImpressionsCountKey() {
38
37
  return `${this.prefix}.impressions.count`;
39
38
  }
40
-
39
+
41
40
  buildUniqueKeysKey() {
42
41
  return `${this.prefix}.uniquekeys`;
43
42
  }
@@ -65,44 +64,3 @@ export class KeyBuilderSS extends KeyBuilder {
65
64
  }
66
65
 
67
66
  }
68
-
69
- // Used by consumer methods of TelemetryCacheInRedis and TelemetryCachePluggable
70
-
71
- const REVERSE_METHOD_NAMES = Object.keys(METHOD_NAMES).reduce((acc, key) => {
72
- acc[METHOD_NAMES[key as Method]] = key as Method;
73
- return acc;
74
- }, {} as Record<string, Method>);
75
-
76
-
77
- export function parseMetadata(field: string): [metadata: string] | string {
78
- const parts = field.split('/');
79
- if (parts.length !== 3) return `invalid subsection count. Expected 3, got: ${parts.length}`;
80
-
81
- const [s /* metadata.s */, n /* metadata.n */, i /* metadata.i */] = parts;
82
- return [JSON.stringify({ s, n, i })];
83
- }
84
-
85
- export function parseExceptionField(field: string): [metadata: string, method: Method] | string {
86
- const parts = field.split('/');
87
- if (parts.length !== 4) return `invalid subsection count. Expected 4, got: ${parts.length}`;
88
-
89
- const [s /* metadata.s */, n /* metadata.n */, i /* metadata.i */, m] = parts;
90
- const method = REVERSE_METHOD_NAMES[m];
91
- if (!method) return `unknown method '${m}'`;
92
-
93
- return [JSON.stringify({ s, n, i }), method];
94
- }
95
-
96
- export function parseLatencyField(field: string): [metadata: string, method: Method, bucket: number] | string {
97
- const parts = field.split('/');
98
- if (parts.length !== 5) return `invalid subsection count. Expected 5, got: ${parts.length}`;
99
-
100
- const [s /* metadata.s */, n /* metadata.n */, i /* metadata.i */, m, b] = parts;
101
- const method = REVERSE_METHOD_NAMES[m];
102
- if (!method) return `unknown method '${m}'`;
103
-
104
- const bucket = parseInt(b);
105
- if (isNaN(bucket) || bucket >= MAX_LATENCY_BUCKET_COUNT) return `invalid bucket. Expected a number between 0 and ${MAX_LATENCY_BUCKET_COUNT - 1}, got: ${b}`;
106
-
107
- return [JSON.stringify({ s, n, i }), method, bucket];
108
- }
@@ -12,9 +12,10 @@ import { SplitsCacheInMemory } from '../inMemory/SplitsCacheInMemory';
12
12
  import { DEFAULT_CACHE_EXPIRATION_IN_MILLIS } from '../../utils/constants/browser';
13
13
  import { InMemoryStorageCSFactory } from '../inMemory/InMemoryStorageCS';
14
14
  import { LOG_PREFIX } from './constants';
15
- import { NONE, STORAGE_LOCALSTORAGE } from '../../utils/constants';
15
+ import { DEBUG, NONE, STORAGE_LOCALSTORAGE } from '../../utils/constants';
16
16
  import { shouldRecordTelemetry, TelemetryCacheInMemory } from '../inMemory/TelemetryCacheInMemory';
17
17
  import { UniqueKeysCacheInMemoryCS } from '../inMemory/UniqueKeysCacheInMemoryCS';
18
+ import { getMatching } from '../../utils/key';
18
19
 
19
20
  export interface InLocalStorageOptions {
20
21
  prefix?: string
@@ -31,22 +32,26 @@ export function InLocalStorage(options: InLocalStorageOptions = {}): IStorageSyn
31
32
 
32
33
  // Fallback to InMemoryStorage if LocalStorage API is not available
33
34
  if (!isLocalStorageAvailable()) {
34
- params.log.warn(LOG_PREFIX + 'LocalStorage API is unavailable. Falling back to default MEMORY storage');
35
+ params.settings.log.warn(LOG_PREFIX + 'LocalStorage API is unavailable. Falling back to default MEMORY storage');
35
36
  return InMemoryStorageCSFactory(params);
36
37
  }
37
38
 
38
- const log = params.log;
39
- const keys = new KeyBuilderCS(prefix, params.matchingKey as string);
39
+ const { settings, settings: { log, scheduler: { impressionsQueueSize, eventsQueueSize, }, sync: { impressionsMode, __splitFiltersValidation } } } = params;
40
+ const matchingKey = getMatching(settings.core.key);
41
+ const keys = new KeyBuilderCS(prefix, matchingKey as string);
40
42
  const expirationTimestamp = Date.now() - DEFAULT_CACHE_EXPIRATION_IN_MILLIS;
41
43
 
44
+ const splits = new SplitsCacheInLocal(log, keys, expirationTimestamp, __splitFiltersValidation);
45
+ const segments = new MySegmentsCacheInLocal(log, keys);
46
+
42
47
  return {
43
- splits: new SplitsCacheInLocal(log, keys, expirationTimestamp, params.splitFiltersValidation),
44
- segments: new MySegmentsCacheInLocal(log, keys),
45
- impressions: new ImpressionsCacheInMemory(params.impressionsQueueSize),
46
- impressionCounts: params.optimize ? new ImpressionCountsCacheInMemory() : undefined,
47
- events: new EventsCacheInMemory(params.eventsQueueSize),
48
- telemetry: shouldRecordTelemetry(params) ? new TelemetryCacheInMemory() : undefined,
49
- uniqueKeys: params.impressionsMode === NONE ? new UniqueKeysCacheInMemoryCS() : undefined,
48
+ splits,
49
+ segments,
50
+ impressions: new ImpressionsCacheInMemory(impressionsQueueSize),
51
+ impressionCounts: impressionsMode !== DEBUG ? new ImpressionCountsCacheInMemory() : undefined,
52
+ events: new EventsCacheInMemory(eventsQueueSize),
53
+ telemetry: shouldRecordTelemetry(params) ? new TelemetryCacheInMemory(splits, segments) : undefined,
54
+ uniqueKeys: impressionsMode === NONE ? new UniqueKeysCacheInMemoryCS() : undefined,
50
55
 
51
56
  destroy() {
52
57
  this.splits = new SplitsCacheInMemory();
@@ -7,10 +7,10 @@ export class AttributesCacheInMemory {
7
7
 
8
8
  /**
9
9
  * Create or update the value for the given attribute
10
- *
10
+ *
11
11
  * @param {string} attributeName attribute name
12
12
  * @param {Object} attributeValue attribute value
13
- * @returns {boolean} the attribute was stored
13
+ * @returns {boolean} the attribute was stored
14
14
  */
15
15
  setAttribute(attributeName: string, attributeValue: Object): boolean {
16
16
  this.attributesCache[attributeName] = attributeValue;
@@ -19,7 +19,7 @@ export class AttributesCacheInMemory {
19
19
 
20
20
  /**
21
21
  * Retrieves the value of a given attribute
22
- *
22
+ *
23
23
  * @param {string} attributeName attribute name
24
24
  * @returns {Object?} stored attribute value
25
25
  */
@@ -29,7 +29,7 @@ export class AttributesCacheInMemory {
29
29
 
30
30
  /**
31
31
  * Create or update all the given attributes
32
- *
32
+ *
33
33
  * @param {[string, Object]} attributes attributes to create or update
34
34
  * @returns {boolean} attributes were stored
35
35
  */
@@ -40,7 +40,7 @@ export class AttributesCacheInMemory {
40
40
 
41
41
  /**
42
42
  * Retrieve the full attributes map
43
- *
43
+ *
44
44
  * @returns {Map<string, Object>} stored attributes
45
45
  */
46
46
  getAll(): Record<string, Object> {
@@ -49,7 +49,7 @@ export class AttributesCacheInMemory {
49
49
 
50
50
  /**
51
51
  * Removes a given attribute from the map
52
- *
52
+ *
53
53
  * @param {string} attributeName attribute to remove
54
54
  * @returns {boolean} attribute removed
55
55
  */
@@ -63,7 +63,7 @@ export class AttributesCacheInMemory {
63
63
 
64
64
  /**
65
65
  * Clears all attributes stored in the SDK
66
- *
66
+ *
67
67
  */
68
68
  clear() {
69
69
  this.attributesCache = {};
@@ -14,15 +14,19 @@ import { UniqueKeysCacheInMemory } from './UniqueKeysCacheInMemory';
14
14
  * @param params parameters required by EventsCacheSync
15
15
  */
16
16
  export function InMemoryStorageFactory(params: IStorageFactoryParams): IStorageSync {
17
+ const { settings: { scheduler: { impressionsQueueSize, eventsQueueSize, }, sync: { impressionsMode } } } = params;
18
+
19
+ const splits = new SplitsCacheInMemory();
20
+ const segments = new SegmentsCacheInMemory();
17
21
 
18
22
  return {
19
- splits: new SplitsCacheInMemory(),
20
- segments: new SegmentsCacheInMemory(),
21
- impressions: new ImpressionsCacheInMemory(params.impressionsQueueSize),
22
- impressionCounts: params.impressionsMode !== DEBUG ? new ImpressionCountsCacheInMemory() : undefined,
23
- events: new EventsCacheInMemory(params.eventsQueueSize),
24
- telemetry: shouldRecordTelemetry(params) ? new TelemetryCacheInMemory() : undefined,
25
- uniqueKeys: params.impressionsMode === NONE ? new UniqueKeysCacheInMemory() : undefined,
23
+ splits,
24
+ segments,
25
+ impressions: new ImpressionsCacheInMemory(impressionsQueueSize),
26
+ impressionCounts: impressionsMode !== DEBUG ? new ImpressionCountsCacheInMemory() : undefined,
27
+ events: new EventsCacheInMemory(eventsQueueSize),
28
+ telemetry: shouldRecordTelemetry(params) ? new TelemetryCacheInMemory(splits, segments) : undefined,
29
+ uniqueKeys: impressionsMode === NONE ? new UniqueKeysCacheInMemory() : undefined,
26
30
 
27
31
  // When using MEMORY we should clean all the caches to leave them empty
28
32
  destroy() {
@@ -14,15 +14,19 @@ import { UniqueKeysCacheInMemoryCS } from './UniqueKeysCacheInMemoryCS';
14
14
  * @param params parameters required by EventsCacheSync
15
15
  */
16
16
  export function InMemoryStorageCSFactory(params: IStorageFactoryParams): IStorageSync {
17
+ const { settings: { scheduler: { impressionsQueueSize, eventsQueueSize, }, sync: { impressionsMode } } } = params;
18
+
19
+ const splits = new SplitsCacheInMemory();
20
+ const segments = new MySegmentsCacheInMemory();
17
21
 
18
22
  return {
19
- splits: new SplitsCacheInMemory(),
20
- segments: new MySegmentsCacheInMemory(),
21
- impressions: new ImpressionsCacheInMemory(params.impressionsQueueSize),
22
- impressionCounts: params.impressionsMode !== DEBUG ? new ImpressionCountsCacheInMemory() : undefined,
23
- events: new EventsCacheInMemory(params.eventsQueueSize),
24
- telemetry: shouldRecordTelemetry(params) ? new TelemetryCacheInMemory() : undefined,
25
- uniqueKeys: params.impressionsMode === NONE ? new UniqueKeysCacheInMemoryCS() : undefined,
23
+ splits,
24
+ segments,
25
+ impressions: new ImpressionsCacheInMemory(impressionsQueueSize),
26
+ impressionCounts: impressionsMode !== DEBUG ? new ImpressionCountsCacheInMemory() : undefined,
27
+ events: new EventsCacheInMemory(eventsQueueSize),
28
+ telemetry: shouldRecordTelemetry(params) ? new TelemetryCacheInMemory(splits, segments) : undefined,
29
+ uniqueKeys: impressionsMode === NONE ? new UniqueKeysCacheInMemoryCS() : undefined,
26
30
 
27
31
  // When using MEMORY we should clean all the caches to leave them empty
28
32
  destroy() {
@@ -1,7 +1,7 @@
1
- import { ImpressionDataType, EventDataType, LastSync, HttpErrors, HttpLatencies, StreamingEvent, Method, OperationType, MethodExceptions, MethodLatencies } from '../../sync/submitters/types';
2
- import { LOCALHOST_MODE } from '../../utils/constants';
1
+ import { ImpressionDataType, EventDataType, LastSync, HttpErrors, HttpLatencies, StreamingEvent, Method, OperationType, MethodExceptions, MethodLatencies, TelemetryUsageStatsPayload } from '../../sync/submitters/types';
2
+ import { DEDUPED, DROPPED, LOCALHOST_MODE, QUEUED } from '../../utils/constants';
3
3
  import { findLatencyIndex } from '../findLatencyIndex';
4
- import { IStorageFactoryParams, ITelemetryCacheSync } from '../types';
4
+ import { ISegmentsCacheSync, ISplitsCacheSync, IStorageFactoryParams, ITelemetryCacheSync } from '../types';
5
5
 
6
6
  const MAX_STREAMING_EVENTS = 20;
7
7
  const MAX_TAGS = 10;
@@ -19,12 +19,48 @@ const ACCEPTANCE_RANGE = 0.001;
19
19
  * Record telemetry if mode is not localhost.
20
20
  * All factory instances track telemetry on server-side, and 0.1% on client-side.
21
21
  */
22
- export function shouldRecordTelemetry(params: IStorageFactoryParams) {
23
- return params.mode !== LOCALHOST_MODE && (params.matchingKey === undefined || Math.random() <= ACCEPTANCE_RANGE);
22
+ export function shouldRecordTelemetry({ settings }: IStorageFactoryParams) {
23
+ return settings.mode !== LOCALHOST_MODE && (settings.core.key === undefined || Math.random() <= ACCEPTANCE_RANGE);
24
24
  }
25
25
 
26
26
  export class TelemetryCacheInMemory implements ITelemetryCacheSync {
27
27
 
28
+ constructor(private splits?: ISplitsCacheSync, private segments?: ISegmentsCacheSync) { }
29
+
30
+ // isEmpty flag
31
+ private e = true;
32
+
33
+ isEmpty() { return this.e; }
34
+
35
+ clear() { /* no-op */ }
36
+
37
+ pop(): TelemetryUsageStatsPayload {
38
+ this.e = true;
39
+
40
+ return {
41
+ lS: this.getLastSynchronization(),
42
+ mL: this.popLatencies(),
43
+ mE: this.popExceptions(),
44
+ hE: this.popHttpErrors(),
45
+ hL: this.popHttpLatencies(),
46
+ tR: this.popTokenRefreshes(),
47
+ aR: this.popAuthRejections(),
48
+ iQ: this.getImpressionStats(QUEUED),
49
+ iDe: this.getImpressionStats(DEDUPED),
50
+ iDr: this.getImpressionStats(DROPPED),
51
+ spC: this.splits && this.splits.getSplitNames().length,
52
+ seC: this.segments && this.segments.getRegisteredSegments().length,
53
+ skC: this.segments && this.segments.getKeysCount(),
54
+ sL: this.getSessionLength(),
55
+ eQ: this.getEventStats(QUEUED),
56
+ eD: this.getEventStats(DROPPED),
57
+ sE: this.popStreamingEvents(),
58
+ t: this.popTags(),
59
+ };
60
+ }
61
+
62
+ /** Config stats */
63
+
28
64
  private timeUntilReady?: number;
29
65
 
30
66
  getTimeUntilReady() {
@@ -55,6 +91,8 @@ export class TelemetryCacheInMemory implements ITelemetryCacheSync {
55
91
  this.notReadyUsage++;
56
92
  }
57
93
 
94
+ /** Usage stats */
95
+
58
96
  private impressionStats = [0, 0, 0];
59
97
 
60
98
  getImpressionStats(type: ImpressionDataType) {
@@ -63,6 +101,7 @@ export class TelemetryCacheInMemory implements ITelemetryCacheSync {
63
101
 
64
102
  recordImpressionStats(type: ImpressionDataType, count: number) {
65
103
  this.impressionStats[type] += count;
104
+ this.e = false;
66
105
  }
67
106
 
68
107
  private eventStats = [0, 0];
@@ -73,9 +112,9 @@ export class TelemetryCacheInMemory implements ITelemetryCacheSync {
73
112
 
74
113
  recordEventStats(type: EventDataType, count: number) {
75
114
  this.eventStats[type] += count;
115
+ this.e = false;
76
116
  }
77
117
 
78
- // @ts-expect-error
79
118
  private lastSync: LastSync = {};
80
119
 
81
120
  getLastSynchronization() {
@@ -84,40 +123,35 @@ export class TelemetryCacheInMemory implements ITelemetryCacheSync {
84
123
 
85
124
  recordSuccessfulSync(resource: OperationType, timeMs: number) {
86
125
  this.lastSync[resource] = timeMs;
126
+ this.e = false;
87
127
  }
88
128
 
89
- // @ts-expect-error
90
129
  private httpErrors: HttpErrors = {};
91
130
 
92
131
  popHttpErrors() {
93
- const result = this.httpErrors; // @ts-expect-error
132
+ const result = this.httpErrors;
94
133
  this.httpErrors = {};
95
134
  return result;
96
135
  }
97
136
 
98
137
  recordHttpError(resource: OperationType, status: number) {
99
- if (!this.httpErrors[resource]) this.httpErrors[resource] = {};
100
- if (!this.httpErrors[resource][status]) {
101
- this.httpErrors[resource][status] = 1;
102
- } else {
103
- this.httpErrors[resource][status]++;
104
- }
138
+ const statusErrors = (this.httpErrors[resource] = this.httpErrors[resource] || {});
139
+ statusErrors[status] = (statusErrors[status] || 0) + 1;
140
+ this.e = false;
105
141
  }
106
142
 
107
- // @ts-expect-error
108
143
  private httpLatencies: HttpLatencies = {};
109
144
 
110
145
  popHttpLatencies() {
111
- const result = this.httpLatencies; // @ts-expect-error
146
+ const result = this.httpLatencies;
112
147
  this.httpLatencies = {};
113
148
  return result;
114
149
  }
115
150
 
116
151
  recordHttpLatency(resource: OperationType, latencyMs: number) {
117
- if (!this.httpLatencies[resource]) {
118
- this.httpLatencies[resource] = newBuckets();
119
- }
120
- this.httpLatencies[resource][findLatencyIndex(latencyMs)]++;
152
+ const latencyBuckets = (this.httpLatencies[resource] = this.httpLatencies[resource] || newBuckets());
153
+ latencyBuckets[findLatencyIndex(latencyMs)]++;
154
+ this.e = false;
121
155
  }
122
156
 
123
157
  private authRejections = 0;
@@ -130,6 +164,7 @@ export class TelemetryCacheInMemory implements ITelemetryCacheSync {
130
164
 
131
165
  recordAuthRejections() {
132
166
  this.authRejections++;
167
+ this.e = false;
133
168
  }
134
169
 
135
170
  private tokenRefreshes = 0;
@@ -142,6 +177,7 @@ export class TelemetryCacheInMemory implements ITelemetryCacheSync {
142
177
 
143
178
  recordTokenRefreshes() {
144
179
  this.tokenRefreshes++;
180
+ this.e = false;
145
181
  }
146
182
 
147
183
  private streamingEvents: StreamingEvent[] = []
@@ -154,6 +190,7 @@ export class TelemetryCacheInMemory implements ITelemetryCacheSync {
154
190
  if (this.streamingEvents.length < MAX_STREAMING_EVENTS) {
155
191
  this.streamingEvents.push(streamingEvent);
156
192
  }
193
+ this.e = false;
157
194
  }
158
195
 
159
196
  private tags: string[] = [];
@@ -166,6 +203,7 @@ export class TelemetryCacheInMemory implements ITelemetryCacheSync {
166
203
  if (this.tags.length < MAX_TAGS) {
167
204
  this.tags.push(tag);
168
205
  }
206
+ this.e = false;
169
207
  }
170
208
 
171
209
  private sessionLength?: number;
@@ -176,39 +214,34 @@ export class TelemetryCacheInMemory implements ITelemetryCacheSync {
176
214
 
177
215
  recordSessionLength(ms: number) {
178
216
  this.sessionLength = ms;
217
+ this.e = false;
179
218
  }
180
219
 
181
- // @ts-expect-error
182
220
  private exceptions: MethodExceptions = {};
183
221
 
184
222
  popExceptions() {
185
- const result = this.exceptions; // @ts-expect-error
223
+ const result = this.exceptions;
186
224
  this.exceptions = {};
187
225
  return result;
188
226
  }
189
227
 
190
228
  recordException(method: Method) {
191
- if (!this.exceptions[method]) {
192
- this.exceptions[method] = 1;
193
- } else {
194
- this.exceptions[method]++;
195
- }
229
+ this.exceptions[method] = (this.exceptions[method] || 0) + 1;
230
+ this.e = false;
196
231
  }
197
232
 
198
- // @ts-expect-error
199
233
  private latencies: MethodLatencies = {};
200
234
 
201
235
  popLatencies() {
202
- const result = this.latencies; // @ts-expect-error
236
+ const result = this.latencies;
203
237
  this.latencies = {};
204
238
  return result;
205
239
  }
206
240
 
207
241
  recordLatency(method: Method, latencyMs: number) {
208
- if (!this.latencies[method]) {
209
- this.latencies[method] = newBuckets();
210
- }
211
- this.latencies[method][findLatencyIndex(latencyMs)]++;
242
+ const latencyBuckets = (this.latencies[method] = this.latencies[method] || newBuckets());
243
+ latencyBuckets[findLatencyIndex(latencyMs)]++;
244
+ this.e = false;
212
245
  }
213
246
 
214
247
  }
@@ -61,7 +61,7 @@ export class ImpressionCountsCacheInRedis extends ImpressionCountsCacheInMemory
61
61
 
62
62
  this.redis.del(this.key).catch(() => { /* no-op */ });
63
63
 
64
- const impressionsCount: ImpressionCountsPayload = { pf: [] };
64
+ const pf: ImpressionCountsPayload['pf'] = [];
65
65
 
66
66
  forOwn(counts, (count, key) => {
67
67
  const nameAndTime = key.split('::');
@@ -82,14 +82,14 @@ export class ImpressionCountsCacheInRedis extends ImpressionCountsCacheInMemory
82
82
  return;
83
83
  }
84
84
 
85
- impressionsCount.pf.push({
85
+ pf.push({
86
86
  f: nameAndTime[0],
87
87
  m: timeFrame,
88
88
  rc: rawCount,
89
89
  });
90
90
  });
91
91
 
92
- return impressionsCount;
92
+ return { pf };
93
93
  });
94
94
  }
95
95
  }