From 8a5c87bd5774ad6c06d04a015a2dd79d8dc73abd Mon Sep 17 00:00:00 2001 From: Tim Yiu <137842098+tyiuhc@users.noreply.github.com> Date: Wed, 17 Dec 2025 12:45:05 -0800 Subject: [PATCH 1/6] add time_on_page trigger --- packages/experiment-tag/src/message-bus.ts | 2 ++ packages/experiment-tag/src/subscriptions.ts | 22 +++++++++++++++++++- packages/experiment-tag/src/types.ts | 5 +++++ 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/packages/experiment-tag/src/message-bus.ts b/packages/experiment-tag/src/message-bus.ts index 14d27fd7..5b51d3e6 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 = { pageId: string }; 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 a1d69f84..7b1a6311 100644 --- a/packages/experiment-tag/src/subscriptions.ts +++ b/packages/experiment-tag/src/subscriptions.ts @@ -6,7 +6,7 @@ import { MessagePayloads, ElementAppearedPayload, ManualTriggerPayload, - MessageType, + MessageType, TimeOnPagePayload, } from './message-bus'; import { DebouncedMutationManager } from './mutation-manager'; import { @@ -15,6 +15,7 @@ import { ManualTriggerValue, PageObject, PageObjects, + TimeOnPageTriggerValue, } from './types'; const evaluationEngine = new EvaluationEngine(); @@ -412,6 +413,20 @@ export class SubscriptionManager { wrapHistoryMethods(); }; + setUpTimeOnPagePublisher = () => { + 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; + setTimeout(() => { + this.messageBus.publish('time_on_page'); + }, durationMs); + } + } + } + }; + private isPageObjectActive = ( page: PageObject, message: MessagePayloads[T], @@ -500,6 +515,11 @@ export class SubscriptionManager { return this.elementVisibilityState.get(observerKey) ?? false; } + case 'time_on_page': { + const pageId = message as TimeOnPagePayload; + return pageId.pageId === page.id; + } + 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; }; From 3fe4ab7168d94562919d7eb33e64965e100b1ff4 Mon Sep 17 00:00:00 2001 From: Tim Yiu <137842098+tyiuhc@users.noreply.github.com> Date: Wed, 17 Dec 2025 13:10:21 -0800 Subject: [PATCH 2/6] update trigger value --- packages/experiment-tag/src/message-bus.ts | 2 +- packages/experiment-tag/src/subscriptions.ts | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/experiment-tag/src/message-bus.ts b/packages/experiment-tag/src/message-bus.ts index 5b51d3e6..dd32830d 100644 --- a/packages/experiment-tag/src/message-bus.ts +++ b/packages/experiment-tag/src/message-bus.ts @@ -17,7 +17,7 @@ export type ElementVisiblePayload = { mutationList: MutationRecord[] }; export type AnalyticsEventPayload = AnalyticsEvent; export type ManualTriggerPayload = { name: string }; export type UrlChangePayload = { updateActivePages?: boolean }; -export type TimeOnPagePayload = { pageId: string }; +export type TimeOnPagePayload = { durationMs: number }; export type MessagePayloads = { element_appeared: ElementAppearedPayload; diff --git a/packages/experiment-tag/src/subscriptions.ts b/packages/experiment-tag/src/subscriptions.ts index 7b1a6311..d9944917 100644 --- a/packages/experiment-tag/src/subscriptions.ts +++ b/packages/experiment-tag/src/subscriptions.ts @@ -6,7 +6,8 @@ import { MessagePayloads, ElementAppearedPayload, ManualTriggerPayload, - MessageType, TimeOnPagePayload, + MessageType, + TimeOnPagePayload, } from './message-bus'; import { DebouncedMutationManager } from './mutation-manager'; import { @@ -71,6 +72,7 @@ export class SubscriptionManager { this.setupUrlChangeReset(); // Initial check for elements that already exist this.checkInitialElements(); + this.setUpTimeOnPagePublisher(); }; /** @@ -516,8 +518,9 @@ export class SubscriptionManager { } case 'time_on_page': { - const pageId = message as TimeOnPagePayload; - return pageId.pageId === page.id; + const triggerValue = page.trigger_value as TimeOnPageTriggerValue; + const durationMs = (message as TimeOnPagePayload).durationMs; + return durationMs >= triggerValue.durationMs; } default: From 6e556ee0ac9bcddd24a5d6ac2bc9e57cff661259 Mon Sep 17 00:00:00 2001 From: Tim Yiu <137842098+tyiuhc@users.noreply.github.com> Date: Wed, 17 Dec 2025 15:11:44 -0800 Subject: [PATCH 3/6] reset timeOnPage on navigation --- packages/experiment-tag/src/message-bus.ts | 2 +- packages/experiment-tag/src/subscriptions.ts | 21 ++++++++++++++++---- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/packages/experiment-tag/src/message-bus.ts b/packages/experiment-tag/src/message-bus.ts index dd32830d..3a0ed1ed 100644 --- a/packages/experiment-tag/src/message-bus.ts +++ b/packages/experiment-tag/src/message-bus.ts @@ -17,7 +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 TimeOnPagePayload = object; export type MessagePayloads = { element_appeared: ElementAppearedPayload; diff --git a/packages/experiment-tag/src/subscriptions.ts b/packages/experiment-tag/src/subscriptions.ts index d9944917..59577481 100644 --- a/packages/experiment-tag/src/subscriptions.ts +++ b/packages/experiment-tag/src/subscriptions.ts @@ -43,6 +43,8 @@ export class SubscriptionManager { private elementVisibilityState: Map = new Map(); private elementAppearedState: Map = new Map(); private activeElementSelectors: Set = new Set(); + private timeOnPageTimeouts: Set> = new Set(); + private timeOnPage: number = 0; constructor( webExperimentClient: DefaultWebExperimentClient, @@ -69,10 +71,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(); }; /** @@ -190,6 +192,7 @@ export class SubscriptionManager { ); this.setupVisibilityPublisher(); this.checkInitialElements(); + this.setUpTimeOnPagePublisher(); }); }; @@ -416,14 +419,25 @@ export class SubscriptionManager { }; setUpTimeOnPagePublisher = () => { + // Clear any existing timeouts first + this.timeOnPageTimeouts.forEach((timeoutId) => clearTimeout(timeoutId)); + this.timeOnPageTimeouts.clear(); + this.timeOnPage = 0; + // 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; - setTimeout(() => { + const timeoutId = setTimeout(() => { + this.timeOnPage = durationMs; this.messageBus.publish('time_on_page'); + this.timeOnPageTimeouts.delete(timeoutId); + clearTimeout(timeoutId); }, durationMs); + this.timeOnPageTimeouts.add(timeoutId); } } } @@ -519,8 +533,7 @@ export class SubscriptionManager { case 'time_on_page': { const triggerValue = page.trigger_value as TimeOnPageTriggerValue; - const durationMs = (message as TimeOnPagePayload).durationMs; - return durationMs >= triggerValue.durationMs; + return this.timeOnPage >= triggerValue.durationMs; } default: From c6ed9a4b3e7be7c9af23db86004559de818e101e Mon Sep 17 00:00:00 2001 From: Tim Yiu <137842098+tyiuhc@users.noreply.github.com> Date: Wed, 17 Dec 2025 15:14:23 -0800 Subject: [PATCH 4/6] fix lint --- packages/experiment-tag/src/subscriptions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/experiment-tag/src/subscriptions.ts b/packages/experiment-tag/src/subscriptions.ts index 59577481..68dff12b 100644 --- a/packages/experiment-tag/src/subscriptions.ts +++ b/packages/experiment-tag/src/subscriptions.ts @@ -44,7 +44,7 @@ export class SubscriptionManager { private elementAppearedState: Map = new Map(); private activeElementSelectors: Set = new Set(); private timeOnPageTimeouts: Set> = new Set(); - private timeOnPage: number = 0; + private timeOnPage = 0; constructor( webExperimentClient: DefaultWebExperimentClient, From 945bbbb1bbc7a80854d504ae7a7732daea11dcc7 Mon Sep 17 00:00:00 2001 From: Tim Yiu <137842098+tyiuhc@users.noreply.github.com> Date: Fri, 19 Dec 2025 15:54:50 -0800 Subject: [PATCH 5/6] remove unnecessary clearTimeout --- packages/experiment-tag/src/subscriptions.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/experiment-tag/src/subscriptions.ts b/packages/experiment-tag/src/subscriptions.ts index 59f557d9..6d492806 100644 --- a/packages/experiment-tag/src/subscriptions.ts +++ b/packages/experiment-tag/src/subscriptions.ts @@ -436,7 +436,6 @@ export class SubscriptionManager { this.timeOnPage = durationMs; this.messageBus.publish('time_on_page'); this.timeOnPageTimeouts.delete(timeoutId); - clearTimeout(timeoutId); }, durationMs); this.timeOnPageTimeouts.add(timeoutId); } From 91f88e1ab4487f241218d8ea2380448ba94328ff Mon Sep 17 00:00:00 2001 From: Tim Yiu <137842098+tyiuhc@users.noreply.github.com> Date: Fri, 19 Dec 2025 16:00:54 -0800 Subject: [PATCH 6/6] remove timeOnPage prop --- packages/experiment-tag/src/message-bus.ts | 2 +- packages/experiment-tag/src/subscriptions.ts | 8 +++----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/packages/experiment-tag/src/message-bus.ts b/packages/experiment-tag/src/message-bus.ts index 3a0ed1ed..dd32830d 100644 --- a/packages/experiment-tag/src/message-bus.ts +++ b/packages/experiment-tag/src/message-bus.ts @@ -17,7 +17,7 @@ export type ElementVisiblePayload = { mutationList: MutationRecord[] }; export type AnalyticsEventPayload = AnalyticsEvent; export type ManualTriggerPayload = { name: string }; export type UrlChangePayload = { updateActivePages?: boolean }; -export type TimeOnPagePayload = object; +export type TimeOnPagePayload = { durationMs: number }; export type MessagePayloads = { element_appeared: ElementAppearedPayload; diff --git a/packages/experiment-tag/src/subscriptions.ts b/packages/experiment-tag/src/subscriptions.ts index 6d492806..87ce8208 100644 --- a/packages/experiment-tag/src/subscriptions.ts +++ b/packages/experiment-tag/src/subscriptions.ts @@ -44,7 +44,6 @@ export class SubscriptionManager { private elementAppearedState: Map = new Map(); private activeElementSelectors: Set = new Set(); private timeOnPageTimeouts: Set> = new Set(); - private timeOnPage = 0; constructor( webExperimentClient: DefaultWebExperimentClient, @@ -423,7 +422,6 @@ export class SubscriptionManager { // Clear any existing timeouts first this.timeOnPageTimeouts.forEach((timeoutId) => clearTimeout(timeoutId)); this.timeOnPageTimeouts.clear(); - this.timeOnPage = 0; // Publish initial time_on_page event this.messageBus.publish('time_on_page'); @@ -433,8 +431,7 @@ export class SubscriptionManager { const triggerValue = page.trigger_value as TimeOnPageTriggerValue; const durationMs = triggerValue.durationMs; const timeoutId = setTimeout(() => { - this.timeOnPage = durationMs; - this.messageBus.publish('time_on_page'); + this.messageBus.publish('time_on_page', { durationMs }); this.timeOnPageTimeouts.delete(timeoutId); }, durationMs); this.timeOnPageTimeouts.add(timeoutId); @@ -533,7 +530,8 @@ export class SubscriptionManager { case 'time_on_page': { const triggerValue = page.trigger_value as TimeOnPageTriggerValue; - return this.timeOnPage >= triggerValue.durationMs; + const triggerPayload = message as TimeOnPagePayload; + return triggerPayload.durationMs >= triggerValue.durationMs; } default: