
Product
Socket Threat Intel Now Shows Which Malware Campaigns Were Blocked or Detected
Socket Threat Intel now shows which malware campaigns were blocked or detected in your organization, with links to scans and Firewall events.
@litemetrics/node
Advanced tools
Self-hosted analytics server for Litemetrics. Includes event collection, query API, site management, GeoIP enrichment, and bot filtering.
npm install @litemetrics/node
import express from 'express';
import { createCollector } from '@litemetrics/node';
const app = express();
app.use(express.json());
const collector = await createCollector({
db: { url: 'http://localhost:8123' }, // ClickHouse (default)
adminSecret: 'your-admin-secret',
geoip: true,
cors: { origins: [] }, // Allow all origins
});
// Event collection endpoint (receives tracker data)
app.post('/api/collect', collector.handler());
// Query API (stats, time series, retention)
app.get('/api/stats', collector.queryHandler());
// Event & user listing
app.all('/api/events', collector.eventsHandler());
app.all('/api/users/*', collector.usersHandler());
// Site management (CRUD)
app.all('/api/sites/*', collector.sitesHandler());
// Serve tracker script
app.use(express.static('node_modules/@litemetrics/tracker/dist'));
app.listen(3000);
const collector = await createCollector({
db: { url: 'http://localhost:8123' },
});
Uses MergeTree for events and ReplacingMergeTree for sites. Tables are auto-created on init.
const collector = await createCollector({
db: { adapter: 'postgres', url: 'postgres://user:pass@localhost:5432/litemetrics' },
});
Full feature parity with the ClickHouse adapter — every metric, time series, top-N query, and retention cohort produces identical results across both backends. Uses native jsonb for properties/traits, indexed (site_id, timestamp) for fast range scans, and a soft-delete column on sites. Tables are auto-created on init. Recommended when you already run Postgres and want one less moving piece.
const collector = await createCollector({
db: { adapter: 'mongodb', url: 'mongodb://localhost:27017/litemetrics' },
});
The collector validates client-supplied event timestamps against server time. Events with timestamps outside the allowed window are dropped by default, preventing data corruption from clients with incorrect system clocks and timestamp-spoofing analytics poisoning.
Defaults: 5 min future tolerance, 24 h past tolerance, mode: 'drop'.
const collector = await createCollector({
db: { url: 'http://localhost:8123' },
timestampSanity: {
// pastMs: 7 * 24 * 60 * 60 * 1000, // widen for long offline queues
// mode: 'clamp', // replace with server-now instead of dropping
onOutOfWindow: ({ reason, offsetMs, event }) => {
metrics.increment('litemetrics.ts_out_of_window', { reason });
},
},
});
Modes:
'drop' (default) — out-of-window and invalid (NaN, missing, non-number) events are silently discarded.'clamp' — replace the timestamp with server-now instead of dropping. Preserves the event body.'off' — pass valid client timestamps through unchanged. Invalid values (NaN, missing, non-number) are still replaced with server-now to avoid breaking downstream storage.If your tracker uses an offline queue that may replay events after long delays, widen pastMs accordingly (e.g., 7 * 24 * 60 * 60 * 1000 for a 7-day queue).
The optional onOutOfWindow(info) callback fires whenever the sanitizer rejects a value (drop or clamp). Use it to wire poisoning signals into your metrics/alerting. info.reason is 'future' | 'past' | 'invalid'; info.offsetMs is how far past the boundary the value was (0 for invalid types). Callback errors are swallowed.
Multi-layer filtering runs on every collected event of a web site. Defaults are
conservative (legitimate traffic is never dropped on a false-positive heuristic) and
tunable per-site via the botFilterMode site field.
isbot list.Mozilla/5.0, missing platform tokens).Sites with type: 'app' run Layer 3 only. Layers 1 and 2 reason about browser
User-Agents, which an app SDK does not send (React Native on Android goes out as
okhttp/<version>, which isbot matches), so on an app site they would only
misfire. A site receiving app SDK traffic must therefore be typed app
(POST / PUT /api/sites with {"type":"app"}); otherwise it keeps being
filtered as browser traffic and loses its Android events. When app SDK payloads
arrive at a non-app site the collector fires onSiteTypeMismatch once per site
(reporting only — the payload never bypasses the filter).
Modes: off, standard (default — Layer 1 drops, Layers 2 & 3 flag; on an app
site nothing runs), strict (every layer drops; app site: rate limit only),
shadow (every layer flags only). Every detection reports both the layer that
fired and a finer reason — the signature layer fires for a missing User-Agent
(empty-ua) and for an isbot list match (ua-signature), and those call for
different responses from an operator. Pass per-collector via botFilter:
const collector = await createCollector({
db: { url: 'http://localhost:8123' },
botFilter: {
defaultMode: 'standard', // server-wide default (off | standard | strict | shadow)
rateLimitWindowMs: 60_000, // sliding window for Layer 3
rateLimitMaxEvents: 60, // max events / window / IP
onBotDetected: (info) => {
// info: { siteId, ip, userAgent, layer, reason, action, mode }
// reason: 'empty-ua' | 'ua-signature' | 'no-browser-signals' | 'rate-limit'
console.log(`[bot-filter] ${info.action} layer=${info.layer} reason=${info.reason} mode=${info.mode} site=${info.siteId} ip=${info.ip} ua="${info.userAgent}"`);
},
onSiteTypeMismatch: (info) => {
// info: { siteId, siteType, platform, mode } — fired once per site when app SDK
// events arrive at a site whose type is not 'app'. Fix: PUT /api/sites/:id {"type":"app"}
console.warn(`[site-type-mismatch] site=${info.siteId} type=${info.siteType ?? 'unset'} platform=${info.platform} mode=${info.mode}`);
},
},
});
Server wrapper env vars (apps/server):
BOT_FILTER_MODE (default standard): one of off / standard / strict / shadow. Controls server-wide bot filtering for sites that don't override per-site.BOT_RATE_WINDOW_MS (default 60000): sliding-window size for the per-IP rate limiter (ms).BOT_RATE_MAX (default 60): max events per window per IP before the rate-limit layer fires.BOT_LOG_MAX_PER_MIN (default 20): detail [bot-filter] log lines allowed per minute; the overflow is counted as suppressed= on the [collect] summary line.Read endpoints (/api/stats, /api/events, /api/users) exclude flagged
events by default. Pass ?includeBots=true to include them.
const { deleted } = await db.deleteUserEvents('site_abc', 'user_123');
// or via HTTP:
// DELETE /api/users/:identifier/events?siteId=site_abc
// Headers: X-Litemetrics-Admin-Secret OR X-Litemetrics-Secret (matching site)
// Returns: { ok: true, deleted: <number> }
Resolves both userId and visitorId through the identity map; idempotent.
// Track events from your backend
await collector.track('site-id', 'Purchase', { amount: 99 }, { userId: 'user-123' });
// Identify users
await collector.identify('site-id', 'user-123', { plan: 'pro' });
Litemetrics automatically links anonymous visitors to identified users. After a
user is identified, subsequent events are merged across all linked visitor IDs.
GET /api/users/:identifier accepts either a visitorId or a userId and
returns a merged profile when available.
If you already have identify events, run the backfill script once to populate the identity map:
bun packages/node/src/backfill-identity.ts --adapter clickhouse --url http://localhost:8123
| Metric | Description |
|---|---|
pageviews | Total page views |
visitors | Unique visitors |
sessions | Unique sessions |
events | Custom events count |
conversions | Conversion events count |
top_pages | Most visited pages |
top_referrers | Top traffic sources |
top_countries | Visitors by country |
top_cities | Visitors by city |
top_events | Most common custom events |
top_conversions | Most common conversion events |
top_exit_pages | Last page in session |
top_transitions | Page-to-page transitions |
top_scroll_pages | Pages with most scroll depth events |
top_button_clicks | Most clicked buttons (auto events) |
top_link_targets | Most clicked link targets |
top_browsers | Browser breakdown |
top_os | OS breakdown |
top_devices | Device type breakdown |
MIT
FAQs
Node.js analytics collector with DB adapters and query API
The npm package @litemetrics/node receives a total of 7 weekly downloads. As such, @litemetrics/node popularity was classified as not popular.
We found that @litemetrics/node demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.

Product
Socket Threat Intel now shows which malware campaigns were blocked or detected in your organization, with links to scans and Firewall events.

Security News
Fixes for eMudhra and Brotli are out. Getting them everywhere they’re needed means tracking down bundled libraries, runtimes, and certificate stores.

Threat Research
/Security News
Socket uncovered secret-stealing workflows that ran in LiteLLM’s GitHub repository 11 days before the supply chain attack on PyPI and Docker.