diff --git a/packages/experiment-tag/src/message-bus.ts b/packages/experiment-tag/src/message-bus.ts index 14d27fd7..dd32830d 100644 --- a/packages/experiment-tag/src/message-bus.ts +++ b/packages/experiment-tag/src/message-bus.ts @@ -17,6 +17,7 @@ export type ElementVisiblePayload = { mutationList: MutationRecord[] }; export type AnalyticsEventPayload = AnalyticsEvent; export type ManualTriggerPayload = { name: string }; export type UrlChangePayload = { updateActivePages?: boolean }; +export type TimeOnPagePayload = { durationMs: number }; export type MessagePayloads = { element_appeared: ElementAppearedPayload; @@ -24,6 +25,7 @@ export type MessagePayloads = { url_change: UrlChangePayload; analytics_event: AnalyticsEventPayload; manual: ManualTriggerPayload; + time_on_page: TimeOnPagePayload; }; export type MessageType = keyof MessagePayloads; diff --git a/packages/experiment-tag/src/subscriptions.ts b/packages/experiment-tag/src/subscriptions.ts index c62c3b9b..87ce8208 100644 --- a/packages/experiment-tag/src/subscriptions.ts +++ b/packages/experiment-tag/src/subscriptions.ts @@ -7,6 +7,7 @@ import { ElementAppearedPayload, ManualTriggerPayload, MessageType, + TimeOnPagePayload, } from './message-bus'; import { DebouncedMutationManager } from './mutation-manager'; import { @@ -15,6 +16,7 @@ import { ManualTriggerValue, PageObject, PageObjects, + TimeOnPageTriggerValue, } from './types'; const evaluationEngine = new EvaluationEngine(); @@ -41,6 +43,7 @@ export class SubscriptionManager { private elementVisibilityState: Map = new Map(); private elementAppearedState: Map = new Map(); private activeElementSelectors: Set = new Set(); + private timeOnPageTimeouts: Set> = new Set(); constructor( webExperimentClient: DefaultWebExperimentClient, @@ -67,9 +70,10 @@ export class SubscriptionManager { this.setupMutationObserverPublisher(); this.setupVisibilityPublisher(); this.setupPageObjectSubscriptions(); - this.setupUrlChangeReset(); // Initial check for elements that already exist this.checkInitialElements(); + this.setUpTimeOnPagePublisher(); + this.setupUrlChangeReset(); }; /** @@ -188,6 +192,7 @@ export class SubscriptionManager { ); this.setupVisibilityPublisher(); this.checkInitialElements(); + this.setUpTimeOnPagePublisher(); }); }; @@ -413,6 +418,28 @@ export class SubscriptionManager { wrapHistoryMethods(); }; + setUpTimeOnPagePublisher = () => { + // Clear any existing timeouts first + this.timeOnPageTimeouts.forEach((timeoutId) => clearTimeout(timeoutId)); + this.timeOnPageTimeouts.clear(); + // Publish initial time_on_page event + this.messageBus.publish('time_on_page'); + + for (const pages of Object.values(this.pageObjects)) { + for (const page of Object.values(pages)) { + if (page.trigger_type === 'time_on_page') { + const triggerValue = page.trigger_value as TimeOnPageTriggerValue; + const durationMs = triggerValue.durationMs; + const timeoutId = setTimeout(() => { + this.messageBus.publish('time_on_page', { durationMs }); + this.timeOnPageTimeouts.delete(timeoutId); + }, durationMs); + this.timeOnPageTimeouts.add(timeoutId); + } + } + } + }; + private isPageObjectActive = ( page: PageObject, message: MessagePayloads[T], @@ -501,6 +528,12 @@ export class SubscriptionManager { return this.elementVisibilityState.get(observerKey) ?? false; } + case 'time_on_page': { + const triggerValue = page.trigger_value as TimeOnPageTriggerValue; + const triggerPayload = message as TimeOnPagePayload; + return triggerPayload.durationMs >= triggerValue.durationMs; + } + default: return false; } diff --git a/packages/experiment-tag/src/types.ts b/packages/experiment-tag/src/types.ts index 42639506..479b53f3 100644 --- a/packages/experiment-tag/src/types.ts +++ b/packages/experiment-tag/src/types.ts @@ -45,6 +45,10 @@ export interface ManualTriggerValue { name: string; } +export interface TimeOnPageTriggerValue { + durationMs: number; +} + export type PageObject = { id: string; name: string; @@ -54,6 +58,7 @@ export type PageObject = { | ElementAppearedTriggerValue | ElementVisibleTriggerValue | ManualTriggerValue + | TimeOnPageTriggerValue | Record; };