import { CarouselScrollEvent, EventCallback, EventCallbacks, EventMap, EventMapKeys, IWidgetEvents, MediaPosition, PostEvent, PostNavigationEvent, ProductEvent, } from '../types'; import { Debug } from '../debug'; import { AnalyticsEventData, Analytics } from './analytics'; const createWidgetEvents = (): IWidgetEvents => { let destroyed = false; const eventCallbacks: { [key in EventMapKeys]?: EventCallbacks } = {}; // Event types that are not sent to AdAlong Analytics const ignoredEventsAnalytics: Set = new Set(['minContentReached']); const getCallbacks = (eventName: K): EventCallbacks => { eventCallbacks[eventName] = eventCallbacks[eventName] || []; return eventCallbacks[eventName] as EventCallbacks; }; // eslint-disable-next-line max-len const shouldIgnoreEvent = (eventName: EventMapKeys): boolean => ignoredEventsAnalytics.has(eventName); const formatEventForAnalytics = ( eventName: K, event: EventMap[K], widgetId: string, ): AnalyticsEventData => { switch (eventName) { case 'thumbnailUnavailable': case 'thumbnailHover': case 'thumbnailLoaded': case 'originalPostOpened': case 'socialProfileOpened': case 'postOpened': case 'postNavigation': case 'postClosed': case 'stlOpened': case 'stlNavigation': case 'stlClosed': case 'mobilePlayerOpened': case 'mobilePlayerNavigation': case 'mobilePlayerClosed': case 'videoPlayed': return { event_widget_id: widgetId, event_post_id: (event as PostEvent).post.id, }; case 'thumbnailViewed': return { event_widget_id: widgetId, event_post_id: (event as PostEvent & MediaPosition).post.id, event_position: (event as PostEvent & MediaPosition).position, }; case 'thumbnailClicked': return { event_widget_id: widgetId, event_post_id: (event as PostEvent & MediaPosition).post.id, event_position: (event as PostEvent & MediaPosition).position, }; case 'productViewed': return { event_widget_id: widgetId, event_post_id: (event as PostEvent).post.id, event_product_id: (event as ProductEvent).product, }; case 'productClicked': return { event_widget_id: widgetId, event_post_id: (event as PostEvent).post.id, event_product_id: (event as ProductEvent).product, }; case 'packshotClicked': return { event_widget_id: widgetId, event_post_id: (event as PostEvent).post.id, event_product_id: (event as ProductEvent).product, }; return { event_widget_id: widgetId, event_post_id: (event as PostNavigationEvent).post.id, event_direction: (event as PostNavigationEvent).direction, }; case 'carouselArrowClicked': case 'carouselNativeScroll': default: return { event_widget_id: widgetId, }; } }; const onEvent = ( eventName: K, callback: EventCallback, ): void => { if (destroyed) return; Debug.try(() => { const callbacks = getCallbacks(eventName); if (!callbacks.includes(callback)) { callbacks.push(callback); } }).catch((error) => { console.error('Failed to add event callback:', error); }); }; const offEvent = ( eventName: K, callback?: EventCallback, ): void => { Debug.try(() => { if (callback) { const callbacks = getCallbacks(eventName); const index = callbacks.indexOf(callback); if (index > -1) { callbacks.splice(index, 1); } } else { eventCallbacks[eventName] = []; } }).catch((error) => { console.error('Failed to remove event callback:', error); }); }; const hasSubscribed = (eventName: EventMapKeys): boolean => !!eventCallbacks[eventName]?.length; const triggerEvent = ( eventName: K, event: EventMap[K], widgetId?: string, ): void => { if (destroyed) return; const callbacks = getCallbacks(eventName); callbacks.forEach((callback) => callback(event)); if (widgetId && !shouldIgnoreEvent(eventName)) { const data = formatEventForAnalytics(eventName, event, widgetId); Analytics.trackEvent({ eventName, data }); } }; const destroy = (): void => { Object.keys(eventCallbacks).forEach((key) => { eventCallbacks[key as EventMapKeys] = []; }); destroyed = true; }; return { onEvent, offEvent, hasSubscribed, triggerEvent, destroy, }; }; export default createWidgetEvents;