@splitsoftware/splitio-commons 1.6.2-rc.11 → 1.6.2-rc.13

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 (58) hide show
  1. package/cjs/listeners/browser.js +9 -11
  2. package/cjs/sdkFactory/index.js +7 -24
  3. package/cjs/storages/inLocalStorage/index.js +14 -10
  4. package/cjs/storages/inMemory/InMemoryStorage.js +10 -7
  5. package/cjs/storages/inMemory/InMemoryStorageCS.js +10 -7
  6. package/cjs/storages/inMemory/TelemetryCacheInMemory.js +57 -34
  7. package/cjs/storages/inRedis/index.js +4 -2
  8. package/cjs/storages/pluggable/index.js +15 -11
  9. package/cjs/sync/submitters/submitterManager.js +1 -1
  10. package/cjs/sync/submitters/telemetrySubmitter.js +4 -40
  11. package/cjs/trackers/impressionObserver/utils.js +1 -17
  12. package/cjs/trackers/uniqueKeysTracker.js +1 -1
  13. package/cjs/utils/settingsValidation/index.js +7 -1
  14. package/esm/listeners/browser.js +9 -11
  15. package/esm/sdkFactory/index.js +7 -24
  16. package/esm/storages/inLocalStorage/index.js +15 -11
  17. package/esm/storages/inMemory/InMemoryStorage.js +10 -7
  18. package/esm/storages/inMemory/InMemoryStorageCS.js +10 -7
  19. package/esm/storages/inMemory/TelemetryCacheInMemory.js +58 -35
  20. package/esm/storages/inRedis/index.js +4 -2
  21. package/esm/storages/pluggable/index.js +15 -11
  22. package/esm/sync/submitters/submitterManager.js +1 -1
  23. package/esm/sync/submitters/telemetrySubmitter.js +4 -39
  24. package/esm/trackers/impressionObserver/utils.js +1 -15
  25. package/esm/trackers/uniqueKeysTracker.js +1 -1
  26. package/esm/utils/settingsValidation/index.js +7 -1
  27. package/package.json +2 -1
  28. package/src/listeners/browser.ts +9 -13
  29. package/src/sdkClient/sdkClient.ts +1 -1
  30. package/src/sdkFactory/index.ts +7 -29
  31. package/src/sdkFactory/types.ts +2 -2
  32. package/src/storages/inLocalStorage/index.ts +16 -11
  33. package/src/storages/inMemory/InMemoryStorage.ts +11 -7
  34. package/src/storages/inMemory/InMemoryStorageCS.ts +11 -7
  35. package/src/storages/inMemory/TelemetryCacheInMemory.ts +66 -33
  36. package/src/storages/inRedis/TelemetryCacheInRedis.ts +1 -1
  37. package/src/storages/inRedis/index.ts +4 -1
  38. package/src/storages/pluggable/TelemetryCachePluggable.ts +1 -1
  39. package/src/storages/pluggable/index.ts +12 -8
  40. package/src/storages/types.ts +41 -60
  41. package/src/sync/submitters/submitter.ts +2 -2
  42. package/src/sync/submitters/submitterManager.ts +1 -1
  43. package/src/sync/submitters/telemetrySubmitter.ts +5 -41
  44. package/src/sync/submitters/types.ts +5 -5
  45. package/src/trackers/impressionObserver/utils.ts +1 -16
  46. package/src/trackers/strategy/strategyNone.ts +9 -9
  47. package/src/trackers/strategy/strategyOptimized.ts +9 -9
  48. package/src/trackers/uniqueKeysTracker.ts +6 -6
  49. package/src/utils/settingsValidation/index.ts +5 -1
  50. package/types/storages/inMemory/TelemetryCacheInMemory.d.ts +19 -8
  51. package/types/storages/types.d.ts +31 -41
  52. package/types/sync/submitters/submitter.d.ts +2 -2
  53. package/types/sync/submitters/telemetrySubmitter.d.ts +2 -10
  54. package/types/sync/submitters/types.d.ts +6 -6
  55. package/types/trackers/impressionObserver/utils.d.ts +0 -8
  56. package/types/trackers/strategy/strategyNone.d.ts +2 -2
  57. package/types/trackers/strategy/strategyOptimized.d.ts +2 -2
  58. package/types/trackers/uniqueKeysTracker.d.ts +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@splitsoftware/splitio-commons",
3
- "version": "1.6.2-rc.11",
3
+ "version": "1.6.2-rc.13",
4
4
  "description": "Split Javascript SDK common components",
5
5
  "main": "cjs/index.js",
6
6
  "module": "esm/index.js",
@@ -24,6 +24,7 @@
24
24
  "build:cjs": "rimraf cjs && tsc -m CommonJS --outDir cjs",
25
25
  "test": "jest",
26
26
  "test:coverage": "jest --coverage",
27
+ "all": "npm run check && npm run build && npm run test",
27
28
  "publish:rc": "npm run check && npm run test && npm run build && npm publish --tag rc",
28
29
  "publish:stable": "npm run check && npm run test && npm run build && npm publish"
29
30
  },
@@ -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.
@@ -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();
@@ -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
  }
@@ -76,7 +76,7 @@ export class TelemetryCacheInRedis implements ITelemetryCacheAsync {
76
76
  tr: newBuckets(),
77
77
  });
78
78
 
79
- result.get(metadata)![method][bucket] = count;
79
+ result.get(metadata)![method]![bucket] = count;
80
80
  });
81
81
 
82
82
  return this.redis.del(this.keys.latencyPrefix).then(() => result);
@@ -10,6 +10,7 @@ import { DEBUG, NONE, STORAGE_REDIS } from '../../utils/constants';
10
10
  import { TelemetryCacheInRedis } from './TelemetryCacheInRedis';
11
11
  import { UniqueKeysCacheInRedis } from './UniqueKeysCacheInRedis';
12
12
  import { ImpressionCountsCacheInRedis } from './ImpressionCountsCacheInRedis';
13
+ import { metadataBuilder } from '../metadataBuilder';
13
14
 
14
15
  export interface InRedisStorageOptions {
15
16
  prefix?: string
@@ -24,7 +25,9 @@ export function InRedisStorage(options: InRedisStorageOptions = {}): IStorageAsy
24
25
 
25
26
  const prefix = validatePrefix(options.prefix);
26
27
 
27
- function InRedisStorageFactory({ log, metadata, onReadyCb, impressionsMode }: IStorageFactoryParams): IStorageAsync {
28
+ function InRedisStorageFactory(params: IStorageFactoryParams): IStorageAsync {
29
+ const { onReadyCb, settings, settings: { log, sync: { impressionsMode } } } = params;
30
+ const metadata = metadataBuilder(settings);
28
31
  const keys = new KeyBuilderSS(prefix, metadata);
29
32
  const redisClient = new RedisAdapter(log, options.options || {});
30
33
  const telemetry = new TelemetryCacheInRedis(log, keys, redisClient);
@@ -75,7 +75,7 @@ export class TelemetryCachePluggable implements ITelemetryCacheAsync {
75
75
  tr: newBuckets(),
76
76
  });
77
77
 
78
- result.get(metadata)![method][bucket] = count;
78
+ result.get(metadata)![method]![bucket] = count;
79
79
  }
80
80
 
81
81
  return Promise.all(latencyKeys.map((latencyKey) => this.wrapper.del(latencyKey))).then(() => result);
@@ -18,6 +18,7 @@ import { ImpressionCountsCachePluggable } from './ImpressionCountsCachePluggable
18
18
  import { UniqueKeysCachePluggable } from './UniqueKeysCachePluggable';
19
19
  import { UniqueKeysCacheInMemory } from '../inMemory/UniqueKeysCacheInMemory';
20
20
  import { UniqueKeysCacheInMemoryCS } from '../inMemory/UniqueKeysCacheInMemoryCS';
21
+ import { metadataBuilder } from '../metadataBuilder';
21
22
 
22
23
  const NO_VALID_WRAPPER = 'Expecting pluggable storage `wrapper` in options, but no valid wrapper instance was provided.';
23
24
  const NO_VALID_WRAPPER_INTERFACE = 'The provided wrapper instance doesn’t follow the expected interface. Check our docs.';
@@ -61,7 +62,8 @@ export function PluggableStorage(options: PluggableStorageOptions): IStorageAsyn
61
62
  const prefix = validatePrefix(options.prefix);
62
63
 
63
64
  function PluggableStorageFactory(params: IStorageFactoryParams): IStorageAsync {
64
- const { log, metadata, onReadyCb, mode, eventsQueueSize, impressionsQueueSize, impressionsMode, matchingKey } = params;
65
+ const { onReadyCb, settings, settings: { log, mode, sync: { impressionsMode }, scheduler: { impressionsQueueSize, eventsQueueSize } } } = params;
66
+ const metadata = metadataBuilder(settings);
65
67
  const keys = new KeyBuilderSS(prefix, metadata);
66
68
  const wrapper = wrapperAdapter(log, options.wrapper);
67
69
 
@@ -82,7 +84,7 @@ export function PluggableStorage(options: PluggableStorageOptions): IStorageAsyn
82
84
 
83
85
  const uniqueKeysCache = impressionsMode === NONE || isSyncronizer ?
84
86
  isPartialConsumer ?
85
- matchingKey === undefined ? new UniqueKeysCacheInMemory() : new UniqueKeysCacheInMemoryCS() :
87
+ settings.core.key === undefined ? new UniqueKeysCacheInMemory() : new UniqueKeysCacheInMemoryCS() :
86
88
  new UniqueKeysCachePluggable(log, keys.buildUniqueKeysKey(), wrapper) :
87
89
  undefined;
88
90
 
@@ -90,10 +92,12 @@ export function PluggableStorage(options: PluggableStorageOptions): IStorageAsyn
90
92
  const connectPromise = wrapper.connect().then(() => {
91
93
  onReadyCb();
92
94
 
93
- // Start periodic flush of async storages
94
- if (impressionCountsCache && (impressionCountsCache as ImpressionCountsCachePluggable).start) (impressionCountsCache as ImpressionCountsCachePluggable).start();
95
- if (uniqueKeysCache && (uniqueKeysCache as UniqueKeysCachePluggable).start) (uniqueKeysCache as UniqueKeysCachePluggable).start();
96
- if (telemetry && (telemetry as ITelemetryCacheAsync).recordConfig && !isSyncronizer) (telemetry as ITelemetryCacheAsync).recordConfig();
95
+ // Start periodic flush of async storages if not running synchronizer (producer mode)
96
+ if (!isSyncronizer) {
97
+ if (impressionCountsCache && (impressionCountsCache as ImpressionCountsCachePluggable).start) (impressionCountsCache as ImpressionCountsCachePluggable).start();
98
+ if (uniqueKeysCache && (uniqueKeysCache as UniqueKeysCachePluggable).start) (uniqueKeysCache as UniqueKeysCachePluggable).start();
99
+ if (telemetry && (telemetry as ITelemetryCacheAsync).recordConfig) (telemetry as ITelemetryCacheAsync).recordConfig();
100
+ }
97
101
  }).catch((e) => {
98
102
  e = e || new Error('Error connecting wrapper');
99
103
  onReadyCb(e);
@@ -109,9 +113,9 @@ export function PluggableStorage(options: PluggableStorageOptions): IStorageAsyn
109
113
  telemetry,
110
114
  uniqueKeys: uniqueKeysCache,
111
115
 
112
- // Disconnect the underlying storage
116
+ // Stop periodic flush and disconnect the underlying storage
113
117
  destroy() {
114
- return Promise.all([
118
+ return Promise.all(isSyncronizer ? [] : [
115
119
  impressionCountsCache && (impressionCountsCache as ImpressionCountsCachePluggable).stop && (impressionCountsCache as ImpressionCountsCachePluggable).stop(),
116
120
  uniqueKeysCache && (uniqueKeysCache as UniqueKeysCachePluggable).stop && (uniqueKeysCache as UniqueKeysCachePluggable).stop(),
117
121
  ]).then(() => wrapper.disconnect());