Collectors
Sentinel ships six collectors, each implementing the Collector interface:
interface Collector {
start(): void;
stop(): void;
}
All collectors receive a SignalHandler callback (onSignal) during construction, which they invoke whenever a new signal is captured. Signals are created via the shared createSignal() factory that stamps each with a ULID and the current timestamp.
FetchCollector
Signal type: fetch
Captures outbound HTTP requests that pass through Sentinel's FetchInterceptor.
| Field | Source |
|---|---|
host | URL.host of the request |
url | URL.pathname of the request |
How it works
FetchCollector does not instrument window.fetch itself — instead, the FetchInterceptor calls onFetchCall(input) for every intercepted POST request. The collector extracts the host and pathname, then emits a fetch signal.
SSR guard
start() is a no-op when typeof window === 'undefined'.
ClickCollector
Signal type: click
Captures user click/tap interactions.
| Field | Source |
|---|---|
identifier | Stable selector for the clicked element |
screen | Current window.location.pathname |
Event Listener
Listens on document for pointerdown events (capture phase, passive). Only primary pointer (mouse button 0 or touch) events are processed.
Element Resolution
- Walk
composedPath()to find the firstElement(handles Shadow DOM). - Find the nearest clickable ancestor using
element.closest()against:button,a,input,select,textarea[role="button"],[data-testid],[data-test],[data-id]
Identifier Strategy
The collector builds a stable, human-readable identifier for each click target using the following priority:
| Priority | Strategy | Example |
|---|---|---|
| 1 | Stable data attribute (data-testid, data-test, data-id, name) | button.search-submit |
| 2 | Stable id attribute (non-numeric, reasonably short) | button.main-cta |
| 3 | CSS selector path (up to 4 ancestors) | div.header>button:nth-of-type(2) |
| 4 | Tag name only | button |
Identifiers are cached per-element with a 10-second TTL (WeakMap) and truncated to 200 characters.
Class Name Cleaning
CSS-module hashes (e.g. styles_button_a1b2c) are stripped to their semantic prefix (styles_button). Single/double-letter class names and strings with 5+ consecutive uppercase/digit chars are discarded as unstable.
TrackingCollector
Signal type: tracking
Captures analytics events sent through Traveloka's tracking pipeline.
| Field | Source |
|---|---|
eventName | event field from the tracking payload |
How it works
Like FetchCollector, this collector is not self-instrumenting. The FetchInterceptor calls onFetchCall(input, init) for intercepted requests. TrackingCollector checks if the URL contains /api/v1/tvlk/events and, if so, parses the JSON body to extract event names from data.events[].event.
Parse errors are silently ignored.
NavigationCollector
Signal type: navigation
Tracks client-side page navigations.
| Field | Source |
|---|---|
screenName | window.location.pathname |
Instrumentation
history.pushState/history.replaceState— monkey-patched to call the original then check for path changes.popstateevent — listener onwindowfor back/forward navigation.
A signal is only emitted when the pathname actually changes (deduplication).
Cleanup
stop() removes the popstate listener. Note: pushState/replaceState patches are not reverted (they are applied once and remain for the page lifetime).
CustomCollector
Signal type: custom
Allows domain code to emit arbitrary signals.
| Field | Source |
|---|---|
name | Caller-provided event name |
payload | Caller-provided key-value map |
Public API
// Via the Sentinel instance (window.__SEN)
window.__SEN.collect('checkout_started', { currency: 'IDR' });
collect(name, payload) is a no-op when the collector is inactive.
AppStateCollector
Signal type: appState
Tracks foreground/background transitions of the browser tab.
| Field | Source |
|---|---|
state | 'goes_to_foreground' or 'goes_to_background' |
Detection Strategy
| Browser Support | Mechanism |
|---|---|
document.visibilityState available | visibilitychange event |
| Fallback | focus / blur events on window |
Foreground Callback
When the app returns to foreground, the collector invokes onGoesToForeground(), which triggers reporter.flushIfNeeded(true) — ensuring buffered signals are sent promptly after the user returns.
Deduplication
State changes are deduplicated; the same state emitted consecutively produces only one signal.
Signal Factory
All collectors use the shared createSignal function:
function createSignal<T extends SignalData>(
signalType: SignalType,
signal: T
): Signal<T> {
return {
id: ulid(), // time-sortable unique ID
ts: Date.now(), // creation timestamp (ms)
signalType,
signal,
};
}
The ULID ensures global uniqueness and chronological ordering without coordination.
Last updated: 2026-02-24