Analytics
Loopar includes privacy-friendly, first-party web analytics — no external scripts, no advertising cookies.
How It Works
Every public web page automatically renders a TrafficTracker: on page load it registers a view, then periodically reports how long the visitor actually engaged with the page.
| Piece | Role |
|---|---|
TrafficTracker | Client component, auto-injected on the web workspace |
Page View entity | One row per page view |
publicActionTrack / publicActionPing | Public endpoints receiving the data |
| Analytics Dashboard | KPIs at Desk → Analytics |
Nothing to configure — it works out of the box on every tenant.
Opt-out
Visiting any URL with ?notrack=1 sets a persistent opt-out for that browser. Logged-in users are recorded as internal traffic and excluded from the dashboard KPIs.
What Gets Tracked
On page load — track
The tracker POSTs to /api/Page View/track with a fresh viewId. The server enriches and stores a Page View row:
| Field | Source |
|---|---|
document, referrer, language, viewport | Sent by the client |
session_id | 30-minute rolling session id (localStorage) |
utm_source / utm_medium / utm_campaign | Taken from the URL |
browser, os, device_type | Parsed server-side from the User-Agent |
country, region, city, timezone, isp | Geo lookup by IP (cached, 2s timeout) |
ip_hash | Salted hash — the raw IP is never stored |
is_internal | 1 when the visitor is logged in |
While reading — ping
Every 15 seconds (and when the tab is hidden or closed, via sendBeacon) the tracker updates the same row through /api/Page View/ping:
active_ms— time the page was visible AND the user was active (mouse/keyboard/scroll within the last 30s)scroll_depth— deepest scroll position reached (0–100%)
Abuse protection
- Bots filtered: requests with bot user-agents (crawler lists + headless/lighthouse patterns) are acknowledged but not stored.
- Rate limits per IP: 30
trackand 120pingper minute — beyond that the endpoint answers429. activeMsis clamped to 24h andscrollDepthto 0–100, so a hostile client can't poison the metrics.
Engagement Model
Classic analytics count a "bounce" as a single-page visit — useless for landing pages. Loopar measures real engagement:
A view is engaged when the visitor was actively on the page for at least 10 seconds (
active_ms ≥ 10000).
Derived KPIs:
| KPI | Definition |
|---|---|
| Engaged rate | engaged views / measured views |
| Bounce rate | 100 − engaged rate |
| Avg. active seconds | Mean active_ms across views |
| Avg. scroll depth | Mean deepest scroll % |
Because activity pauses when the tab is hidden or the user goes idle (30s without input), a tab left open in the background does not inflate engagement.
The Dashboard
Open Analytics Dashboard in the desk. It reads the last N days (1–365, default 30), always excluding internal (logged-in) traffic.
| Block | Contents |
|---|---|
| KPIs | Total views, unique visitors, sessions, pages/session, engaged rate, bounce rate, avg. active seconds, avg. scroll depth — each with % change vs the previous period |
| Visits over time | Daily views/visitors series |
| Top pages | Most viewed documents |
| Audience | Countries, devices, browsers |
| Acquisition | Referrers and UTM campaigns |
| Hourly | Traffic distribution by hour |
Unique visitors are counted by distinct ip_hash, sessions by distinct session_id.
Privacy
- No raw IPs stored. The IP is hashed as
sha256(ip + salt)truncated to 16 characters. The salt comes from theTRACKING_SALTenv var, or is generated once and persisted atsites/.tracking-salt(never committed). - No third-party scripts or cookies. The only client-side state is a session id in
localStorage. - Geo lookup is coarse — country/region/city via IP, private/LAN IPs skipped.
- Opt-out honored (
?notrack=1), bots discarded, internal traffic flagged.
This keeps the data useful for product decisions without profiling individual visitors.