npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

datalex-fingerprint

v1.1.1

Published

A TypeScript fingerprinting library for browser and device identification

Downloads

62

Readme

datalex-fingerprint npm version

A lightweight TypeScript library for browser and device fingerprinting. The SDK owns collecting, caching, and talking to the backend — you don't hand-write that glue. Ships three ways:

  • npm package, core: import { init } from 'datalex-fingerprint'
  • npm package, React: import { useFingerprint } from 'datalex-fingerprint/react'
  • plain <script> tag, no build step, no npm: window.DatalexFingerprint

Table of contents

Install

npm package

npm install datalex-fingerprint

React is an optional peer dependency — only needed if you import datalex-fingerprint/react. Plain/vanilla/other-framework usage has zero extra dependencies.

# Only if you're using the React subpath:
npm install react react-dom

Script tag (no install)

Nothing to install — point a <script> tag at the hosted build (see As a plain script tag below).

Quick start

As an npm package (vanilla / any framework)

import { init, ready } from 'datalex-fingerprint';

// Call once, anywhere at bootstrap. Pass the backend URL to send fingerprint data to — collection
// starts automatically once the page is idle, and the result is sent to that backend for you.
init({ apiUrl: 'https://api.yourbackend.com' });

// Read the result whenever you need it — resolves whether the hash came from cache or a fresh
// compute, and never rejects (a failure surfaces as `result.error`).
const { hash } = await ready();

That's the whole surface: the SDK gives you a hash and stores fingerprint data on the backend. It has no concept of sessions, users, or any other application identity — if you need to link a fingerprint to a user or session on your backend, correlate it there using the hash.

As an npm package (React)

import { TouchpointProvider, useFingerprint } from 'datalex-fingerprint/react';

function App() {
  return (
    <TouchpointProvider config={{ apiUrl: 'https://api.yourbackend.com' }}>
      <Checkout />
    </TouchpointProvider>
  );
}

function Checkout() {
  const { hash } = useFingerprint();

  async function onSubmit() {
    // hash is whatever init() has produced so far — null if collection hasn't settled yet.
    // Submit immediately either way; never block checkout on the fingerprint.
    // ...submit the order with hash (may be null; handle that case server-side)
  }

  return <button onClick={onSubmit}>Pay now</button>;
}

<TouchpointProvider> is a convenience, not a requirement — useFingerprint() works as soon as init() has run anywhere (app bootstrap, a <script> tag, another component). If neither has run yet, the hook reports status: 'uninitialized' instead of throwing.

As a plain script tag (CDN, no build step)

For a page with no bundler at all — a static checkout page, a CMS, a third-party embed. Everything is bundled into one file and exposed as window.DatalexFingerprint, with the same API as the npm core package.

<!-- Pin integrity="sha384-..." for the exact version you're loading, generated with:
       openssl dgst -sha384 -binary touchpoint.global.js | openssl base64 -A -->
<script
  src="https://cdn.datalex.solutions/sdk/v1/touchpoint.global.js"
  integrity="sha384-..."
  crossorigin="anonymous"
></script>
<script>
  window.DatalexFingerprint.init({ apiUrl: 'https://api.yourbackend.com' });

  window.DatalexFingerprint.ready().then(({ hash }) => {
    console.log('fingerprint hash:', hash);
  });
</script>

No React build is published for the script-tag path — datalex-fingerprint/react is an npm-only subpath. A vanilla page can still read window.DatalexFingerprint.ready()/subscribe() directly from a <script> of its own.

Core API reference

Available from datalex-fingerprint (npm) and as window.DatalexFingerprint.* (script tag) — identical API either way.

init(config)

Configures the SDK and (by default) starts collecting once the page is idle. Safe to call more than once: later calls merge new config without restarting an in-progress or already-settled collection. Returns a handle ({ ready, refresh, getResult, subscribe }) equivalent to the standalone functions below.

apiUrl is required on every call — the SDK ships with no built-in backend URL and no fallback. Omitting it (or passing an empty string) throws immediately, including from a plain <script> tag with no TypeScript to catch it at compile time.

| Option | Type | Default | Description | |---------------|-------------------------------|---------------------------|--------------| | apiUrl | string | (required, no default) | Backend URL to send fingerprint data to. | | autoCollect | boolean | true | Start collecting once idle. Set false to collect only on demand via ready(). | | cache | boolean \| CacheOptions | true | Reuse the persisted hash across visits. Pass a CacheOptions object to customize the storage key/drivers/cookie domain. | | timeout | number | 10000 | Hard ceiling (ms) on the whole collect. ready() always resolves by this point, never hangs. | | onReady | (result: TouchpointResult) => void | — | Sugar over subscribe() for a fire-once callback. | | debug | boolean | false | Logs a notice when a redundant init() call is ignored. |

ready(): Promise<TouchpointResult>

Resolves with the current (or in-flight, or freshly started) fingerprint result. Never rejects — failures surface as result.error with hash: ''. Starts collection immediately if it hasn't already, regardless of init()'s idle deferral — an explicit reader is never made to wait for idle.

interface TouchpointResult {
  hash: string;
  cached: boolean;         // true if this result came from persisted storage, not a fresh compute
  source?: 'cookie' | 'localStorage' | 'indexedDB' | 'memory'; // which layer served the cache hit
  error?: { code: string; message: string; details: string[] };
}

getResult(): TouchpointResult | null

Synchronous, no-op peek at the current settled result — null if collection hasn't settled yet. Use ready() to actually wait for a result.

subscribe(callback): () => void

Registers a listener for the next settle (and any later refresh() update). Returns an unsubscribe function. Does not replay the current value — call getResult() too if you need it immediately.

const unsubscribe = subscribe((result) => console.log('fingerprint updated:', result));
// later: unsubscribe();

refresh(): Promise<TouchpointResult>

Forces a new collect cycle, discarding the in-memory settled result. Still reads/writes through the persisted cache the same way init()'s auto-collect always does — this re-evaluates, it does not bypass storage the way clearCache() would.

React API reference

Available from datalex-fingerprint/react. Requires react/react-dom ≥ 18 (peer dependencies).

<TouchpointProvider config autoInit? children>

Optional convenience that calls the core's init(config) from a declarative spot in the tree — config (with its required apiUrl) is forwarded straight through. The provider itself isn't required — useFingerprint() works with no provider as long as init() ran anywhere. Set autoInit={false} to skip calling init() (e.g. it's already called elsewhere).

useFingerprint(): UseFingerprintResult

interface UseFingerprintResult {
  status: 'uninitialized' | 'loading' | 'ready' | 'error';
  hash: string | null;
  data: TouchpointResult | null;
  error: Error | null;
  isLoading: boolean;   // status === 'loading'
  isReady: boolean;     // status === 'ready'
  refresh(): Promise<void>;
}

SSR-safe by construction: on the server it returns a stable placeholder (status: 'uninitialized') and the first client render matches it exactly, so there's no hydration mismatch — the real fingerprint arrives on a subsequent update, never during the initial render.

useFingerprintValue(): string | null

Convenience wrapper for callers who only want the hash string (or null until ready / on error).

Low-level / advanced API

For cases where the init()-owned flow doesn't fit — full manual control over configuration, collection, and storage:

| Export | Purpose | |---|---| | getFingerprint({ cache?, cacheOptions? }) | Compute (or read the cached) hash directly, without the init() singleton/auto-collect machinery. Talks to the same backend as everything else — call setApiBaseUrl() first (see below). | | generateAndStore({ cache?, cacheOptions? }) | Compute the fingerprint and POST it to the backend in one call. | | generateFingerprint(options?) | Hash of just the user agent string — lighter-weight, no device collectors. | | getUserAgent(), getMediaDevices(), getRawMediaDevices(), getWebGLFingerprint(), getAudioFingerprint() | Individual collectors, if you want to assemble your own fingerprint. | | hashFingerprint(data, options?), createFingerprint(userAgent, additionalData?), isValidFingerprint(value) | Hashing primitives. | | clearCache(options?), peekCache(options?), isPersistenceAvailable(options?), getCachedFingerprint(compute, options?) | Direct access to the persisted-hash cache (cookie → localStorage → IndexedDB → memory fallback). | | storeToBackend(request), findSimilarFingerprints(hash, threshold?, limit?) | Direct backend calls — will fail with an API_URL_NOT_CONFIGURED error until setApiBaseUrl() has been called. | | setApiBaseUrl(apiUrl), getApiBaseUrl() | Required if you're using the low-level API without init(): there's no built-in backend URL, so getApiBaseUrl() returns null until you call setApiBaseUrl(apiUrl). |

How collection works

  • One hash definition. init()'s auto-collect, ready(), getFingerprint(), and generateAndStore() all derive the hash from the same collector pipeline, so they never disagree on what "the fingerprint" is for a given device.
  • Environment metadata doesn't affect the hash. Screen resolution, timezone, language, and platform are sent to the backend as context but excluded from the hash itself — a monitor change or a flight doesn't make the same device look like a new one.
  • Collectors run in parallel, each with its own timeout. A slow or stuck component (e.g. audio fingerprinting in a backgrounded tab) degrades to that component's own "unavailable" value instead of blocking the rest of collection or hanging indefinitely.
  • Concurrent callers share one run. Two components calling ready() at once, or React StrictMode's double-invoke, trigger exactly one collection cycle and one set of backend calls.
  • Caching persists across cookie, localStorage, and IndexedDB, self-healing any layer that was missing, so a returning visitor's hash survives clearing any single storage mechanism.

Development

# Install dependencies
npm install

# Typecheck
npm run typecheck

# Run tests
npm test

# Build all artifacts (core esm/cjs, react esm/cjs, CDN global, type declarations)
npm run build

Build outputs in dist/:

| File | What it's for | |---|---| | touchpoint.esm.js / touchpoint.cjs.js | npm core package (. export) | | react/index.esm.js / react/index.cjs.js | npm React subpath (./react export) | | touchpoint.global.js | Script-tag / CDN build, exposes window.DatalexFingerprint | | types/ | TypeScript declarations for both entry points |

Changelog

See CHANGELOG.md for a detailed history of changes.