There are two calls: load() makes an agent and agent.collect() makes one result. Everything else is constants and types that describe the result. collect() never throws.
Everything below is exported from whiskerjs (the package’s only entry
point, ES modules). Version 0.1.0, signalsVersion 5, idVersion 2.
Functions
load(options?: LoadOptions): Promise<Agent>
Creates an agent. Does no collection work and keeps no state between
collect() calls. Never rejects: if options.env cannot be used, the
default environment is used.
agent.collect(options?: CollectOptions): Promise<CollectResult>
Runs one collection. Never throws and never rejects. Each call is independent.
bindingInput(result): string
Returns the exact text the binding digest is computed over (see
Anti-replay binding). Takes a result with its
binding; binding.digest is not part of the text. Pure: usable on a
server.
Options
LoadOptions
| Option | Type | Default | Meaning |
|---|---|---|---|
env |
Partial<Environment> |
the real browser globals | override what the library touches; mainly for tests |
onResult |
(result: CollectResult) => void | Promise<void> |
none (off) | called once per collect() with a deep copy of the result; errors are swallowed, not awaited. See Integration |
Environment
| Field | Type | Default |
|---|---|---|
window |
WindowLike (any Window-like object) |
globalThis |
now() |
() => number (milliseconds) |
window.performance.now(), else Date.now() |
setTimeout(fn, ms) |
timer | globalThis.setTimeout |
clearTimeout(handle) |
timer | globalThis.clearTimeout |
subtle |
DigestLike or undefined |
window.crypto.subtle, else globalThis.crypto.subtle |
Passing subtle: undefined explicitly simulates a browser without Web
Crypto.
CollectOptions
| Option | Type | Default | Meaning |
|---|---|---|---|
timeoutMs |
number |
5000 |
overall budget; clamped to 0 to 60000; not a finite number means 5000 |
signal |
AbortSignal |
none | aborting resolves every pending component as timed_out / aborted and skips the iframe checks |
exclude |
string[] |
[] |
component names not to collect; they report excluded / caller_excluded; unknown names and non-strings are ignored |
binding |
{ nonce: string; timestamp: number } |
none | anti-replay input from your server; see Anti-replay binding |
Types
CollectResult
| Field | Type | Meaning |
|---|---|---|
visitorId |
string | null |
32 lowercase hex characters; null without a digest |
version |
string |
library version, '0.1.0' |
signalsVersion |
string |
'5' |
idVersion |
string |
'2' |
components |
Record<string, Component> |
all 46, always present |
structuredId |
string | null |
'2_' then seven 12-hex parts joined by _; null without a digest |
integrityFlags |
string[] |
sorted names of the checks that fired |
durationMs |
number |
whole milliseconds the collection took |
binding |
Binding | null |
null unless binding was passed |
The result is plain JSON: no functions, no undefined values, no cycles.
Component
| Field | Type | Present |
|---|---|---|
status |
ComponentStatus |
always |
value |
string |
only when status is 'ok'; at most 128 printable ASCII characters |
hashed |
boolean |
only when status is 'ok'; true when value is a digest |
ruleId |
string |
only when status is not 'ok'; matches ^[a-z0-9_]{1,32}$ |
ComponentStatus
'ok' | 'unavailable' | 'blocked' | 'noisy' | 'excluded' | 'timed_out'.
Meanings and every ruleId: How it works.
Binding
| Field | Type | Meaning |
|---|---|---|
nonce |
string | null |
your nonce; null when the input was invalid |
timestamp |
number | null |
your timestamp; null when the input was invalid |
digest |
string | null |
32 hex characters; null when invalid or without a digest |
ruleId |
string (optional) |
why digest is null: invalid_binding, invalid_nonce, invalid_timestamp or no_digest |
Other types
Agent:{ collect(options?): Promise<CollectResult> }.DigestLike:{ digest(algorithm: string, data: ArrayBuffer): Promise<ArrayBuffer> }, the part ofSubtleCryptothe library uses.WindowLike:any; collectors read the browser only through it.BrowserClass:'brave' | 'firefox_rfp' | 'firefox' | 'safari' | 'chromium' | 'other'.
Constants
| Export | Value |
|---|---|
VERSION |
'0.1.0' |
SIGNALS_VERSION |
'5' |
ID_VERSION |
'2' |
DEFAULT_TIMEOUT_MS |
5000 |
COMPONENT_NAMES |
the 46 component names, sorted (frozen array) |
INTEGRITY_FLAG_NAMES |
the 14 flag names, sorted (frozen array) |
EXCLUSION_TABLE |
Record<BrowserClass, Record<componentName, ruleId>> (frozen) |
ID_GROUPS |
Record<group, componentName[]>, seven groups (frozen) |
ID_GROUP_ORDER |
['gpu', 'audio', 'fonts', 'device', 'browser', 'locale', 'prefs'] |
ID_PART_LENGTH |
12 (hex characters per structuredId part) |
Not exported but fixed: the maximum timeoutMs (60000), the inner timeout
per collector (1000 ms), the value length limit (128 characters).
Testing with a fake environment
import { load } from 'whiskerjs';
const agent = await load({
env: {
window: myFakeWindow, // anything Window-like
now: () => fakeClock.now(),
subtle: globalThis.crypto.subtle,
},
});
const result = await agent.collect();
The project’s own tests do this with test/fake-browser.ts.