-
Notifications
You must be signed in to change notification settings - Fork 7
Expand file tree
/
Copy pathimpressionsTracker.ts
More file actions
88 lines (78 loc) · 3.83 KB
/
impressionsTracker.ts
File metadata and controls
88 lines (78 loc) · 3.83 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
import { objectAssign } from '../utils/lang/objectAssign';
import { thenable } from '../utils/promise/thenable';
import { IImpressionsCacheBase, ITelemetryCacheSync, ITelemetryCacheAsync } from '../storages/types';
import { IImpressionsHandler, IImpressionsTracker, IStrategy } from './types';
import { ISettings } from '../types';
import { IMPRESSIONS_TRACKER_SUCCESS, ERROR_IMPRESSIONS_TRACKER, ERROR_IMPRESSIONS_LISTENER } from '../logger/constants';
import { CONSENT_DECLINED, DEDUPED, QUEUED } from '../utils/constants';
import SplitIO from '../../types/splitio';
/**
* Impressions tracker stores impressions in cache and pass them to the listener and integrations manager if provided.
*
* @param impressionsCache - cache to save impressions
* @param metadata - runtime metadata (ip, hostname and version)
* @param impressionListener - optional impression listener
* @param integrationsManager - optional integrations manager
* @param strategy - strategy for impressions tracking.
*/
export function impressionsTrackerFactory(
settings: ISettings,
impressionsCache: IImpressionsCacheBase,
strategy: IStrategy,
whenInit: (cb: () => void) => void,
integrationsManager?: IImpressionsHandler,
telemetryCache?: ITelemetryCacheSync | ITelemetryCacheAsync,
): IImpressionsTracker {
const { log, impressionListener, runtime: { ip, hostname }, version } = settings;
return {
track(impressions: SplitIO.ImpressionDTO[], attributes?: SplitIO.Attributes) {
if (settings.userConsent === CONSENT_DECLINED) return;
const impressionsCount = impressions.length;
const { impressionsToStore, impressionsToListener, deduped } = strategy.process(impressions);
const impressionsToListenerCount = impressionsToListener.length;
if (impressionsToStore.length > 0) {
const res = impressionsCache.track(impressionsToStore);
// If we're on an async storage, handle error and log it.
if (thenable(res)) {
res.then(() => {
log.info(IMPRESSIONS_TRACKER_SUCCESS, [impressionsCount]);
}).catch(err => {
log.error(ERROR_IMPRESSIONS_TRACKER, [impressionsCount, err]);
});
} else {
// Record when impressionsCache is sync only (standalone mode)
// @TODO we are not dropping impressions on full queue yet, so DROPPED stats are not recorded
if (telemetryCache) {
(telemetryCache as ITelemetryCacheSync).recordImpressionStats(QUEUED, impressionsToStore.length);
(telemetryCache as ITelemetryCacheSync).recordImpressionStats(DEDUPED, deduped);
}
}
}
// @TODO next block might be handled by the integration manager. In that case, the metadata object doesn't need to be passed in the constructor
if (impressionListener || integrationsManager) {
for (let i = 0; i < impressionsToListenerCount; i++) {
const impressionData: SplitIO.ImpressionData = {
// copy of impression, to avoid unexpected behavior if modified by integrations or impressionListener
impression: objectAssign({}, impressionsToListener[i]),
attributes,
ip,
hostname,
sdkLanguageVersion: version
};
whenInit(() => {
// Wrap in a timeout because we don't want it to be blocking.
setTimeout(() => {
// integrationsManager.handleImpression does not throw errors
if (integrationsManager) integrationsManager.handleImpression(impressionData);
try { // @ts-ignore. An exception on the listeners should not break the SDK.
if (impressionListener) impressionListener.logImpression(impressionData);
} catch (err) {
log.error(ERROR_IMPRESSIONS_LISTENER, [err]);
}
});
});
}
}
}
};
}