@serge-ai/js
v0.2.2
Published
Serge — agent-traffic tracker for the browser. Detect AI-agent sessions on your site, plus a custom-event API. Cookieless. SSR-safe. MIT-licensed.
Maintainers
Readme
@serge-ai/js
No cookies. No localStorage by default. No consent banner required. ePrivacy Art. 5(3) compliant by construction. Visitor identity derived server-side from a daily-rotating salt. Track agent traffic in EU/Swiss/UK markets without the cookie-banner tax.
The vanilla-JS core for Serge. Detects AI-agent sessions on your site (ChatGPT, Claude, Perplexity, Gemini, browser-use, …) and emits custom events from them. SSR-safe, tree-shakeable, MIT-licensed.
pnpm add @serge-ai/js
# or
npm install @serge-ai/jsFor React → install @serge-ai/react instead.
For Next.js → install @serge-ai/nextjs instead.
For plain HTML / WordPress / Shopify (no build step) → use the script-tag snippet.
Quick start
import { init, track } from '@serge-ai/js'
init('srg_pub_xxxxxxxxxxxxxxxxxxxxxxxxxx')
document.querySelector('#checkout')?.addEventListener('click', () => {
track('checkout_started', { plan: 'pro' })
})Plain HTML (no build step)
<script
defer
data-site-id="srg_pub_xxxxxxxxxxxxxxxxxxxxxxxxxx"
src="https://serge.ai/js/s.js"
></script>Same API surface, exposed on window.serge('track', name, props). Pre-queue support means calls before the script loads are buffered and flushed on init.
API
init(token, options?)
Initialize the tracker. Idempotent within a page; SSR-safe (no-op when window isn't present).
init('srg_pub_xxx')
init('srg_pub_xxx', {
apiBase: '/serge', // first-party proxy to dodge ad-blockers (optional)
debug: false, // track every session, not just agents (dev only)
requireConsent: false, // hold tracking until consent('granted')
defaults: '2026-05-10', // pin SDK behavior to a published defaults date
})The token is the public token from your Serge dashboard (srg_pub_*). It's safe to embed in client-side code, OG image URLs, and pixel src attributes. Visually distinct from the future srg_sec_* admin/management API key so a leaked token is unambiguous at a glance.
track(event, properties?)
track('add_to_cart', { sku: 'AA-batteries-10pk', price_chf: 9.9 })- Event name validated server-side:
^[a-z][a-z0-9_]{0,63}$(lowercase snake_case, ≤64 chars) - Properties: optional, capped at ~4 KB JSON
- No-op when not in browser, before
init(), or when detection confidence is below the agent threshold (anddebug=false)
consent(value)
Update consent state when init(token, { requireConsent: true }).
consent('granted') // start tracking
consent('denied') // stop; nothing persisted client-side
consent('unknown') // pending; tracker stays dormantPersisted in localStorage so the choice survives reload — and only then. Default installs (no requireConsent) don't touch storage at all.
pageview()
Manual pageview emit. The tracker auto-tracks navigation via the History API; call this only when your app uses a custom client-router that bypasses pushState/popstate.
First-party proxy (apiBase: '/serge')
Setting apiBase to a path on your own domain dodges browser ad-blockers — the snippet's network requests look like calls to your own infrastructure. Configure your edge / reverse proxy to forward /serge/* to https://serge.ai/*.
// next.config.ts
module.exports = {
async rewrites() {
return [{ source: '/serge/:path*', destination: 'https://serge.ai/:path*' }]
},
}Credentials behavior under proxy. Every regular fetch() call passes credentials: 'omit' so your site cookies are never sent — even when the request becomes same-origin via the proxy. One caveat: navigator.sendBeacon, used as a fallback on page-unload, doesn't accept a credentials option (browser API limitation). Under first-party proxy, the unload flush includes site cookies. Strip them in the proxy layer if your security team requires zero-cookie egress on every wire.
What gets sent to the server
Per session:
session_start: viewport, user-agent, language, agent platform classification, detection signalspage_view/route_change: pathname, scroll depth, time on pagesemantic_action: your custom events (track)session_end: terminal page metrics
What does not get sent:
- Cookies (cookieless by construction)
- IP addresses (used transiently server-side for daily-rotating bucket assignment, never stored)
- Form input values, keystroke content, raw mouse coordinates
- User identifiers (no
vidfield, no localStorage entry, no per-useridentify()API) - Query strings (path only — sensitive query keys also stripped from referrers via the built-in scrub list)
Why no identify()?
PostHog, Mixpanel, Amplitude all ship an identify() API to link events to a user_id. Serge intentionally doesn't — Serge measures agent sessions, not end-users. The cookieless architecture exists precisely because we don't need per-user identity to do our job.
SSR safety
Every browser-API access is guarded behind typeof window !== 'undefined'. Import this package from a Next.js server component, an Edge runtime, or a Node-side test without errors. Side effects only fire after init() is called in a browser context.
Bundle size
Under 6 KB gzipped (≈5 KB Brotli). size-limit enforces this in CI.
Browser support
- Chrome / Edge ≥ 100 (2022)
- Firefox ≥ 100 (2022)
- Safari ≥ 15.4 (2022)
ES2020 build target. No transpiled polyfills for older browsers.
Privacy + compliance
- ePrivacy Art. 5(3) compliant by default — no consent banner required
- GDPR Art. 6(1)(f) legitimate-interest legal basis for security analytics (agent detection)
- No cookies. No localStorage by default
- No third-party data sharing
- Visitor identity derived server-side from a daily-rotating salt; CNIL Sheet 16 + EDPB Guidelines 2/2023 alignment
License
MIT © Superstellar LLC. See LICENSE.
