diff --git a/src/index.ts b/src/index.ts index 07534ed..e708f37 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,8 +1,11 @@ import { ComponentSettings, Manager, MCEvent } from '@managed-components/types' import { getFinalURL } from './requestBuilder' -import { countConversion, countPageview } from './utils' - -const SESSION_DURATION_IN_MIN = 30 +import { + computeEngagementDuration, + countConversion, + countPageview, + sendUserEngagementEvent, +} from './utils' const sendGaAudiences = ( event: MCEvent, @@ -66,102 +69,73 @@ const sendGaAudiences = ( client.fetch(finalDoubleClickURL) } } +export const sendEvent = async ( + eventType: string, + event: MCEvent, + settings: ComponentSettings, + manager: Manager +) => { + const { client } = event + const { finalURL, requestBody } = getFinalURL(eventType, event, settings) + console.log( + '🚀🚀🚀🚀🚀🚀🚀🚀 final URL is here and send event fires', + finalURL + ) + console.log('🚀🚀🚀🚀🚀🚀🚀🚀 also manager.fetch is working: ', manager.fetch) + manager.fetch(finalURL, { + headers: { 'User-Agent': client.userAgent }, + }) -export default async function (manager: Manager, settings: ComponentSettings) { - const sendEvent = async ( - eventType: string, - event: MCEvent, - settings: ComponentSettings - ) => { - const { client } = event - const { finalURL, requestBody } = getFinalURL(eventType, event, settings) - - manager.fetch(finalURL, { - headers: { 'User-Agent': client.userAgent }, - }) - - if (settings['ga-audiences'] || event.payload['ga-audiences']) { - sendGaAudiences(event, settings, requestBody) - } - - client.set('let', Date.now().toString()) // reset the last event time + if (settings['ga-audiences'] || event.payload['ga-audiences']) { + sendGaAudiences(event, settings, requestBody) } +} - const onVisibilityChange = - (settings: ComponentSettings) => (event: MCEvent) => { - const { client, payload } = event - - if (payload.visibilityChange[0].state == 'visible') { - event.client.set( - 'engagementStart', - payload.visibilityChange[0].timestamp - ) - } else if (payload.visibilityChange[0].state == 'hidden') { - // on pageblur - computeEngagementDuration(event) - - const msSinceLastEvent = Date.now() - parseInt(client.get('let') || '0') // _let = "_lastEventTime" - if (msSinceLastEvent > 10000) { - // order matters so engagement duration is set before dispatching the hit - computeEngagementDuration(event) - - sendEvent('user_engagement', event, settings) - - // Reset engagementDuration after event has been dispatched so it does not accumulate - event.client.set('engagementDuration', '0') - } - } +const onVisibilityChange = + (settings: ComponentSettings, manager: Manager) => (event: MCEvent) => { + const { client, payload } = event + if (payload.visibilityChange[0].state == 'visible') { + client.set('engagementStart', payload.visibilityChange[0].timestamp) + } else if (payload.visibilityChange[0].state == 'hidden') { + // when visibilityChange status changes to hidden, fire `user_engagement` event + sendUserEngagementEvent(event, settings, manager) } + } - const computeEngagementDuration = (event: MCEvent) => { - const now = new Date(Date.now()).getTime() +export default async function (manager: Manager, settings: ComponentSettings) { + manager.createEventListener( + 'visibilityChange', + onVisibilityChange(settings, manager) + ) - let engagementDuration = - parseInt(event.client.get('engagementDuration') || '0') || 0 - let engagementStart = - parseInt(event.client.get('engagementStart') || '0') || now - const delaySinceLast = (now - engagementStart) / 1000 / 60 + manager.addEventListener('pageview', event => { + // this line does not trigger visibilityChange after a pagview, it will start triggering events only on the fist change to hidden + event.client.attachEvent('visibilityChange') - // Last interaction occured in a previous session, reset engagementStart - if (delaySinceLast > SESSION_DURATION_IN_MIN) { - engagementStart = now + // if engagement duration is >1 send a user_engagement event before pageview, to count the time on previous page properly + const engagementDuration = + parseInt(String(event.client.get('engagementDuration')), 10) || 0 + if (engagementDuration >= 1) { + sendUserEngagementEvent(event, settings, manager) } - - engagementDuration += now - engagementStart - - event.client.set('engagementDuration', `${engagementDuration}`) - // engagement start gets reset on every new pageview or event + const now = new Date(Date.now()).getTime() event.client.set('engagementStart', `${now}`) - } + // Reset engagementDuration after pageview has been dispatched so it restarts the count + event.client.set('engagementDuration', '0') + // count pageviews for 'seg' value + countPageview(event.client) - manager.createEventListener('visibilityChange', onVisibilityChange(settings)) + sendEvent('page_view', event, settings, manager) + }) manager.addEventListener('event', event => { // count conversion events for 'seg' value countConversion(event) // order matters so engagement duration is set before dispatching the hit - computeEngagementDuration(event) - - sendEvent('event', event, settings) + computeEngagementDuration(event, settings) - // Reset engagementDuration after event has been dispatched so it does not accumulate - event.client.set('engagementDuration', '0') - }) - - manager.addEventListener('pageview', event => { - event.client.attachEvent('visibilityChange') - - // count pageviews for 'seg' value - countPageview(event.client) - // order matters so engagement duration is set before dispatching the hit - - computeEngagementDuration(event) - - sendEvent('page_view', event, settings) - - // Reset engagementDuration after event has been dispatched so it does not accumulate - event.client.set('engagementDuration', '0') + sendEvent('event', event, settings, manager) }) manager.addEventListener('ecommerce', async event => { @@ -169,11 +143,8 @@ export default async function (manager: Manager, settings: ComponentSettings) { // count conversion events for 'seg' value countConversion(event) // order matters so engagement duration is set before dispatching the hit - computeEngagementDuration(event) + computeEngagementDuration(event, settings) - sendEvent('ecommerce', event, settings) - - // Reset engagementDuration after event has been dispatched so it does not accumulate - event.client.set('engagementDuration', '0') + sendEvent('ecommerce', event, settings, manager) }) } diff --git a/src/requestBuilder.ts b/src/requestBuilder.ts index 58fa8b7..5ad7984 100644 --- a/src/requestBuilder.ts +++ b/src/requestBuilder.ts @@ -1,4 +1,4 @@ -import { ComponentSettings, MCEvent } from '@managed-components/types' +import { Client, ComponentSettings, MCEvent } from '@managed-components/types' import { buildProductRequest, EVENTS, @@ -9,6 +9,16 @@ import { flattenKeys, getParamSafely } from './utils' const getRandomInt = () => Math.floor(2147483647 * Math.random()) +const firstPageEvent = (client: Client) => { + const countedEvent = client.get('countedEvent') + if (countedEvent) { + console.log('🥑🥑🥑🥑🥑🥑🥑 this is not the first event!') + return false + } else { + console.log('🥑🥑🥑🥑🥑🥑🥑 this is the FIRST event!') + return true + } +} function getToolRequest( eventType: string, event: MCEvent, @@ -108,8 +118,29 @@ function getToolRequest( //const notTheFirstSession = parseInt(requestBody['_s'] as string) > 1 const engagementDuration = parseInt(String(client.get('engagementDuration')), 10) || 0 - if (engagementDuration) { + + // include _et parameter for engagement time metrics + if ( + (eventType === 'event' || eventType === 'ecommerce') && + firstPageEvent(client) + ) { + requestBody._et = engagementDuration + console.log( + '💡💡💡💡💡💡💡💡 event/ecommerce includes _et: ', + requestBody._et + ) + // mark first event to avoid sending _et with upcoming events on that page + event.client.set('countedEvent', '1', { scope: 'page' }) + // Reset engagementDuration after event has been dispatched so it does not accumulate + event.client.set('engagementDuration', '0') + } else if (eventType === 'user_engagement') { requestBody._et = engagementDuration + // Reset engagementDuration after event has been dispatched so it does not accumulate + event.client.set('engagementDuration', '0') + console.log( + '🐝🐝🐝🐝🐝🐝🐝 user_engagement includes _et: ', + requestBody._et + ) } /* Start of gclid treating */ diff --git a/src/utils.ts b/src/utils.ts index 92313e7..c99cdb9 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,4 +1,11 @@ -import { Client, MCEvent } from '@managed-components/types' +import { + Client, + ComponentSettings, + MCEvent, + Manager, +} from '@managed-components/types' +import { sendEvent } from '.' +import { Settings } from 'http2' export const flattenKeys = (obj: { [k: string]: unknown } = {}, prefix = '') => Object.keys(obj).reduce((acc: { [k: string]: unknown }, k) => { @@ -70,3 +77,36 @@ export const countConversion = (event: MCEvent) => { }) } } + +export const computeEngagementDuration = ( + event: MCEvent, + settings: ComponentSettings +) => { + const SESSION_DURATION_IN_MIN = settings.sessionLength || 30 // inactivity time gap between sessions (in min) + + const now = new Date(Date.now()).getTime() + + let engagementDuration = + parseInt(event.client.get('engagementDuration') || '0') || 0 + let engagementStart = + parseInt(event.client.get('engagementStart') || '0') || now + const delaySinceLast = (now - engagementStart) / 1000 / 60 + + // Last interaction occured in a previous session, reset engagementStart + if (delaySinceLast > SESSION_DURATION_IN_MIN) { + engagementStart = now + } + + engagementDuration += now - engagementStart + event.client.set('engagementDuration', `${engagementDuration}`) +} + +export const sendUserEngagementEvent = ( + event: MCEvent, + settings: Settings, + manager: Manager +) => { + computeEngagementDuration(event, settings) + + sendEvent('user_engagement', event, settings, manager) +}