Shav Vimalendiran
BlogWiki ↗
  • The Last MoatsJul 7, 2026
  • The Tech FrontierJul 6, 2026
  • The Trust BarrierJul 4, 2026
  • I Open-Sourced My Coding Agents' MemoryJun 24, 2026
  • How I Gave My Coding Agents Persistent MemoryMar 12, 2026
  • Multi‑Agent Web Exploration with Shared Graph MemoryFeb 19, 2026
  • Our Lessons from Building Production Voice AIJan 11, 2026
  • Reinforcement Learning, Memory and LawDec 10, 2025
  • Automating Secret ManagementOct 23, 2025
  • The Knowledge LayerOct 5, 2025
  • OTel Sidecars on FargateSep 11, 2025
  • Git Disasters and Process DebtSep 7, 2025
  • Is Code Rotting Due To AI?Sep 3, 2025
  • The Integration IllusionAug 30, 2025
  • When MCP FailsAug 26, 2025
  • Context EngineeringAug 22, 2025
  • Stop Email Spoofing with DMARCAug 5, 2025
  • SOTA Embedding Retrieval: Gemini + pgvector for Production ChatJul 21, 2025
  • Agentic Design PatternsJun 21, 2025
  • Building AI Agents for Automated PodcastsJan 1, 2025
  • Rediscovering CursorDec 2, 2024
  • GraphRAG > Traditional Vector RAGAug 8, 2024
  • Cultural Bias in LLMsJul 20, 2024
  • Mapping out the AI Landscape with Topic ModellingJul 7, 2024
  • Sustainable Cloud Computing: Carbon-Aware AIJun 27, 2024
  • Defensive Technology for the Next Decade of AIJun 24, 2024
  • Situational Awareness: The Decade AheadJun 13, 2024
  • Mechanistic Interpretability: A SurveyJun 7, 2024
  • Why I Left UbuntuMay 24, 2024
  • Multi-Agent CollaborationApr 16, 2024
  • Building Better Retrieval SystemsMar 28, 2024
  • Building an Automated Newsletter-to-Summary Pipeline with Zapier AI Actions vs AWS SES & LambdaFeb 3, 2024
  • Local AI Image GenerationDec 15, 2023
  • Deploying a Distributed Ray Python Server with Kubernetes, EKS & KubeRayNov 15, 2023
  • Making the Switch to Linux for DevelopmentOct 24, 2023
  • Scaling Options Pricing with RayOct 1, 2023
  • The Async Worker PoolSep 23, 2023
  • ›Browser Fingerprinting: Introducing My First NPM PackageSep 8, 2023
  • Reading Data from @socket.io/redis-emitter without Using a Socket.io ClientJul 6, 2023
  • Socket.io Middleware for Redux Store IntegrationJul 1, 2023
  • Sharing TypeScript Code Between Microservices: A Guide Using Git SubmodulesApr 21, 2023
  • Efficient Dataset Storage: Beyond CSVsFeb 3, 2023
  • Why I switched from Plain React to Next.js 13Nov 8, 2022
  • Deploy & Scale Socket.io Containers in ECS with ElasticacheNov 3, 2022
  • Implementing TOTP Authentication in Python using PyOTPSep 13, 2022
  • Simplifying Lambda Layer ARNs and Creating Custom Layers in AWSSep 9, 2022
  • TimeScaleDB Deployment: Docker Containers and EC2 SetupJun 23, 2022
  • How to SSH into an EC2 Instance Using PuTTYDec 16, 2021
Loading post…

In This Post

Device FingerprintingThe Data Behind Browser FingerprintingSingleton Pattern: Efficient and EffectiveA Quick Tip on Hydration ErrorsConclusionReferences and Special Thanks
Published: September 8, 2023
PreviousNext

Browser Fingerprinting: Introducing My First NPM Package

This post shares my debut into the world of NPM packages-a zero dependencies package exporting a swift and synchronous function to compute a browser fingerprint. No user permissions or cookie storage is needed either!

See the details on NPM or check out the source code on Github.

Device Fingerprinting

In the age of the internet, accurately identifying visitors is paramount for websites, whether it's to serve personalized content or keep malicious actors at bay. Although cookies have been the traditional heroes, their limitations-like incompatibility with incognito modes and multi-browser usage-led to the invention of device fingerprinting.

Often referred to as machine fingerprint, browser fingerprint, and several other names, a device fingerprint collects specific information about an online device, ensuring its unique identification during future visits. This form of identification remains robust, even when users disable cookies or other tracking tools.

The essence of browser fingerprinting lies in capturing a unique combination of a user's web browser and device details, creating a distinctive 'digital fingerprint'. For example, while many visitors to a website may have the same model of iPhone, the software and drivers installed, geo-location, languages, browser and OS version, and even minute variances in the hardware could be different.

Browser fingerprinting techniques gather one or more of these signals and aim to capture these minor variances between users. Such fingerprints are resilient, remaining consistent across incognito sessions or when users are using VPNs.

The Data Behind Browser Fingerprinting

The depth of information that browser fingerprinting can mine is truly staggering. From device models, operating system versions, and browser specifications to intricate details like user timezone, preferred language settings, and ad blocker usage, the scope is vast.

The function used in my package amalgamates many of these signals. Below is a screenshot of some of the data I decided to use in my NPM package to generate a fingerprint:

Browser fingerprint signals and data collection interface showing various device and browser characteristics

For a closer look, explore the deployed Next app here.

⚠️

Be careful: The strongest discriminating factor is canvas token which can't be computed on old devices (e.g. iPhone 6)

This data is then run through a MurmurHash3 hashing algorithm to generate a unique fingerprint for each browser.

Singleton Pattern: Efficient and Effective

For the creation of the fingerprint, I've opted for the singleton design pattern. This ensures that the class is instantiated only once. On successive calls to the fingerprint function, the very same instance returns, facilitating faster caching of fingerprint data and swifter function calls.

This approach has proven effective, especially when paired with API calls to authenticate that the user making a request was the same user who logged in and was issued the JWT.

A Quick Tip on Hydration Errors

To avoid the hydration errors we get when using checks like typeof window !== 'undefined' in the logic, use the following approach:

Import the fingerprintBrowser function from the package:

import { getBrowserFingerprint } from 'fingerprint-browser';

Next, make use of it within a useEffect hook:

const [browserFingerprint, setBrowserFingerprint] = useState('');
useEffect(() => {
  setBrowserFingerprint(fingerprintBrowser());
}, []);

This approach ensures that server-side rendering remains unaffected as hooks aren't executed. Wrapping the window usage inside a useEffect triggered on mount ensures the client executes it post hydration.

Conclusion

Browser fingerprinting is an innovative, resilient, and essential tool for the modern web. I'm happy to have contributed to this domain, and I invite developers and enthusiasts to explore and provide feedback on my package.


References and Special Thanks

  • Thanks to @damianobarbati for get-browser-fingerprint which provided some inspiration
  • Special thanks to Valentin Vasilyev for the original fingerprintjs slightly modified
  • Thanks to Open Source Device Fingerprinting by Dark Wave Tech for the various identity functions
  • Dave Alger for his fingerprinting CodePen
  • N8Brooks on Github for his implementation of unsigned 32-bit MurmurHash3

Loading comments...
PreviousThe Async Worker PoolNextReading Data from @socket.io/redis-emitter without Using a Socket.io Client

Be the first to share your thoughts!