The world's easiest, smallest and powerful visitor identifier for browsers.
Generate a stable per-browser identifier from canvas + audio fingerprints, hashed with a fast, zero-dependency 53-bit hash. No tracking pixels, no third-party services, no runtime dependencies.
⚠️ Not a security primitive. The hash (cyrb53) is a fast, non-cryptographic checksum. Use it for visitor identification, analytics, and fraud-signal heuristics — not for authentication, password storage, or anything that requires collision resistance.
📦 Migrating from v2? See
MIGRATION.mdfor the breaking changes in v3.0.0.
npm i @rajesh896/broprint.js
# or
yarn add @rajesh896/broprint.js
# or
pnpm add @rajesh896/broprint.jsESM (modern browsers):
<script type="module">
import { getCurrentBrowserFingerPrint } from 'https://cdn.jsdelivr.net/npm/@rajesh896/broprint.js@latest/lib/index.mjs';
getCurrentBrowserFingerPrint().then((fp) => console.log('Fingerprint:', fp));
</script>Classic global build (no module support needed):
<script src="https://cdn.jsdelivr.net/npm/@rajesh896/broprint.js@latest/lib/index.global.js"></script>
<script>
Broprint.getCurrentBrowserFingerPrint().then((fp) => console.log('Fingerprint:', fp));
</script>import { getCurrentBrowserFingerPrint } from '@rajesh896/broprint.js';
const fingerprint = await getCurrentBrowserFingerPrint();
// → "1234567890123456" — a stable numeric string for this browser// canvas-only (skip audio entirely)
await getCurrentBrowserFingerPrint({ useAudio: false });
// audio-only
await getCurrentBrowserFingerPrint({ useCanvas: false });
// custom seed (e.g. to namespace fingerprints per-app)
await getCurrentBrowserFingerPrint({ seed: 42 });The individual signal generators and the hash function are also exported, so you can compose your own pipeline:
import {
getCanvasFingerprint,
getAudioFingerprint,
cyrb53,
isCanvasSupported
} from '@rajesh896/broprint.js';
const canvas = getCanvasFingerprint(); // sync, returns canvas dataURL
const audio = await getAudioFingerprint(); // async, returns numeric string
const hash = cyrb53(canvas + audio); // → number in [0, 2^53)| Option | Type | Default | Description |
|---|---|---|---|
useAudio |
boolean |
true |
Include the audio fingerprint signal. |
useCanvas |
boolean |
true |
Include the canvas fingerprint signal. |
seed |
number |
0 |
Custom seed for the cyrb53 hash. Use to namespace fingerprints per-app. |
Returns: Promise<string> — a stable numeric string (53-bit hash, base-10).
Throws:
- If both
useAudioanduseCanvasarefalse(no signals selected). - If the only enabled signal fails (e.g.
useCanvas: falseon a browser that blocksOfflineAudioContext). - If audio fails and canvas also fails.
If audio is enabled but fails (e.g. privacy mode blocks OfflineAudioContext) and canvas is also enabled, the function automatically falls back to a canvas-only fingerprint.
getCanvasFingerprint(): string— render the canvas + return itstoDataURL().isCanvasSupported(): boolean— feature-detects HTML5 canvas with a 2D context.getAudioFingerprint(): Promise<string>— render an OfflineAudioContext graph + return the float sum as a string.cyrb53(str: string, seed?: number): number— fast, non-cryptographic 53-bit hash.BroprintOptions— TypeScript type for the options object.
┌────────────────────────────────┐
│ getAudioFingerprint │
Audio entropy ──▶ │ OfflineAudioContext renders │ ──▶ float sum (string)
│ 500 frames through a │
│ DynamicsCompressor. │
└────────────────────────────────┘
│
▼ base64-encode
┌────────────────────────────────┐
│ getCanvasFingerprint │
Canvas entropy ──▶│ Draw layered text + shapes │ ──▶ canvas.toDataURL()
│ and read back the data URI │
│ (varies by GPU / font stack). │
└────────────────────────────────┘
│
▼ concat
┌────────────────────────────────┐
│ cyrb53 │ ──▶ 53-bit numeric hash
│ (custom, no deps, ~10 lines) │ coerced to string
└────────────────────────────────┘
│
▼
fingerprint
| API | Required for | Support |
|---|---|---|
HTMLCanvasElement |
canvas signal | All modern browsers |
OfflineAudioContext |
audio signal | All modern browsers (webkitOfflineAudioContext fallback for older Safari) |
Promise / async |
always | All modern browsers (ES2017+) |
Tested on the latest stable Chrome, Firefox, Safari, and Edge. Node ≥ 18 is required for the build/test toolchain.
- Not unique forever. A browser update, GPU driver change, OS upgrade, or "ungoogled" build can change the output. Treat the fingerprint as a stable signal for a session/install, not a permanent identity.
- Privacy modes can block signals. Some privacy-focused browsers (Brave Strict, Tor, Firefox RFP) randomize or block the underlying APIs. The library falls back gracefully to the available signal, but the output will differ from the same user's "normal" browsing.
- Not for security. The 53-bit
cyrb53hash is fast and well-distributed but not cryptographically secure — collisions exist and the algorithm is reversible with effort. Don't use the output as a session token or password salt.
See CONTRIBUTING.md for the dev setup, test/lint loop, and PR process. Bug reports and feature requests are welcome via GitHub Issues; security-sensitive reports should follow SECURITY.md.
MIT © Rajesh Royal
