import { objectAssign } from '../utils/lang/objectAssign'; import { thenable } from '../utils/promise/thenable'; import { IEventsCacheBase, ITelemetryCacheAsync, ITelemetryCacheSync } from '../storages/types'; import { IEventsHandler, IEventTracker } from './types'; import { ISettings } from '../types'; import SplitIO from '../../types/splitio'; import { EVENTS_TRACKER_SUCCESS, ERROR_EVENTS_TRACKER } from '../logger/constants'; import { CONSENT_DECLINED, DROPPED, QUEUED } from '../utils/constants'; import { isConsumerMode } from '../utils/settingsValidation/mode'; /** * Event tracker stores events in cache and pass them to the integrations manager if provided. * * @param eventsCache - cache to save events * @param integrationsManager - optional event handler used for integrations */ export function eventTrackerFactory( settings: ISettings, eventsCache: IEventsCacheBase, whenInit: (cb: () => void) => void, integrationsManager?: IEventsHandler, telemetryCache?: ITelemetryCacheSync | ITelemetryCacheAsync ): IEventTracker { const { log, mode } = settings; const isAsync = isConsumerMode(mode); function queueEventsCallback(eventData: SplitIO.EventData, tracked: boolean) { const { eventTypeId, trafficTypeName, key, value, timestamp, properties } = eventData; // Logging every prop would be too much. const msg = `event of type "${eventTypeId}" for traffic type "${trafficTypeName}". Key: ${key}. Value: ${value}. Timestamp: ${timestamp}. ${properties ? 'With properties.' : 'With no properties.'}`; if (tracked) { log.info(EVENTS_TRACKER_SUCCESS, [msg]); if (integrationsManager) { whenInit(() => { // Wrap in a timeout because we don't want it to be blocking. setTimeout(() => { // copy of event, to avoid unexpected behavior if modified by integrations const eventDataCopy = objectAssign({}, eventData); if (properties) eventDataCopy.properties = objectAssign({}, properties); // integrationsManager does not throw errors (they are internally handled by each integration module) integrationsManager.handleEvent(eventDataCopy); }); }); } } else { log.error(ERROR_EVENTS_TRACKER, [msg]); } return tracked; } return { track(eventData: SplitIO.EventData, size?: number) { if (settings.userConsent === CONSENT_DECLINED) { return isAsync ? Promise.resolve(false) : false; } const tracked = eventsCache.track(eventData, size); if (thenable(tracked)) { return tracked.then(queueEventsCallback.bind(null, eventData)); } else { // Record when eventsCache is sync only (standalone mode) // @TODO we are not dropping events on full queue yet, so `tracked` is always true ATM if (telemetryCache) (telemetryCache as ITelemetryCacheSync).recordEventStats(tracked ? QUEUED : DROPPED, 1); return queueEventsCallback(eventData, tracked); } } }; }