snowplow/snowplow-javascript-tracker

View on GitHub
plugins/browser-plugin-media/src/repeatedEventFilter.ts

Summary

Maintainability
A
1 hr
Test Coverage
import { getMediaEventSchema } from './schemata';
import { FilterOutRepeatedEvents, MediaEventType, EventWithContext } from './types';

/**
 * This class filters out repeated events that are sent by the media player.
 * This applies to seek and volume change events.
 */
export class RepeatedEventFilter {
  private aggregateEventsWithOrder: { [schema: string]: boolean } = {};
  private eventsToAggregate: { [schema: string]: (() => void)[] } = {};
  private flushTimeout?: number;
  private flushTimeoutMs: number;

  constructor(configuration?: FilterOutRepeatedEvents) {
    let allFiltersEnabled = configuration === undefined || configuration === true;
    if (allFiltersEnabled || (typeof configuration === 'object' && configuration.seekEvents !== false)) {
      this.aggregateEventsWithOrder[getMediaEventSchema(MediaEventType.SeekStart)] = true;
      this.aggregateEventsWithOrder[getMediaEventSchema(MediaEventType.SeekEnd)] = false;
    }
    if (allFiltersEnabled || (typeof configuration === 'object' && configuration.volumeChangeEvents !== false)) {
      this.aggregateEventsWithOrder[getMediaEventSchema(MediaEventType.VolumeChange)] = false;
    }

    this.flushTimeoutMs = (typeof configuration === 'object' ? configuration.flushTimeoutMs : undefined) ?? 5000;

    Object.keys(this.aggregateEventsWithOrder).forEach((schema) => {
      this.eventsToAggregate[schema] = [];
    });
  }

  trackFilteredEvents(events: EventWithContext[], trackEvent: (event: EventWithContext) => void) {
    let startFlushTimeout = false;

    events.forEach(({ event, context }) => {
      if (this.eventsToAggregate[event.schema] !== undefined) {
        startFlushTimeout = true;
        this.eventsToAggregate[event.schema].push(() => trackEvent({ event, context }));
      } else {
        startFlushTimeout = false;
        // flush any events waiting
        this.flush();

        trackEvent({ event, context });
      }
    });

    if (startFlushTimeout && this.flushTimeout === undefined) {
      this.setFlushTimeout();
    }
  }

  flush() {
    this.clearFlushTimeout();

    Object.keys(this.eventsToAggregate).forEach((schema) => {
      let eventsToAggregate = this.eventsToAggregate[schema];
      if (eventsToAggregate.length > 0) {
        if (this.aggregateEventsWithOrder[schema]) {
          eventsToAggregate[0]();
        } else {
          eventsToAggregate[eventsToAggregate.length - 1]();
        }
        this.eventsToAggregate[schema] = [];
      }
    });
  }

  private clearFlushTimeout() {
    if (this.flushTimeout !== undefined) {
      clearTimeout(this.flushTimeout);
      this.flushTimeout = undefined;
    }
  }

  private setFlushTimeout() {
    this.clearFlushTimeout();
    this.flushTimeout = window.setTimeout(() => this.flush(), this.flushTimeoutMs);
  }
}