Browser SDK
@sentriq/browser collects coarse, non-invasive browser signals and submits them to Sentriq. The server is authoritative for device identity — this SDK never asserts a device_id; it only reports signals and an opaque local-storage hint.
Install
npm install @sentriq/browserSentriq.init(options)
import { Sentriq } from '@sentriq/browser';
const sentriq = await Sentriq.init({
publicKey: 'pub_test_xxx',
// Optional. Defaults to the hosted Sentriq API.
apiUrl: 'https://api.sentriq.example',
// Optional. Defaults to 5000ms.
timeoutMs: 5000,
});Only pass a public key (pub_test_... / pub_live_...). Sentriq.init rejects anything that doesn't look like one — secret keys must never ship to browser code.
track(eventName, options?)
Reports a named security event. eventName is one of: 'page_view' | 'signup' | 'login' | 'login_failed' | 'logout' | 'password_reset' | 'two_factor_challenge' | 'sensitive_action' | 'payment' | 'custom'.
const result = await sentriq.track('login', {
// Your own opaque reference for this end user — never a password,
// email, or other sensitive value. Optional, max 128 characters.
account: { id: 'usr_123' },
});
result.deviceId; // == result.device.id
result.eventId; // == result.event_id
result.risk.score; // 0-100
result.risk.level; // 'low' | 'medium' | 'high' | 'critical'
result.risk.decision; // 'allow' | 'monitor' | 'challenge' | 'block'
result.risk.signals; // which signals contributedaccount.id is validated client-side (max 128 chars) before any request is sent, mirroring the server's own validation — an oversized value throws a SentriqConfigurationError immediately.
identify()
A thin convenience wrapper around track('page_view').
const result = await sentriq.identify();resetIdentity()
Clears the SDK's locally-stored client_hint, so the next call reports a brand-new one — useful for an explicit "sign out and forget this device" action.
sentriq.resetIdentity();client_hint. Resetting has no retroactive effect on past events or devices, and it shouldn't be called routinely — that defeats the point of the hint.Errors
track() / identify() never fail silently — they always reject with a typed error, all extending SentriqRequestError:
SentriqConfigurationError— bad SDK usage caught client-side before any request is sent (e.g. an invalid public key, an oversizedaccount.id).SentriqNetworkError— the request itself failed (DNS, connection, CORS).SentriqTimeoutError— the request exceededtimeoutMs.SentriqApiError— the server responded with a non-2xx status; exposes.statusand, when the server sent one, a.message.
import { SentriqRequestError } from '@sentriq/browser';
try {
await sentriq.track('login', { account: { id: userId } });
} catch (e) {
if (e instanceof SentriqRequestError) {
// handles all four typed errors
}
}What's collected
Coarse signals only — every call collects:
- Timezone, locale, and language list.
- Screen dimensions, color depth, pixel ratio.
hardwareConcurrency/deviceMemorywhen the browser exposes them.- Platform string and user agent.
- Touch support.
- Storage capability booleans (localStorage/sessionStorage/IndexedDB/cookies) — capability flags only, never storage contents.
- Three coarse automation heuristics, never treated as proof:
navigator.webdriver, a plugin/mime-type headless pattern, and a runtime/viewport consistency check. - A
client_hint: a random opaque token generated once and persisted inlocalStorage.
What's deliberately not collected
No canvas/audio/WebGL fingerprinting, no installed-font or plugin enumeration, no cross-site tracking, no IP address (the server derives that from the request itself), no page content, no form input, no credentials.