snowplow/snowplow-javascript-tracker

View on GitHub
plugins/browser-plugin-vimeo-tracking/src/initialization.ts

Summary

Maintainability
A
35 mins
Test Coverage
import Player, { EventMap } from '@vimeo/player';

import { LOG } from '@snowplow/tracker-core';
import { BrowserPlugin } from '@snowplow/browser-tracker-core';
import { MediaEventType, MediaTrackingConfiguration, MediaType } from '@snowplow/browser-plugin-media/src/types';
import { SnowplowMediaPlugin, endMediaTracking, startMediaTracking } from '@snowplow/browser-plugin-media';

import { trackEvent } from './mediaPluginBinding';
import { CapturableInternalVimeoEvents, InternalVimeoEvent } from './events';
import { EventData, TrackedPlayer, VimeoTrackingConfiguration } from './types';
import { filterVimeoEvents, getErrorCallback, getVimeoMediaAttributes } from './utils';

export function VimeoTrackingPlugin(): BrowserPlugin {
  return SnowplowMediaPlugin();
}

const trackedPlayers: Record<string, TrackedPlayer> = {};

/** Start tracking a Vimeo Iframe element or existing Vimeo {@link Player} */
export function startVimeoTracking(args: VimeoTrackingConfiguration): void {
  const { video, ...config } = args;
  if (video === undefined) {
    LOG.error("Property 'video' is undefined");
  } else if (video instanceof HTMLIFrameElement && video.nodeName !== 'IFRAME') {
    LOG.error("Property 'video' is not an Iframe");
  } else {
    const internalConfig = filterVimeoEvents(config as VimeoTrackingConfiguration);

    if ((video as HTMLIFrameElement).nodeName === 'IFRAME') {
      startVimeoTrackingOnIframe(video as HTMLIFrameElement, internalConfig);
    } else {
      startVimeoTrackingOnPlayer(video as Player, internalConfig);
    }
  }
}

/**
 * Ends media tracking with the given ID if previously started. Clears local state for the media tracking.
 * Clears all listeners set by the plugin on the Vimeo player.
 */
export function endVimeoTracking(id: string): void {
  const playerToEnd = trackedPlayers[id];
  if (playerToEnd === undefined) {
    LOG.error(`No Vimeo player found with ID ${id}`);
  } else {
    Object.entries(playerToEnd.eventListeners).forEach(([eventType, callback]) =>
      playerToEnd.player.off(eventType as keyof EventMap, callback)
    );
    endMediaTracking({ id: playerToEnd.config.id });
  }
}

/** Enables the tracking of Vimeo events on an Iframe */
function startVimeoTrackingOnIframe(iframe: HTMLIFrameElement, config: VimeoTrackingConfiguration): void {
  setInitialPlayerAttributes({
    player: new Player(iframe),
    config,
    eventListeners: {},
  });
}

/** Enables the tracking of Vimeo events on an existing Vimeo player */
function startVimeoTrackingOnPlayer(player: Player, config: VimeoTrackingConfiguration): void {
  setInitialPlayerAttributes({
    player,
    config,
    eventListeners: {},
  });
}

/** Set the initial Media plugin player configuration before tracking begins */
async function setInitialPlayerAttributes(trackedPlayer: TrackedPlayer): Promise<void> {
  trackedPlayer.config.player = {
    ...(await getVimeoMediaAttributes(trackedPlayer)),
    ...trackedPlayer.config.player,
    ...{ mediaType: MediaType.Video, playerType: 'com.vimeo' },
  };

  trackedPlayers[trackedPlayer.config.id] = trackedPlayer;
  startTracking(trackedPlayer);
}

/** Attaches listeners to the Vimeo player for each event in {@link VimeoTrackingConfiguration.vimeoCaptureEvents | vimeoCaptureEvents} */
async function startTracking(trackedPlayer: TrackedPlayer): Promise<void> {
  const errorCallback = getErrorCallback(trackedPlayer);

  startMediaTracking(trackedPlayer.config as MediaTrackingConfiguration);

  trackEvent(trackedPlayer, MediaEventType.Ready, {}, errorCallback);

  // Track every event, event filtering is done by the Media Plugin
  Object.keys(InternalVimeoEvent).forEach((eventName) => {
    const eventType = InternalVimeoEvent[eventName as keyof typeof InternalVimeoEvent];
    // Don't attach listeners for Vimeo events that haven't been passed in with the Media Plugin config
    if (
      CapturableInternalVimeoEvents.includes(eventType) &&
      !trackedPlayer.config.vimeoCaptureEvents?.includes(eventType)
    ) {
      return;
    }

    // Create a named function to allow us to remove the listener later
    const callback = (eventData: EventData | unknown) => trackEvent(trackedPlayer, eventType, eventData, errorCallback);
    trackedPlayer.eventListeners[eventType] = callback;

    trackedPlayer.player.on(eventType, callback);
  });
}