Design and Implementation of Monitor and Monitor‑Tracer SDKs for Frontend Event Tracking
This article explains the architecture of a complete event‑tracking system, introduces two kinds of front‑end events, and details the technical design and implementation of the monitor‑tracer SDK for page visibility/active time as well as the monitor SDK for custom trigger events, including lifecycle monitoring, DOM observation, decorators, and React hooks.
Background
A full event‑tracking system consists of three parts: the application, the data‑analysis platform, and the data‑platform SDK. Event reporting uploads application‑level events to the platform, enabling analysts and product managers to optimise the product.
Two Types of Front‑End Events
Page events are global, injected once at initialisation, while trigger events are custom and need manual injection in code.
SDK for Page Events – monitor‑tracer
monitor‑tracer is a front‑end SDK that records page visibility time, page active time, and component visibility time.
Key Terminology
Page – identified by location.pathname
Page visible time – cumulative time the page is visible to the user
Page active time – cumulative time the user interacts with the page
Component – a DOM element group within a page
Component visible time – cumulative time a component is visible
Page Visibility and Activity Statistics
The SDK distinguishes four states: visible / invisible for visibility and active / inactive for activity. By recording timestamps of state changes, it computes total visible and active durations.
Getting Page Visibility Data
The SDK relies on Google’s Page Lifecycle API (2018) to listen to lifecycle states such as active , passive , hidden , frozen , terminated , and discarded . Mapping these states to visibility yields the visible or invisible flag.
import lifecycleInstance, { StateChangeEvent } from "@byted-cg/page-lifecycle-typed";
lifecycleInstance.addEventListener("statechange", (event: StateChangeEvent) => {
switch (event.newState) {
case "active":
case "passive":
// page visible, do something
break;
case "hidden":
case "terminated":
case "frozen":
// page invisible, do something else
break;
}
});Two shortcomings of PageLifecycle.js are addressed: (1) it cannot detect SPA route changes, so the SDK listens to popstate and replacestate events and treats them as terminated ; (2) it cannot capture the discarded state, so the SDK stores the current statistics in localStorage when the page becomes invisible and flushes them when the page becomes visible again.
Getting Page Activity Data
The SDK listens to six user‑interaction events ( keydown , mousedown , mouseover , touchstart , touchend , scroll ) to mark the page as active . If none of these fire for a configurable timeout (default 30 s) or the page is invisible, the SDK marks it as inactive and records the timestamp.
Component Visibility Statistics
Component tracking requires (1) collecting DOM elements that need monitoring and (2) observing them. The SDK uses MutationObserver to watch DOM changes:
const observer = new MutationObserver(function (mutations, observer) {
mutations.forEach(function (mutation) {
console.log(mutation.target); // target: changed DOM node
});
});
observer.observe(document.documentElement, {
childList: true,
attributes: true,
characterData: true,
subtree: true,
attributeOldValue: false,
characterDataOldValue: false,
attributeFilter: false,
});
observer.disconnect(); // stop observingElements that should be tracked carry a monitor-pv or data-monitor-pv attribute. For component libraries that strip custom attributes, the Babel plugin @byted-cg/babel-plugin-tracer wraps such elements with a custom <monitor> tag and converts the attribute to a JSON string.
import { Card } from 'antd';
const Component = () => {
return
HAHA
;
};After the plugin, the compiled DOM becomes:
<monitor is="custom" data-monitor-pv='{"event":"component_one_pv","params":{...}}'>
<div class="ant-card">...</div>
</monitor>Determining Component Visibility
The SDK checks three dimensions: (1) whether the component intersects the viewport using IntersectionObserver , (2) CSS visibility via display , visibility , and opacity , and (3) page visibility. If any dimension is invisible, the component is marked invisible .
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
// entry.isIntersecting, entry.intersectionRatio, etc.
});
}, { threshold: [0, 0.25, 0.5, 0.75, 1] });
observer.observe(document.getElementById("img"));
observer.disconnect();SDK for Trigger Events – monitor
The monitor SDK decouples event reporting from business logic. It supports three injection methods:
Class‑Directive Injection
Attributes such as monitor-click or data-monitor-hover are scanned on the event target and its ancestors; matching directives cause the associated JSON payload to be sent.
<Button monitor-click={JSON.stringify({type:"func_operation",params:{value:3}})}>Click Me</Button>Decorator Injection
Using TypeScript decorators (e.g., @monitorBefore ), a function is wrapped so that the SDK sends a prepared event before executing the original logic.
@monitorBefore((value: string) => ({
type: 'func_operation',
params: { keyword: value }
}))
handleSearch() {
console.log('[Decorators Demo]: this should happen AFTER a monitor event is sent.');
}React Hook Injection
The useMonitor hook works in functional components, returning a new function that first reports the event and then runs the business callback.
const handleChange = useMonitor(
(value: string) => { console.log('The user entered', value); },
(value: string) => ({ type: "func_operation", params: { value } })
);Summary of Injection Methods
Class directives are simple and work for any DOM element; decorators provide pre‑processing for class components; hooks extend the same capability to functional components, covering all major front‑end frameworks.
Technical Stack
Programming language – TypeScript
Page‑lifecycle listening – PageLifecycle.js
Event emitter – wolfy87-eventemitter
DOM mutation observation – MutationObserver
Viewport intersection – IntersectionObserver
Credits
monitor SDK – Lilly Jiang
monitor‑tracer SDK – Aiqing Dong (page visibility & Babel plugin) and Yuling Chen (component visibility)
References
Links to Google’s Page Lifecycle API, MDN documentation for MutationObserver and IntersectionObserver , the internal ByteDance packages, and source repositories are provided.
ByteFE
Cutting‑edge tech, article sharing, and practical insights from the ByteDance frontend team.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.