user-agents
A JavaScript library that generates realistic, frequency-weighted random user agents and browser fingerprints from a dataset refreshed daily.
Repository Health
Technical Analysis
user-agents is an npm package for generating random browser user agent strings and full navigator/browser fingerprint objects (platform, screen size, viewport, connection type, plugins length, vendor) for web scraping, testing, and QA automation. Rather than sampling from a static hardcoded list, the package ships a dataset rebuilt every 24 hours from real browser traffic, so generated agents track current market share and never go stale.
The core API is a single UserAgent class that can be filtered with objects, regular expressions, arrays, or custom predicate functions, and reused efficiently via its callable-instance pattern (userAgent()) to generate large batches of fresh entries without re-running the filter setup each time.
What You Get
- A
UserAgentclass supporting object, regular expression, array, and custom-function filters - A dataset of real-world user agents and browser fingerprints refreshed daily from live traffic
- Full TypeScript type definitions (
UserAgentData,Filter) with native ESM and CommonJS builds - Static and instance
random()/top()helpers for both weighted-random and frequency-ranked sampling - Zero runtime dependencies in the published package
Common Use Cases
- Rotating realistic user agents for web scraping and crawling
- Randomizing browser fingerprints in browser-automation test suites
- Simulating realistic traffic mixes in load and performance tests
- Generating device- or browser-specific fixtures for unit tests
Under The Hood
Architecture
The package is a small, single-purpose module built around one class, UserAgent (in src/user-agent.ts), that wraps a JSON-embedded dataset (src/user-agents.json, shipped gzip-compressed and decompressed at build time by src/gunzip-data.ts) imported directly via a JSON import assertion. Filtering logic (constructFilter) recursively converts a flexible filter grammar — objects, regular expressions, arrays, or predicate functions — into a single predicate applied against each raw record, and constructCumulativeWeightIndexPairsFromFilters builds a cumulative-weight distribution used to sample proportionally to real-world usage frequency. The entry point src/index.ts re-exports the class and its types from src/user-agent.ts; constructing a UserAgent precomputes that distribution once, and randomize() then draws an index via Math.random() and clones the matching record. Notably, the constructor returns a Proxy wrapping a plain function target so that calling an instance (userAgent()) invokes random() while every other property read, write, and enumeration is forwarded to the real instance — a deliberate workaround, documented in-line, to make instances both callable and property-transparent without extending Function, which would require eval and break under browser-extension CSP. A separate, fully decoupled script (src/update-data.ts) regenerates the dataset offline by querying a DynamoDB table of submitted browser telemetry via dynamoose, filtering bots with isbot, de-duplicating by IP, and re-weighting before gzip-writing a fresh dataset that CI publishes on a recurring schedule — keeping the runtime bundle and the data-refresh pipeline cleanly separated.
Tech Stack
Written entirely in TypeScript and built with tsup into dual native ESM and CommonJS outputs wired through package.json’s exports map, targeting modern Node. The published package carries zero runtime dependencies — the whole surface is the UserAgent class plus its bundled dataset. Development tooling includes ESLint with typescript-eslint and eslint-config-prettier, Prettier for formatting, and Mocha with a tsx loader for tests; remnants of an older Babel-based toolchain persist alongside the newer esbuild/tsup pipeline. The offline data-refresh path pulls in AWS’s dynamoose ODM against DynamoDB, isbot for bot filtering, a seeded-noise random package, ua-parser-js for device classification, and fast-json-stable-stringify for canonical de-duplication keys — none of which reach consumers of the published package. CI runs on CircleCI, driving build, test, and the daily data-refresh publish.
Code Quality
Tests live in a dedicated Mocha suite that exercises the full filter grammar — object, nested object, regular expression, and array filters, plus custom predicate functions — alongside the no-match error path and both instance and static random()/top() behavior, with each randomized case repeated many times to guard against flaky sampling. Error handling is explicit and intentional: the constructor throws a descriptive error when no user agents match the given filters, while the static random()/top() helpers deliberately swallow that same error to return null/an empty array instead — a documented contract difference rather than a silent failure. Types are strict, with a fully modeled UserAgentData interface using literal-union types for fields like platform and vendor, and lint/format are both enforced through a single script gating the codebase. No obvious gaps were found in the core filtering and sampling logic; the DynamoDB-backed data-refresh script has comparatively little test coverage, which is reasonable given it runs offline and outside the published package.
API Design
The defining ergonomic choice is that a UserAgent instance is simultaneously the fingerprint data and a generator: its properties (platform, userAgent, etc.) read directly off the instance via the Proxy’s forwarding get trap, it coerces to a string naturally through Symbol.toPrimitive, and calling it as a function produces a new random draw that reuses the same precomputed filter — so generating a batch of fresh agents is just mapping a call over a fixed-length array, with no separate factory or config object required. The filter grammar lets a caller express a fairly specific query, like a mobile Safari agent, as a small nested object with no query language to learn. The static and instance APIs are deliberately dual: the static form never throws and returns null/an empty array on no match, while the instance form throws but reuses precomputed filter state for much faster repeated generation — a documented trade-off rather than an accidental inconsistency. What differentiates the library from other random-user-agent generators is the daily-refreshed, real-traffic-weighted dataset underneath an otherwise simple API: hardcoded UA lists in comparable tools go stale within months, and this one structurally can’t.