import { LocalEvent } from "../../commons/types/local-event"; import { PageType } from "../../commons/types/page-type"; import { Runner } from "../../commons/types/runner"; import { EventEmitter } from "../../commons/utils/event-emitter"; import { Http } from "../../commons/utils/http"; import { LocationUtils } from "../../commons/utils/location-utils"; import { Logger } from "../../commons/utils/logger"; import { SessionPingEvent } from "../../event/types/session/ping"; import { ApiClickEvent } from "../types/conversion/api-click"; import { MailClickEvent } from "../types/conversion/mail-click"; import * as DataLayerEvent from "../types/datalayer/datalayer-event"; // NOTE: Maybe one day we correctly type these. export type DataLayer = any[]; // eslint-disable-line export type PageItem = any; // eslint-disable-line /** * Current page info. * @export * @interface PageInfo */ export interface PageInfo { item: PageItem; pageType: PageType; } /** * Main event runner class. * * It sends a "session.ping" event whenever * the runner starts. * * @export * @abstract * @class EventRunner * @extends {Runner} */ export abstract class EventRunner extends Runner { /** * Do the setting up before the runner is * started by the bundle manager. * * @abstract * @memberof Runner */ public async start(): Promise { const ping = await new SessionPingEvent().push(); if (!Http.ok(ping.status)) { return; } // Clean EventEmitter EventEmitter.off(LocalEvent.DataLayerChanged); this.processDataLayer(window.dataLayer); this.processQuery(); this.interceptPush(); return super.start(); } /** * Main runner method. * * @abstract * @return {Promise} * @memberof Runner */ protected run(): Promise { return; } /** * Process specific items. Should be implemented * by subclasses if necessary. * * @protected * @param {PageItem} _item Current item. * @return {void} * @memberof EventRunner */ protected processItem(_item: PageItem): void { return; } /** * Process biggy specific items, that sould * be common to all types of event runners. * * @private * @param {PageItem} item Current item. * @return {void} * @memberof EventRunner */ private processBiggyItem(item: PageItem): void { const event = DataLayerEvent.fromItem(item); if (event != null) { event.push(); } } /** * Intercept datalayer push method. * * Whenever the push method is called, process the incoming * datalayer before calling the old push method. * * @private * @memberof VTEXRunner */ private interceptPush(): void { if (window.dataLayer == null) { return; } // Create an event listener for the datalayer changed event that will be // sent whenever new items are added to the datalayer. EventEmitter.on(LocalEvent.DataLayerChanged, (items) => this.processDataLayer(items)); // Get old push, and call it after dispatching the datalayer changed event. const push = window.dataLayer.push; // eslint-disable-line window.dataLayer.push = (...items: DataLayer) => { try { EventEmitter.trigger(LocalEvent.DataLayerChanged, items); } catch (err) { Logger.log(err); } return push.apply(window.dataLayer, items); }; } /** * Process current and incoming datalayer. * * @private * @param {DataLayer} dataLayer Datalayer to be processed. * @memberof VTEXRunner */ private processDataLayer(dataLayer: DataLayer): void { if (dataLayer == null) { return; } for (const layer of dataLayer) { this.processBiggyItem(layer); this.processItem(layer); } } /** * Process any query string related events. * * @private * @memberof EventRunner */ private processQuery(): void { const params = LocationUtils.getURLSearchParams(); const apiClick = params[window._RecSys.Bundle.Properties.conversion.campaign]; const mailClick = params[window._RecSys.Bundle.Properties.conversion.mail]; if (apiClick != null && apiClick.length > 0) { ApiClickEvent.fromClickData(apiClick).push(); } if (mailClick != null && mailClick.length > 0) { MailClickEvent.fromClickData(mailClick).push(); } } }