FingerprintJS OSS: Collection and Visitor Provenance
Trace FingerprintJS OSS load(), source ordering, component errors, and lazy visitorId digest provenance without claiming a runtime detector result.
BotBrowser Team
Want the structured docs for Fingerprint?
This article lives in the editorial library. For step-by-step setup, reference material, and ongoing updates, jump into the docs section.
BotBrowser can repeat an authorized same-origin fixture and preserve bounded capability, component-status, and digest-path observations, but it cannot run a third-party detector, infer a person, or prove that a vendor uses the same signals. This source review follows FingerprintJS OSS commit 7a738d0e9920c7431b34aff24b345a21f9054855 through src/agent.ts, src/sources/index.ts, and src/index.ts.
TL;DR
load()defaultsmonitoringtotrue;monitoring: falseskips the unpersonalized installation request.get()collects components, exposesvalueorerrorper component, and returns a lazyvisitorId.hashComponents()sorts component keys and builds a canonical string forx64hash128; that digest is provenance for a library result, not proof of identity.- Source order matters: asynchronous sources start first, while WebGL sources run after other sources that can affect rendering.
Contents
- Trace the public entry points
- Read collection and monitoring
- Preserve component value and error states
- Follow source ordering and async work
- Understand digest provenance and lazy visitorId
- Build a bounded source observation
- Set the privacy boundary
Trace the public entry points
The pinned src/index.ts exports load, hashComponents, component types, and the source registry. The public surface does not expose a promise that returns a person; it exposes an agent whose get() resolves a structured result. Keep the commit URL beside any review because a moving branch can reorder sources or change fields.
This distinction helps when a support report says that a visitor identifier changed. The exported API tells you which operations a caller may request, but it does not say which operations an application actually invoked, what browser state was present, or whether a remote service used the result. Source reading can answer “what does this commit implement?” It cannot answer “what happened in this customer's session?” without a separately authorized runtime record.
For a useful review, record the exact package version and commit independently. A package tag may point to a commit today and a different published artifact may include build-time transformations, dependency updates, or packaging changes. The commit URL fixes the source snapshot; package metadata identifies the release a site says it loaded. If those identifiers disagree, stop the comparison and resolve the artifact before attributing behavior to the library.
load() is asynchronous. It may send installation monitoring, waits through prepareForSources(), creates the built-in source loader, and returns an agent. The default monitoring = true is a source fact. A fixture should record whether the option was explicitly set, not silently describe the default as a measured network event.
Treat configuration and observation as separate fields. A record such as monitoringOption: "default" means the caller did not override the option; monitoringOption: "false" means the caller supplied a value. Neither field proves a request was transmitted or suppressed in a particular run. To make a network claim, an authorized test would need its own capture boundary, a timestamp, and enough context to distinguish this library request from application traffic. Do not convert source inspection into a network assertion.
This separation also prevents a common privacy overstatement. Disabling one installation request does not disable local collection, remove the caller's own analytics, or change server-side logging. The option describes one behavior in one pinned package. A product privacy statement must account for the complete page, application, and service path instead of treating a single client option as a global switch.
Read collection and monitoring
src/agent.ts defines LoadOptions.monitoring and says false disables an unpersonalized AJAX request. The implementation samples the monitoring path and opens m1.openfpcdn.io/fingerprintjs/v${version}/npm-monitoring; this review does not claim that a request was made. A safe source observation records the option, the source line, and the documented purpose. It does not retain request headers, cookies, account identifiers, or a third-party response.
monitoring: false is a configuration observation, not an anonymity guarantee. A page can still collect components locally, and an application can send its own telemetry. Separate the library option from application policy and server logs. The matching BotBrowser automation consistency documentation covers controlled page checks; it does not establish what a service does with a visitor identifier.
Preserve component value and error states
get() awaits the component loader and returns components. The component type allows a value or an error, so a missing capability is not the same as a value that happens to be null. A bounded fixture can record component names, status (value or error), and coarse value shape. It should not publish canvas pixels, audio samples, account data, or every component value.
Keep three cases distinct in a report. A component can resolve with a usable value, resolve with a value that is empty or null-like, or reject and produce an error result. Redaction adds a fourth case: the test had a value but deliberately did not preserve it. If the stored record collapses these cases into “missing,” an engineer cannot tell whether the browser lacks an API, the source returned a legitimate empty result, the source failed, or the privacy policy removed the detail.
The sample record uses component names and statuses to keep that distinction while omitting the values themselves. A reviewer may need one additional coarse descriptor, such as “string,” “array,” or “boolean,” when the result shape affects compatibility. Choose the descriptor before collecting results, and document its allowed values. Avoid free-form summaries of high-entropy output, because those summaries can accidentally reproduce the detail that redaction was intended to remove.
Do not use a fabricated value as a stand-in for an observed one. In particular, a JSON template should show the state of the test, not an invented expected identifier or a realistic-looking personal record. Mark fields such as hashAccess as not-measured when no read occurred. That is more useful than filling a template with plausible but unverified data.
Use a private record such as:
{
"fixtureId": "fpjs-source-01",
"commit": "7a738d0e9920c7431b34aff24b345a21f9054855",
"monitoringOption": "false",
"componentStatus": { "canvas": "value", "fonts": "value", "webGlBasics": "error" },
"hashAccess": "not-measured",
"testedOn": "2026-10-29"
}
The record distinguishes a component error from a successful component whose value was redacted. That distinction makes a compatibility handoff actionable without turning the record into a reusable profile. For a related browser-side comparison, see browser fingerprint verification and browser API integrity across realms.
For repeated runs, add only the context required to interpret the comparison: fixture revision, browser build, operating system, profile reset or reuse state, page origin, library package, explicit options, and a coarse completion result. A fresh profile and a reused profile answer different questions. Reusing a profile can reveal whether the page returns the same output under retained state, while a fresh profile can reveal whether a controlled setup reproduces a class of behavior. Do not pool those runs under one label.
An observation record should also have an owner and a retention deadline. Keep raw component values out of general issue trackers, chat messages, and analytics exports. If a support engineer needs a richer artifact to diagnose one defect, restrict access to the people handling that case and remove the artifact when the investigation ends. The small durable record should answer whether the test ran and which coarse outcomes differed, not preserve a long-lived browser signature.
Follow source ordering and async work
src/sources/index.ts declares that asynchronous sources are placed first so they can run in parallel with other sources. The registry begins with userAgentData, fonts, domBlockers, fontPreferences, audio, and screenFrame, then continues through canvas, screen, storage, language, hardware, and policy-related sources. The exact list is an implementation detail at this commit, not a universal browser fingerprint standard.
The order is not merely presentation. Starting asynchronous work early can overlap waiting with synchronous work, while placing dependent sources later can let earlier operations settle first. When comparing revisions, inspect both the registry and the loader that consumes it. A list alone does not prove exact timing: scheduling, promises, browser APIs, and failures can affect when each result becomes available. The source establishes intended ordering; a runtime trace would be needed to claim observed duration or concurrency.
The file places webGlBasics and webGlExtensions at the end because some sources can affect other sources, including WebGL and canvas. Source ordering therefore belongs in a provenance record. If a later commit moves a source, compare the commit and the first changed component before attributing a new digest to a browser change.
Use a fixed comparison table when reviewing an upgrade: pinned revision, old and new registry order, first component whose status or coarse shape changed, and whether the environment inputs stayed fixed. A reordered registry is a code change, not evidence by itself that a browser changed. A changed component may also follow from permissions, timing, platform support, profile state, or a rejected API. State the observed difference first and the proposed cause separately.
The same discipline applies to component failures. If webGlBasics reports an error, do not infer that WebGL is globally unavailable or that all downstream output must differ. Record the component result, the test conditions, and any direct browser error that the authorized fixture exposes. If no runtime trace exists, label the explanation as a source-based possibility rather than a diagnosis.
Understand digest provenance and lazy visitorId
hashComponents() sorts component keys, serializes values or the literal error, escapes separators in component names, and passes the canonical string to x64hash128. The function proves how this library version derives a digest from the component object. It does not prove that the input is unique, stable, human, or accepted by a third party.
Canonicalization makes the library's transformation inspectable. Sorting means insertion order in an object does not define the serialized order. Representing an error with a fixed marker means an error state can participate in the same overall derivation as successful components. Escaping separators keeps component names from being confused with boundaries in the serialized form. These choices explain how the implementation constructs its input string, but they do not make different inputs collision-free or reveal what another product does with the result.
Keep this result separate from the raw component object. A digest is a compact derivative of the collected structure, not a privacy guarantee. Depending on the source values and the audience, a stable derivative can still be linkable across observations. A digest is not equivalent to anonymization, and a deterministic output can retain correlation value even when the original fields are not displayed. The conservative public example therefore documents the transformation and stores no real visitor result.
The visitorId getter is lazy. makeLazyGetResult() calculates confidence immediately but calls hashComponents(this.components) only on first access, then caches the result. A test that never reads visitorId has not exercised the digest path. A test that reads it twice has tested cache reuse, not two independent collections. Record hashAccess as not-measured, first-read, or reused rather than inventing a runtime conclusion.
That timing changes how a fixture should be written. Calling load() and then get() establishes that the agent was created and components were requested. It does not establish that the lazy property was evaluated. The test must read visitorId to cover that path, and should note whether that read was the first one on the result object. A second read can confirm that the same object serves its cached property; it is not a second sample and should not be counted as an independent repeat.
Avoid reporting an identifier merely to prove that the getter ran. A Boolean such as visitorIdRead: true, paired with the component-status summary and the fixture revision, can answer the test question without keeping the output. If exact output comparison is authorized and necessary, keep it in a controlled test artifact with a short retention period, restrict its audience, and do not copy it into general documentation. A pass/fail label without the test conditions is also inadequate because it hides which behavior the test exercised.
Confidence is a separate field from the identifier and should be interpreted according to the implementation's documented meaning. It is not a calibrated probability that a result belongs to one person, nor a score that guarantees a service will accept it. Keep the calculation and the lazy property distinct in notes so that an immediate confidence read is not mistakenly described as evidence that the identifier was accessed.
Build a bounded source observation
The following command retrieves the fixed source and prints only structural lines. It is a public-source observation, not a BotBrowser run and not a request to a protected site:
curl -fsSL https://raw.githubusercontent.com/fingerprintjs/fingerprintjs/7a738d0e9920c7431b34aff24b345a21f9054855/src/agent.ts | rg -n 'monitoring = true|hashComponents\(this.components\)|const components = await getComponents|request.open'
A matching line confirms that this commit contains the named implementation markers. It does not establish that a browser executed them. For runtime work, keep browser build, OS, profile lifecycle, origin, option, component status, digest access, and timestamp in a restricted record. Change one declared input at a time.
The command is deliberately narrow: it downloads one immutable public source file and filters for four markers that correspond to claims in this page. It does not install the package, evaluate JavaScript, load a web page, access a customer profile, or contact a detector endpoint. If the command returns no matching lines, first check whether the source path or implementation changed at the pinned revision. Do not interpret an empty match as a browser failure.
For a review that must be independently repeatable, capture the command, retrieval date, HTTP status, requested commit, file path, and output in the evidence record. Record a file digest when the evidence process needs byte-level integrity. The digest identifies the retrieved bytes; it does not prove authorship or runtime behavior. Keep source retrieval separate from a browser run in filenames and report summaries so those evidence types cannot be confused later.
When moving to a newer release, repeat the same source-only check at the new immutable commit, then compare the relevant sections rather than relying on a branch diff summary. If a symbol has moved or been renamed, update the observation command to match the new source and explain the scope change. A command that only prints expected markers is a convenient review aid, not a complete static analysis of every code path.
Set the privacy boundary
The W3C fingerprinting guidance treats fingerprinting as a privacy concern, while the MDN fingerprinting glossary explains the general concept. This source trace adds commit provenance and redaction rules; it does not claim that FingerprintJS OSS is a complete detector or that a visitorId identifies a person.
Keep compatibility and privacy questions separate. A component error can explain an application alternative without justifying collection of raw values. If a release check needs only presence, status, and digest-access state, retain those fields and delete them on a short schedule. If a service decision matters, obtain an authorized integration result instead of extrapolating from this source review.
A practical minimization review asks four questions before a test runs. Which claim requires each field? Can the claim be answered with a status or shape instead of the value? Who can see the artifact? When will the artifact be deleted? If a field has no answer to the first question, omit it. If a status is enough, do not collect a high-detail value “just in case.” Make the retention date part of the test task, not a later cleanup reminder.
Do not present an implementation reading as a compliance conclusion. The source may help identify a collection path and a configuration option, but legal duties depend on context, purpose, jurisdiction, consent, and the complete application. A privacy review should use the actual deployment and its data flows. This article is limited to explaining the cited source and suggesting a smaller diagnostic record.
For product teams, the useful outcome is usually a bounded engineering question: did the expected component resolve, did the page read the lazy property, and did the pinned dependency change? Those questions can be answered without assigning a person-level meaning to the output. If the question instead concerns a vendor decision, ask the vendor for an authorized integration test and its decision semantics.
Practical conclusion
Pin the commit, distinguish monitoring configuration from runtime network evidence, preserve component value versus error, and record whether the lazy digest was accessed. Source ordering and canonical serialization explain provenance; they do not establish identity, entropy, anonymity, or a third-party decision.
Sources
- FingerprintJS OSS
agent.tsat the pinned commit - FingerprintJS OSS
sources/index.tsat the pinned commit - FingerprintJS OSS
index.tsat the pinned commit - W3C fingerprinting guidance
- MDN fingerprinting glossary
- BotBrowser fingerprint documentation
Next reading
Use browser fingerprint verification for owned page checks and browser API integrity across realms when the same property is read from multiple contexts.
This article is part of the guide: What Is a Browser Fingerprint?
Related Articles
How to Read Browser Fingerprint Signals
FingerprintCross-Surface Browser Privacy: Keep One Context Coherent
Fingerprintwebgl_debug_renderer_info: What Your GPU Reveals to Pages
FingerprintClient Hints Fingerprinting: HTTP Headers as Identity
FingerprintCrypto Randomness Observability and Browser Fingerprints
FingerprintWhat Is a Browser Fingerprint?
Take BotBrowser from research to production
The guides cover the model first, then move into cross-platform validation, isolated contexts, and scale-ready browser deployment.