Compare · WhiskerJS 0.1.0

WhiskerJS and ClientJS

Both are Apache-2.0. ClientJS is an older, unmaintained library with a 32-bit hash over values that include the full User-Agent; WhiskerJS is a current collector with versioned ids. Compared from a code reading of ClientJS 0.2.1.

In plain words

ClientJS is one of the older open-source fingerprint libraries. It reads the User-Agent, screen, plugins, fonts, a canvas drawing and a few other values, and turns them into one 32-bit number. It has not been updated since 2021. WhiskerJS is a current collector under the same licence (Apache-2.0) with a much larger set of signals, statuses for every value, and identifiers that do not include the browser version.

At a glance

WhiskerJS 0.1.0 ClientJS 0.2.1
Licence Apache-2.0 Apache-2.0 (bundles fontdetect.js, swfobject.js, deployJava.js; depends on ua-parser-js 0.7 and murmurhash-js)
Version read main at signalsVersion 5, idVersion 2 tag v0.2.1, commit 8f98834
Last release not yet published 2021-10-25 (last push 2023-01-08)
Status in development unmaintained
Kind collector: signals, ids and flags, no verdict device identifier plus a parsed User-Agent
Identifier 32-hex visitorId (128 bits) plus a 7-part structuredId 32-bit MurmurHash3 integer (seed 256)
Bot and tamper checks 14 integrity flags none
Default network behaviour none none found
Off switch not needed not needed
Install build from source; npm package coming npm package clientjs

How this was made: ClientJS’s public repository was read at v0.2.1 on 2026-10-24 (code read, never installed or run). Its own documentation is the reference if anything here differs.

Signals

Y collected for the identifier · b used only as a bot, lie or consistency check · o opt-in, or collected but outside the hash · - not collected.

Signal WhiskerJS 0.1.0 (signalsVersion 5) ClientJS 0.2.1
Canvas 2D image Y (double render, tamper probe) Y
WebGL vendor and renderer (unmasked) Y -
WebGL parameters and extensions Y -
WebGL rendered image Y (double render) -
WebGPU adapter info Y -
Audio (OfflineAudioContext) Y -
Audio base latency o (Safari and Android only) -
Fonts (how probed) Y (54, span width and canvas measureText) Y (231, span)
Font preferences (default font sizes) Y -
Screen size and colour depth Y Y
Screen frame or available area o Y
Time zone Y Y (offset)
Intl locale and calendars Y -
Languages Y Y
User-Agent and platform Y (platform; the UA string only for the class and checks) Y
UA Client Hints (high entropy) Y (versions outside the id) -
Hardware concurrency Y -
Device memory Y -
JS heap limit Y -
CPU architecture (NaN trick or CH) Y (NaN) -
Touch support Y -
Plugins and mimeTypes Y (+ consistency check) Y
Math precision Y -
CSS media features Y (5) -
Storage availability Y Y
Cookies enabled Y Y
Media codecs Y -
Speech voices o -
Permissions states o (24) -
Media device counts - -
WebRTC - -
Network information - -
Battery - -
Keyboard layout o -
Content blockers or extensions o (8 bait elements) -
Vendor-specific globals Y Y (Java, Flash)
Apple Pay, Private Click Measurement Y -
PDF viewer Y -
Automation markers (webdriver, Selenium, Playwright, CDP, PhantomJS) b (webdriver, 32 globals, headless; no CDP) -
Lie or tamper detection b (native source, descriptors, clean iframe, Proxy) -
iframe and Worker cross-check b (iframe; no Worker) -
Anti-replay (nonce, time) Y -
Identifier format 32-hex visitorId plus 7-part structuredId 32-bit int

In short: ClientJS reads the classic set of its time (User-Agent, screen and available screen, colour depth, plugins and mimeTypes, Java, Flash and Silverlight presence, 231 fonts, storage and cookies, time zone offset, language, a canvas drawing). It does not read WebGL, audio, Math, CSS media features, hardware concurrency, device memory or touch details. WhiskerJS reads all of those and more, and reports the plugin list but none of the obsolete plugin technologies.

How they differ in approach

What goes into the hash. ClientJS joins its values with | and hashes them, including the full User-Agent string. The User-Agent carries the browser version, so a browser update changes the fingerprint (an inference from getFingerprint; the project publishes no stability notes). WhiskerJS keeps volatile values out of its ids: exact Client Hints versions are in ua_versions, which is reported but never hashed into the visitorId, and the User-Agent string itself is used only for the browser class and integrity checks.

Hash size. A 32-bit integer has about 4.3 billion values; at scale different inputs collide. WhiskerJS ids are the first 128 bits of SHA-256, and large values inside the result are digests of the same size. Hash size only bounds accidental collisions of different inputs; how often two real devices give identical inputs is a separate question that only measurement answers.

Missing values. ClientJS returns values or empty results. WhiskerJS gives every component a status (ok, unavailable, blocked, noisy, excluded, timed_out) and a reason code, and every non-ok status contributes the same placeholder to the id, so a component blocked in one run and timed out in the next does not change it.

Versions. WhiskerJS stamps every result with signalsVersion and idVersion and documents what each bump changes (What’s new). ClientJS has no such versioning of its fingerprint.

User-Agent parsing. ClientJS parses the User-Agent with the bundled ua-parser-js 0.7 and offers browser, OS and device getters. WhiskerJS does not parse the User-Agent; that is left to your server.

Privacy browsers and noise. ClientJS has no handling for canvas noise or privacy browsers. WhiskerJS renders canvas and WebGL twice and uses an exclusion table for Brave and Firefox with resistFingerprinting.

Their claims, in their words

  • README.md (0.2.1): “By taking multiple data points, combining them, and representing them as a number, you can be surprisingly accurate at recognizing not only browsers and devices, but also individual users”.
  • README.md (0.2.1): “It is not a full proof technique”.
  • No stability or uniqueness figure is published.

WhiskerJS publishes no accuracy, stability or uniqueness figure yet.

Code credits

WhiskerJS contains no ClientJS code. Its THIRD_PARTY_NOTICES.md credits ClientJS 0.2.1 for ideas only: font and plugin probes.

When you might pick either

  • You maintain an existing integration of ClientJS and only need its User-Agent getters: the code is unchanged since 2021.
  • You need signals current browsers offer (WebGL, audio, Client Hints, Intl, media features), statuses per value, a part-by-part id or integrity flags: those are in WhiskerJS.
  • You need an installable package today: ClientJS is on npm; WhiskerJS is built from source until its npm package is published.
  • Either way, measure on your own traffic.

No head-to-head measurement has run. This page compares features and designs. It does not say that either library is more accurate, more stable or more unique. A side-by-side measurement on the same devices is planned and will be published on the comparison page.

Questions and answers

Is ClientJS still maintained?

Its last release, 0.2.1, was published on 2021-10-25 and the last push to its repository was on 2023-01-08, so it is unmaintained in the version read.

Why does the ClientJS fingerprint change after a browser update?

Its getFingerprint joins the full User-Agent string into the hashed values, and the User-Agent carries the browser version. WhiskerJS keeps exact version numbers in ua_versions, which is outside its ids.

Is a 32-bit hash a problem?

A 32-bit hash has about 4.3 billion possible values, so different inputs collide more often as the number of devices grows. WhiskerJS ids use the first 128 bits of SHA-256. Neither number says how often two real devices produce the same inputs.