Docs · WhiskerJS 0.1.0 · signalsVersion 11 · idVersion 2

Diagnostics

The sampled status report, field by field, and how to turn it off.

In plain words

By default, about 1 in 100 page loads that use WhiskerJS send one small report to the WhiskerJS project saying which components worked in that browser and which did not. It helps find signals that break in a new browser release. It never contains what was measured: no values, no digests, no ids. Any one of four switches turns it off. The server that receives it keeps only daily counts and never stores IP addresses or user agents.

Turn it off

Any one is enough:

// 1. For the agent: no report from any collect() of this agent.
const agent = await load({ diagnostics: false });

// 2. For one call.
await agent.collect({ diagnostics: false });
<!-- 3. For the whole page, in the HTML (content is case-insensitive). -->
<meta name="whiskerjs-diagnostics" content="off">
  1. The visitor’s browser: navigator.globalPrivacyControl === true (Global Privacy Control), or Do Not Track set (navigator.doNotTrack, window.doNotTrack or navigator.msDoNotTrack is '1', 'yes' or 1). If these cannot be read, diagnostics are treated as off.

With diagnostics off, the library makes no network request at all. The live demo turns diagnostics off.

Other settings:

Option Default Meaning
load({ diagnostics }) true false turns it off for the agent
load({ diagnosticsEndpoint }) DIAGNOSTICS_ENDPOINT, https://diagnostics.whiskerjs.com/v1/report send to your own endpoint instead; must start with https://, anything else turns it off
load({ diagnosticsSampleRate }) DEFAULT_DIAGNOSTICS_SAMPLE_RATE, 0.01 share of page loads that send, clamped to 0 (never) to 1 (every page load, still at most once)
collect({ diagnostics }) sends when sampled false sends nothing for that call

The report

The whole body, field by field. Nothing else is sent:

Field Example Meaning
version "0.1.0" library version
signalsVersion "11" signal set version
idVersion "2" id derivation version
browser_class "chromium" the value of the browser_class component (other when the component is not ok)
durationMs 412 how long the collection took, whole milliseconds, at most 60000
components {"audio":{"status":"ok"},"canvas":{"status":"blocked","ruleId":"threw"}} every component, sorted: only its status and, when it has one, its ruleId

This is exactly the receiver’s schema: it answers 400 to any other field, a missing field, a value outside these shapes or a body over 8 KB. A result that would not fit (for example a version string the schema does not allow) is not sent at all.

diagnosticsReport(result) (exported) returns exactly this object for a given result, or null when nothing would be sent for it, so you can see what would be sent.

Never sent: component values or digests, the visitorId, the structuredId, integrity flags, the binding nonce, timestamp or digest, the page URL, the referrer, cookies, storage, or anything else from the page. The result itself is never sent by the library; your site sends it with its own request (see Integration).

How it is sent

  • When: after collect() has its result and after your onResult hook. It is never awaited, never retried, and never delays or changes collect(); any failure is ignored.
  • How often: the first collect() on a page makes the sampling decision once (for the window object, in memory; nothing is stored). Later calls on the same page, from any agent, never send. A call with diagnostics: false, an opt-out or an invalid endpoint makes no decision.
  • Sampling: the window’s Math.random() against the sample rate.
  • Request: one fetch with method: 'POST', mode: 'no-cors', credentials: 'omit' (no cookies), referrerPolicy: 'no-referrer' (no URL or referrer), cache: 'no-store', keepalive: true and a text/plain;charset=UTF-8 body (so no CORS preflight). navigator.sendBeacon is not used because it always sends cookies.

What the browser adds on its own

Like every request a browser makes, the report request carries data the library cannot remove:

  • the visitor’s IP address (the server needs it to answer) and the browser’s own request headers, including the User-Agent and the Origin (the page’s scheme and host, without the path);
  • when the endpoint is down or unreachable (an outage, a blocker, a network error), the browser itself may print one line about the failed request in the developer console and show it in the network log. The library never writes to the console; this line comes from the browser;
  • a page Content Security Policy without the endpoint in connect-src blocks the request and may report the violation to the page’s own reporting endpoint. A site that keeps diagnostics on adds https://diagnostics.whiskerjs.com to connect-src; with diagnostics off nothing beyond script-src is needed.

What the server keeps

The receiver at diagnostics.whiskerjs.com is run by the WhiskerJS project. It never stores the IP address or the user agent, nor any other header, URL or referrer, and it does not keep the report body. It only adds the report to daily counts (per version, browser class, component, status and ruleId, and a histogram of durationMs), kept 400 days. The IP address is used only in memory, as the key of a rate limiter, through a salted hash whose salt is replaced every day; there are no access logs.

If your privacy notice does not cover this, turn diagnostics off. See Privacy and what a privacy notice should say.

How the tests keep it honest

  • The compiled-source scan allows a network API only in src/diagnostics.ts and checks that file sends no credentials and no referrer.
  • The fake browser pins Math.random outside the sample, so every other test stays deterministic and sends nothing; the one test that runs against the real Node globals passes diagnostics: false.
  • Dedicated tests cover the exact payload, the unsampled case, once per page load across agents, every off switch, a sample rate of 0, a non-https endpoint, a custom endpoint and rate, and failing, hanging and throwing sends.
  • A schema test keeps a copy of the receiver’s rules and checks that every browser class, status, component name and ruleId the library can emit is accepted, that a report for each browser class and a worst case (every component with a 32-character ruleId) fit, and that a result outside the schema sends nothing.
  • The real-browser tests (Chromium, Firefox in three settings, WebKit) run twice in CI: with diagnostics on and sampling forced to every page load, the only request allowed beyond the page’s own files is exactly one diagnostics report per page load, checked against the receiver’s schema (the test answers it; it never reaches the real endpoint); with diagnostics off, the job asserts zero requests beyond the page’s own files.