Skip to content

Athenox14/Iris

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

24 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Iris

Self-hosted, lightweight product analytics — Rust backend, tiny JS SDK. PostHog-style feature set: event tracking, autocapture, session replay, funnels, retention.

Used in production by OxaDash, the dashboard of OxalisHeberg.fr.

Layout

  • crates/iris-core — pure domain logic: models, funnel/retention math, session boundary rules, user-agent → device/browser/OS detection (automatic, no client work), feature flag / experiment bucketing, A/B stats engine.
  • crates/iris-server — Axum server. GraphQL API (/graphql, async-graphql) for querying, plus a lightweight REST ingestion path (/capture, /batch, /replay) so high-volume writes skip GraphQL parsing overhead. SQLite storage via sqlx (swap the DATABASE_URL for Postgres-compatible storage later without touching the query layer's shape).
  • sdk/iris.js — dependency-free browser tracker (~6kb unminified). Autocapture for pageviews/clicks/form submits, session management, session replay (DOM snapshot + incremental mutation/scroll/mouse/input capture), custom capture() API, batched delivery with sendBeacon on unload.

See DOC.md for a full, non-technical feature walkthrough.

Running

cargo run -p iris-server

Server listens on :8791 (override with PORT). Then open http://localhost:8791/graphql for GraphiQL.

GeoIP (automatic, no signup)

Session IPs are resolved to country/city/coordinates automatically — no MaxMind account or license key needed. On first boot (and weekly after that) the server downloads a free GeoLite2-derived City database from sapics/ip-location-db (both IPv4 and IPv6, cached under ./data/geoip) and reads it locally; no visitor IP is ever sent to a third-party geolocation API. If the download fails (no network egress, air-gapped deployment) the server still starts fine — geo fields just stay empty.

To use your own database instead (e.g. an official combined MaxMind GeoLite2-City/GeoIP2-City .mmdb, for better accuracy or an air-gapped deploy), set GEOIP_DB_PATH and auto-download is skipped entirely:

GEOIP_DB_PATH=/path/to/GeoLite2-City.mmdb cargo run -p iris-server

Query the result once data has flowed in:

query {
  sessionGeo(projectId: "your-project", sessionId: "some-session-id") {
    countryCode
    countryName
    city
    region
    latitude
    longitude
  }
  sessionsByCountry(projectId: "your-project") {
    countryName
    count
  }
}

Embedding in your own site

<script src="https://your-iris-host/sdk/iris.js"></script>
<script>
  Iris.init({
    apiHost: "https://your-iris-host",
    projectId: "your-project",
    autocapture: true,
    sessionReplay: true,
  });
  Iris.capture("custom_event", { foo: "bar" });
  Iris.identify("user-123");
</script>

GraphQL API

Queries: events, eventCounts, sessions, replay, funnel, retention, featureFlags, isFeatureEnabled, experimentVariant, experiments, experimentResults, cohorts, groups, surveys, errors, destinations, exportEventsCsv, runQuery, sessionGeo, sessionsByCountry, project.

Mutations: capture (prefer /capture//batch REST routes for the SDK's own traffic — GraphQL mutation is for server-side/backend integrations), upsertFeatureFlag, upsertExperiment, createCohort, groupIdentify, setPersonProperties, createDestination, createSurvey, upsertProject.

Per-project origin allowlisting

By default a projectId is just a free-form string: any site knowing it can post events under it. To stop site B from posting events under site A's projectId, register the origins A is actually embedded on:

mutation {
  upsertProject(id: "your-project", name: "My Site", allowedOrigins: ["https://example.com"])
}

Once a project has at least one registered origin, /capture, /batch, /replay, and the capture GraphQL mutation reject requests whose Origin (or Referer, as a fallback) header doesn't match one of them, with 403. A project that's never been registered (or registered with an empty allowedOrigins) stays open, so this is opt-in and doesn't break existing deployments.

This is the same mechanism Plausible/PostHog call "site verification" — it stops a page on another site from silently posting analytics in a visitor's browser. It is not a secret: Origin/Referer are just headers, so a non-browser client (curl, a script) that already knows your projectId and expected origin can still set them to match. Treat it as raising the bar against browser-context abuse, not as authentication — the GraphQL API has no auth at all, by design, and is meant to be reached only by trusted/admin callers.

Performance

Benchmarked on SQLite with WAL mode + batched transactions:

Events Time Rate
100,000 ~13s ~7,500/s
1,000,000 ~120s ~7,460/s

DB size: ~190 bytes/event. Server RAM stays under 20MB even at 1M stored events.

Windows build note

MSVC toolchain is required for libsqlite3-sys's C build step. If cc-rs picks up an unrelated CC env var (e.g. from a Python/Triton install), .cargo/config.toml in this repo forces it empty so cc-rs falls back to cl.exe.

What's implemented

Event capture, autocapture, pageviews, session identification, session replay with real incremental DOM diffs (add/remove/attribute/text/input, each targeting a stable per-element id, replayable with a real-time scrubber), funnels, retention, UA-based device/browser/OS detection, channel/referrer classification, GeoIP-based session location (country/city/coordinates, auto-downloaded MaxMind-format mmdb — offline lookups, no signup, bring your own .mmdb optionally), feature flags (consistent-hash rollout %), groups/group-identify, person $set properties, cohorts (event+property filter membership), A/B experiments with weighted variant assignment and a two-proportion z-test significance engine, webhook destinations (fire an HTTP POST on matching events), error tracking (window.onerror + unhandled rejections, grouped by message), web vitals (LCP/CLS), rageclick detection, click heatmap coordinates, surveys (fetch + inline widget + response capture), CSV event export, and a sandboxed read-only SQL insights query (runQuery, events table only, project-scoped, keyword/table denylist).

About

Self-hosted product analytics (Rust + tiny JS SDK) — event tracking, autocapture, session replay, funnels, feature flags, A/B experiments.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Packages

 
 
 

Contributors