/**
* Base/Abstract class for all events. When the `attach` method is called, it creates an event listener.
* When the event is triggered, it pushes the event payload to the Google Tag Manager `dataLayer`.
*
* - To create a new type of event listener, extend this class and override the `eventName` property.
* - override `listen` to define how the event listener is attached. (optional)
* - override `createPayload` to define how the event data is constructed. (optional)
* - assign `onTrigger` to add side effects when the event is triggered. (optional)
* @class Listener
*/
export default class Listener {
/**
* The name of the event that is pushed to GTM
* @type {String}
* @static
* @memberof Listener
*/
eventName = 'event';
constructor() {
this.effects = [];
}
/**
* Adds a callback to be executed when the event is triggered.
* The callback will have access to the event payload object.
* @param {function(Payload): void} effect
* @memberof Listener
*/
set onTrigger(effect) {
this.effects.push(effect);
}
/**
* Call this method to attach the event listener.
* It wraps the `listen` method and handles any errors thrown.
* This allows us to safely attach event listeners and monitor for failures in Sentry.
* @public
* @method attach
* @returns {void}
* @memberof Listener
*/
attach() {
try {
this.listen();
} catch (e) {
const newError = new Error(`Failed to attach listener: ${this.eventName}\nMessage: ${e.message}`);
captureException(newError);
}
}
/**
* Should not be called directly: use `attach()` instead.
* Defines the logic to trigger the event. Typically this will add an event listener or observer.
* Subclasses should override this method.
* - NOTE: Don't handle errors in this method. Allow them to fail loudly so they can be captured in the `attach` method.
* @public
* @method listen
* @returns {void}
* @memberof Listener
*/
listen() {
this.trigger();
}
/**
* Creates an object of event data to be sent to Google Tag Manager.
* This is called when the event is triggered along with any arguments passed to the `trigger` method.
* Subclasses should override this method.
* - NOTE: Don't handle errors in this method. Allow them to fail loudly so they can be captured in the `trigger` method.
* @public
* @method createPayload
* @memberof Listener
* @param {...any} args - arguments needed to create the payload
* @returns {Payload} - the event data to send to GTM
*/
createPayload() {
return {};
}
/**
* Sends the event payload to Google Tag Manager and calls any side effect callbacks assigned to `onTrigger`.
* This should be called somewhere in the `listen` method when the event is triggered.
* Any args will be passed to the `createPayload` method.
* @private
* @param {...any} args - arguments to pass the `createPayload` method
*/
trigger(...args) {
try {
const payload = {
event: this.eventName,
...this.createPayload(...args)
};
dataLayer.push({ ecommerce: null });
dataLayer.push(payload);
this.effects.forEach(
callback => callback(payload)
);
} catch (error) {
const newError = new Error(`Failed to send event data: ${this.eventName}\nMessage: ${error.message}`);
captureException(newError);
}
}
}
/**
* Handles errors by logging them to the console and sending them to Sentry.
* @param {any} error - the error to capture
*/
function captureException(error) {
console.error(error);
if (typeof Sentry !== 'undefined') Sentry.captureException(error);
}