Skip to content

pametan/device-nuid

Repository files navigation

device-nuid

A privacy-first device-signal collector. It enumerates device/browser signals, normalises them into one canonical, versioned schema, and sends them to an endpoint that mints a NUID (network/device unique identifier). The collector gathers and sends; your backend owns identity. It never tracks across sites.

⚠️ Consent matters. Device fingerprinting commonly requires user consent under UK/EU ePrivacy & GDPR (and similar regimes). This package is consent-gated by default — nothing is collected or sent until you grant consent — and high-entropy signals are opt-in and suppressed under Global Privacy Control / Do-Not-Track. This is not legal advice; confirm your obligations.

TypeScript-first, zero runtime dependencies, SSR-safe, with reference native SDKs.

import { createCollector } from '@pametan/device-nuid';

const collector = createCollector({ endpoint: 'https://api.example.com/nuid' });
collector.grantConsent();              // after your consent UX
const { response } = (await collector.send())!;  // response is your endpoint's reply (the NUID)

Why

Device intelligence powers fraud and risk decisions, but the signals are scattered across inconsistent, often-deprecated browser APIs, and the strongest ones carry real consent weight. device-nuid gathers them through one clean, consent-gated interface, normalises them into a single schema that the web collector and the native iOS/Android SDKs all produce, and POSTs that to your endpoint. You compute the NUID (e.g. by hashing the canonical payload); the package never mints an id itself.

Install

npm install @pametan/device-nuid

Requires Node 24+ to build; runs in any modern browser. Ships ESM + types.

Usage

Collect, then send

import { createCollector } from '@pametan/device-nuid';

const collector = createCollector({
  endpoint: 'https://api.example.com/nuid',
  level: 'basic',          // 'basic' (default) | 'extended'
  requireConsent: true,    // default
  beacon: false,           // true => fire-and-forget via sendBeacon (no response)
});

// after the user consents:
collector.grantConsent();

const payload = await collector.collect();        // the normalised payload (or null)
const result  = await collector.send();           // POSTs; result.response is the NUID reply

send() resolves with { payload, response, viaBeacon }response is your endpoint's parsed body (e.g. { nuid: "..." }). One-shot collect(config) and send(config) helpers exist too.

Consent & privacy

  • requireConsent defaults to true: collect()/send() return null until grantConsent() is called (or consent: true is passed).
  • level: 'extended' adds high-entropy signals (WebGL renderer, a canvas hash). They're off by default and are suppressed when the browser signals Global Privacy Control or Do-Not-Track (respectGpcDnt, default true).

Endpoint contract

Your endpoint receives the JSON payload and is expected to return the minted id, e.g. { "nuid": "..." }. That response is surfaced as result.response.

What it collects

Basic (default): device type, OS (+ version), browser (+ version/engine), languages, time zone (IANA + offset), screen (size, available, colour depth, pixel ratio, orientation), viewport, hardware (cores, memory), capabilities (touch, pointer, cookies, DNT/GPC), network hint, UA Client Hints and the raw user-agent. OS/browser/device-type come from UA Client Hints where available, falling back to user-agent parsing.

Extended (opt-in): WebGL vendor/renderer and a canvas hash.

See schema/nuid-payload.schema.json for the full, versioned contract. canonicalize(payload) gives a stable, sorted-key JSON string so client and server hash identical bytes.

API

Export Description
createCollector(config) A collector: collect, send, grant/revoke/hasConsent.
collect(config) / send(config) One-shot equivalents.
canonicalize(value) Deterministic JSON for stable hashing.
validatePayload(payload) Light structural check → list of problems.
SCHEMA_VERSION, SDK_VERSION Version constants.

Types NuidPayload, DeviceType, CollectionLevel, CollectorConfig, SendResult, CollectEnv are exported. Pass env to inject the environment (SSR / testing).

Native SDKs

Reference implementations targeting the same schema (collector.platform ios/android, deviceType: 'mobile-app') live under sdk/: sdk/ios/DeviceNuid.swift and sdk/android/DeviceNuid.kt. They are reviewed skeletons — build and test them in Xcode / your Android project; they are not compiled in this repo's CI, and they keep the same consent gate.

SSR

In a non-browser environment collect() returns null and send() no-ops — no window/navigator access — so it's safe to import in SSR/Node code paths.

Development

npm install
npm run typecheck
npm test          # normalisation, consent, GPC/DNT, transport, SSR, schema
npm run build     # emit dist/

Disclaimer

An engineering aid, not legal advice. You are responsible for obtaining valid consent and meeting your privacy obligations. MIT licensed — see LICENSE.


Need the production version of this?

We're Pametan — a specialist fintech/regtech engineering agency working across UK, US and Canadian rails (FCA · CFPB · FCAC). We build the production device-intelligence and fraud-signal pipelines this feeds into — consent-aware, audited, and tuned to your risk models.

Talk to us →

About

Privacy-first device-signal collector: enumerate and normalise device/browser signals into a canonical schema and send them to an endpoint that mints a NUID. Consent-gated by default.

Topics

Resources

License

Contributing

Security policy

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors