@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.
- package/cjs/listeners/browser.js +9 -11
- package/cjs/sdkFactory/index.js +7 -24
- package/cjs/storages/KeyBuilderSS.js +4 -43
- package/cjs/storages/inLocalStorage/index.js +14 -10
- package/cjs/storages/inMemory/InMemoryStorage.js +10 -7
- package/cjs/storages/inMemory/InMemoryStorageCS.js +10 -7
- package/cjs/storages/inMemory/TelemetryCacheInMemory.js +57 -34
- package/cjs/storages/inRedis/ImpressionCountsCacheInRedis.js +3 -3
- package/cjs/storages/inRedis/ImpressionsCacheInRedis.js +2 -19
- package/cjs/storages/inRedis/TelemetryCacheInRedis.js +4 -4
- package/cjs/storages/inRedis/index.js +4 -2
- package/cjs/storages/pluggable/ImpressionCountsCachePluggable.js +3 -3
- package/cjs/storages/pluggable/ImpressionsCachePluggable.js +2 -19
- package/cjs/storages/pluggable/TelemetryCachePluggable.js +4 -4
- package/cjs/storages/pluggable/inMemoryWrapper.js +8 -6
- package/cjs/storages/pluggable/index.js +4 -2
- package/cjs/storages/utils.js +73 -0
- package/cjs/sync/submitters/submitterManager.js +1 -1
- package/cjs/sync/submitters/telemetrySubmitter.js +4 -40
- package/cjs/trackers/impressionObserver/utils.js +1 -17
- package/cjs/trackers/uniqueKeysTracker.js +1 -1
- package/cjs/utils/settingsValidation/index.js +7 -1
- package/esm/listeners/browser.js +9 -11
- package/esm/sdkFactory/index.js +7 -24
- package/esm/storages/KeyBuilderSS.js +1 -37
- package/esm/storages/inLocalStorage/index.js +15 -11
- package/esm/storages/inMemory/InMemoryStorage.js +10 -7
- package/esm/storages/inMemory/InMemoryStorageCS.js +10 -7
- package/esm/storages/inMemory/TelemetryCacheInMemory.js +58 -35
- package/esm/storages/inRedis/ImpressionCountsCacheInRedis.js +3 -3
- package/esm/storages/inRedis/ImpressionsCacheInRedis.js +2 -19
- package/esm/storages/inRedis/TelemetryCacheInRedis.js +1 -1
- package/esm/storages/inRedis/index.js +4 -2
- package/esm/storages/pluggable/ImpressionCountsCachePluggable.js +3 -3
- package/esm/storages/pluggable/ImpressionsCachePluggable.js +2 -19
- package/esm/storages/pluggable/TelemetryCachePluggable.js +1 -1
- package/esm/storages/pluggable/inMemoryWrapper.js +8 -6
- package/esm/storages/pluggable/index.js +4 -2
- package/esm/storages/utils.js +65 -0
- package/esm/sync/submitters/submitterManager.js +1 -1
- package/esm/sync/submitters/telemetrySubmitter.js +4 -39
- package/esm/trackers/impressionObserver/utils.js +1 -15
- package/esm/trackers/uniqueKeysTracker.js +1 -1
- package/esm/utils/settingsValidation/index.js +7 -1
- package/package.json +2 -1
- package/src/listeners/browser.ts +9 -13
- package/src/sdkClient/sdkClient.ts +1 -1
- package/src/sdkFactory/index.ts +7 -29
- package/src/sdkFactory/types.ts +2 -2
- package/src/services/splitApi.ts +2 -2
- package/src/storages/KeyBuilderSS.ts +2 -44
- package/src/storages/inLocalStorage/index.ts +16 -11
- package/src/storages/inMemory/AttributesCacheInMemory.ts +7 -7
- package/src/storages/inMemory/InMemoryStorage.ts +11 -7
- package/src/storages/inMemory/InMemoryStorageCS.ts +11 -7
- package/src/storages/inMemory/TelemetryCacheInMemory.ts +66 -33
- package/src/storages/inRedis/ImpressionCountsCacheInRedis.ts +3 -3
- package/src/storages/inRedis/ImpressionsCacheInRedis.ts +2 -22
- package/src/storages/inRedis/TelemetryCacheInRedis.ts +3 -2
- package/src/storages/inRedis/index.ts +4 -1
- package/src/storages/pluggable/ImpressionCountsCachePluggable.ts +3 -3
- package/src/storages/pluggable/ImpressionsCachePluggable.ts +3 -23
- package/src/storages/pluggable/TelemetryCachePluggable.ts +3 -2
- package/src/storages/pluggable/inMemoryWrapper.ts +6 -6
- package/src/storages/pluggable/index.ts +4 -2
- package/src/storages/types.ts +45 -64
- package/src/storages/utils.ts +78 -0
- package/src/sync/submitters/submitter.ts +2 -2
- package/src/sync/submitters/submitterManager.ts +1 -1
- package/src/sync/submitters/telemetrySubmitter.ts +5 -41
- package/src/sync/submitters/types.ts +7 -5
- package/src/trackers/impressionObserver/utils.ts +1 -16
- package/src/trackers/impressionsTracker.ts +2 -2
- package/src/trackers/strategy/strategyDebug.ts +4 -4
- package/src/trackers/strategy/strategyNone.ts +9 -9
- package/src/trackers/strategy/strategyOptimized.ts +9 -9
- package/src/trackers/uniqueKeysTracker.ts +6 -6
- package/src/utils/redis/RedisMock.ts +5 -5
- package/src/utils/settingsValidation/index.ts +5 -1
- package/types/storages/KeyBuilderSS.d.ts +1 -3
- package/types/storages/inMemory/TelemetryCacheInMemory.d.ts +19 -8
- package/types/storages/inRedis/ImpressionsCacheInRedis.d.ts +0 -1
- package/types/storages/pluggable/ImpressionsCachePluggable.d.ts +1 -2
- package/types/storages/types.d.ts +35 -45
- package/types/storages/utils.d.ts +8 -0
- package/types/sync/submitters/submitter.d.ts +2 -2
- package/types/sync/submitters/telemetrySubmitter.d.ts +2 -10
- package/types/sync/submitters/types.d.ts +8 -6
- package/types/trackers/impressionObserver/utils.d.ts +0 -8
- package/types/trackers/strategy/strategyNone.d.ts +2 -2
- package/types/trackers/strategy/strategyOptimized.d.ts +2 -2
- package/types/trackers/uniqueKeysTracker.d.ts +1 -1
- package/cjs/storages/metadataBuilder.js +0 -12
- package/esm/storages/metadataBuilder.js +0 -8
- package/src/storages/metadataBuilder.ts +0 -11
package/src/listeners/browser.ts
CHANGED
|
@@ -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 {
|
|
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(
|
|
98
|
-
this._flushData(
|
|
99
|
-
if (this.storage.impressionCounts) this._flushData(
|
|
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:
|
|
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
|
package/src/sdkFactory/index.ts
CHANGED
|
@@ -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
|
|
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
|
-
|
|
43
|
-
|
|
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
|
-
|
|
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 =
|
|
52
|
+
const uniqueKeysTracker = impressionsMode === NONE ? uniqueKeysTrackerFactory(log, storage.uniqueKeys!, filterAdapterFactory && filterAdapterFactory()) : undefined;
|
|
75
53
|
|
|
76
54
|
let strategy;
|
|
77
|
-
switch (
|
|
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(
|
|
101
|
+
Logger: createLoggerAPI(log),
|
|
124
102
|
|
|
125
103
|
settings,
|
|
126
104
|
}, extraProps && extraProps(ctx));
|
package/src/sdkFactory/types.ts
CHANGED
|
@@ -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.
|
|
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.
|
package/src/services/splitApi.ts
CHANGED
|
@@ -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
|
|
39
|
-
const
|
|
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
|
|
44
|
-
segments
|
|
45
|
-
impressions: new ImpressionsCacheInMemory(
|
|
46
|
-
impressionCounts:
|
|
47
|
-
events: new EventsCacheInMemory(
|
|
48
|
-
telemetry: shouldRecordTelemetry(params) ? new TelemetryCacheInMemory() : undefined,
|
|
49
|
-
uniqueKeys:
|
|
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
|
|
20
|
-
segments
|
|
21
|
-
impressions: new ImpressionsCacheInMemory(
|
|
22
|
-
impressionCounts:
|
|
23
|
-
events: new EventsCacheInMemory(
|
|
24
|
-
telemetry: shouldRecordTelemetry(params) ? new TelemetryCacheInMemory() : undefined,
|
|
25
|
-
uniqueKeys:
|
|
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
|
|
20
|
-
segments
|
|
21
|
-
impressions: new ImpressionsCacheInMemory(
|
|
22
|
-
impressionCounts:
|
|
23
|
-
events: new EventsCacheInMemory(
|
|
24
|
-
telemetry: shouldRecordTelemetry(params) ? new TelemetryCacheInMemory() : undefined,
|
|
25
|
-
uniqueKeys:
|
|
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(
|
|
23
|
-
return
|
|
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;
|
|
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
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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;
|
|
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
|
-
|
|
118
|
-
|
|
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;
|
|
223
|
+
const result = this.exceptions;
|
|
186
224
|
this.exceptions = {};
|
|
187
225
|
return result;
|
|
188
226
|
}
|
|
189
227
|
|
|
190
228
|
recordException(method: Method) {
|
|
191
|
-
|
|
192
|
-
|
|
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;
|
|
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
|
-
|
|
209
|
-
|
|
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
|
|
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
|
-
|
|
85
|
+
pf.push({
|
|
86
86
|
f: nameAndTime[0],
|
|
87
87
|
m: timeFrame,
|
|
88
88
|
rc: rawCount,
|
|
89
89
|
});
|
|
90
90
|
});
|
|
91
91
|
|
|
92
|
-
return
|
|
92
|
+
return { pf };
|
|
93
93
|
});
|
|
94
94
|
}
|
|
95
95
|
}
|