@kirillkychkin/auto-tracker
v0.1.9
Published
Framework-agnostic auto-instrumentation of user interactions for web analytics. Heuristic target_id generation, click/change/submit/view events, Yandex.Metrika adapter.
Downloads
403
Maintainers
Readme
auto-tracker
Framework-agnostic auto-instrumentation of user interactions for web analytics.
It listens to DOM events, decides what is worth tracking with a 4-level
heuristic, builds a structured context (element → container → section → page),
derives a stable target_id, and forwards everything to an analytics provider
(Yandex.Metrika adapter included). No per-element wiring required.
Install
npm i @kirillkychkin/auto-trackerQuick start
import { createTracker, YandexMetrikaAdapter } from '@kirillkychkin/auto-tracker';
createTracker({
provider: new YandexMetrikaAdapter({ counterId: 12345, debug: true }),
visibility: { dwellMs: 1500 },
});createTracker(config) creates a Tracker and calls init() immediately. If you
need deferred activation (e.g. after a consent prompt), construct new Tracker(config)
and call init() yourself.
Without a provider, events are still logged to a local store — handy for
exploring what the tool would track before wiring up an analytics system.
What gets tracked
Four event types are captured automatically:
| Event | When |
|----------|------|
| click | a click on an interactive or heuristically clickable element |
| change | a form field value changed (input / select / textarea) |
| submit | a form was submitted |
| view | a page section became visible (visibility tracking) |
An element is selected for tracking by a 4-level heuristic (most → least reliable):
- Explicit markup —
data-trackattribute. - HTML interactive content —
button,a[href], form controls, etc. - ARIA widget roles —
role="button",role="tab", … - Visual heuristics — pointer cursor / click affordance (least reliable).
Each event carries a stable target_id derived from the context layers. Format:
route__section__container__element (and route__section_heading__section_tag for
view). Cyrillic is transliterated to snake_case, the id is capped at 128 chars
with an FNV-1a hash suffix on overflow.
Event context
For every event the tracker builds an EventContext from layers ordered from the
specific to the general — element → container → section → page — plus params:
element— the event target itself:type— what the heuristic decided:button,input:email,role:tab,visual:clickable,explicit, …text— a human-readable label (cascade:data-track-label→ native text /aria-label/placeholder/ … , truncated tomaxLabelLength), ornull.state— type-specific state, present only forchange/submit/view(aclickhas no meaningful state). See below.
container— the nearest semantic block parent (form, fieldset, dialog, tabpanel, a heuristicdiv, …):{ type, heading }, ornullif none.section— a top-level page block (section/main/nav/header/footer/aside):{ tag, heading }, whereheadingis the firsth1–h6inside it (or thedata-track-sectionvalue).page—{ route: location.pathname, title: document.title }.params— collected fromdata-track-param-*attributes up the ancestor chain.
element.state
A discriminated union keyed by kind, chosen by event type:
| kind | Fields | Used for |
|----------|--------|----------|
| text | filled, invalid? | free-text inputs / textarea (value is not captured — PII) |
| toggle | checked, value, invalid? | checkbox / radio |
| select | value, selectedText, invalid? | <select> |
| scalar | value, filled, invalid? | typed inputs (date, time, color, range, file…) |
| form | filledFields, totalFields, submitter | submit event |
| view | trigger (scroll/tab/render/modal) | view event |
Validation flag (aria-invalid)
Field kinds (text / toggle / select / scalar) carry an optional
invalid?: boolean, read from the element's aria-invalid attribute — a
framework-agnostic validation signal:
- Normalized per the HTML spec:
"false"→false; any other value ("true","grammar","spelling", empty string) →true. - If the attribute is absent,
invalidis omitted entirely — this distinguishes "not validated" from an explicitaria-invalid="false".
Example
A click on a "Buy" button produces this TrackedEvent:
{
"targetId": "catalog__catalog__product_card__buy",
"eventType": "click",
"context": {
"element": { "type": "button", "text": "Buy" },
"container": { "type": "heuristic", "heading": "Product card" },
"section": { "tag": "section", "heading": "Catalog" },
"page": { "route": "/catalog", "title": "Catalog — Shop" },
"params": { "action": "buy", "page": "catalog" }
},
"timestamp": 1716200000000
}A change on an invalid email field — note the state with invalid:
"element": {
"type": "input:email",
"text": "Email",
"state": { "kind": "text", "filled": true, "invalid": true }
}Config — TrackerConfig
All fields are optional; the tracker works out of the box with defaults.
| Option | Type | Default | Description |
|-------------------|-------------------------------|--------------|-------------|
| root | HTMLElement \| Document | document | Root for event delegation (e.g. a shadow root). |
| trackDeadClicks | boolean | false | Also record clicks on elements no heuristic level recognized — useful for debugging "dead" UI zones. |
| maxLabelLength | number | 50 | Max length of human-readable labels (element/section/container text). |
| provider | ProviderAdapter | — | Analytics adapter. If omitted, events go only to the local store. |
| visibility | VisibilityConfig | see below | Visibility (view) tracking settings. |
VisibilityConfig
| Option | Type | Default | Description |
|-------------|-----------|---------|-------------|
| enabled | boolean | true | Set false to disable visibility tracking. |
| threshold | number | 0.5 | Viewport intersection ratio (0..1). IAB/MRC recommends 0.5. |
| dwellMs | number | 1000 | Continuous visible time before firing view. IAB/MRC: 1000 ms. |
Markup configuration (data-* attributes)
The heuristics work without any markup, but these attributes let you refine or force the tool's decisions:
| Attribute | Purpose |
|--------------------------|---------|
| data-track | Force-track an element (heuristic level 1, matched via closest('[data-track]')), even if no heuristic recognized it. |
| data-track-type | Override the detected element type (used together with data-track). |
| data-track-label | Explicit human-readable label for an element or form (takes priority over the text cascade). |
| data-track-container | Mark an element as a container; the attribute value becomes container.heading (no truncation), container type → explicit. |
| data-track-section | Mark an element as a section and a visibility unit; value becomes section.heading, type → explicit. The most stable signal for view. |
| data-track-param-<key> | Custom event params, collected up the ancestor chain; <key> becomes a key in context.params. Can even override target_id / page via data-track-param-target_id. |
Example:
<section data-track-section="Catalog" data-track-param-page="catalog">
<button data-track-param-action="buy">Buy</button>
</section>Yandex.Metrika adapter
new YandexMetrikaAdapter({ counterId: 12345, debug: false });| Option | Type | Description |
|-------------|-----------|-------------|
| counterId | number | Counter id from the Metrika snippet. |
| debug | boolean | If true, every event is logged to console.log regardless of whether window.ym is present — a universal debug switch for any host project. |
The adapter maps event types to a fixed set of generic goals
(auto_click / auto_change / auto_submit / auto_view) and sends them via
ym(counterId, 'reachGoal', goal, params). The full context is flattened into
state_* and custom params (Metrika takes flat key-value pairs). Using four
generic goals — instead of creating a goal per unique target_id — is deliberate:
the Management API needs an OAuth token (out of scope), and Metrika funnels work
natively with one goal + a param filter.
If window.ym is not present, the adapter silently skips sending (and still logs
in debug mode).
API
| Export | Kind | Description |
|------------------------|----------|-------------|
| createTracker | function | Create + init() a tracker. Recommended entry point. |
| Tracker | class | Direct access for deferred activation (new Tracker(cfg) + init()). |
| generateTargetId | function | Pure target_id calculator: (context, eventType) => string. Useful for tests / custom pipelines. |
| YandexMetrikaAdapter | class | Built-in Yandex.Metrika provider. |
| ProviderAdapter | type | Contract for custom adapters. |
Plus exported types: TrackerConfig, TrackedEvent, EventType, EventContext,
ElementInfo, ElementState, ContainerInfo, SectionInfo, PageInfo,
HeuristicResult, VisibilityConfig, VisibilityTrigger, YandexMetrikaAdapterOptions.
Custom adapters
Implement the ProviderAdapter contract to forward events anywhere:
import { createTracker, type ProviderAdapter, type TrackedEvent } from '@kirillkychkin/auto-tracker';
class ConsoleAdapter implements ProviderAdapter {
sendEvent(event: TrackedEvent): void {
console.log(event.eventType, event.targetId, event.context);
}
}
createTracker({ provider: new ConsoleAdapter() });One tracker uses one adapter; for fan-out to several systems, wrap them in a composite adapter.
License
MIT © Kirill Kychkin
