| Component name | device_apis |
|---|---|
| In the visitorId | no (reported, kept out of both ids) |
| structuredId group | none |
| Hashing | plain value; a digest only when longer than 128 characters or not printable ASCII |
| Excluded under | no browser class |
| Inner timeout | 1000 ms |
| Added in | signalsVersion 12 |
| Collector revision | 1 (reported as revision with every result) |
Browsers offer web pages different ways to reach hardware: USB, Bluetooth, game controllers, virtual reality headsets, NFC tags. WhiskerJS only checks whether each of 18 such features exists, without using any of them, and writes the answer as one short code.
What it reads
Whether each of these names exists, in this order (bit 0 first):
| Bit | API | Bit | API |
|---|---|---|---|
| 0 | navigator.bluetooth |
9 | navigator.virtualKeyboard |
| 1 | navigator.hid |
10 | navigator.wakeLock |
| 2 | navigator.usb |
11 | navigator.ink |
| 3 | navigator.serial |
12 | window.NDEFReader |
| 4 | navigator.xr |
13 | window.PressureObserver |
| 5 | navigator.share |
14 | window.showOpenFilePicker |
| 6 | navigator.contacts |
15 | window.EyeDropper |
| 7 | navigator.devicePosture |
16 | window.IdleDetector |
| 8 | navigator.windowControlsOverlay |
17 | window.documentPictureInPicture |
How the technique works
Each name is tested with the in operator only: no getter runs and no method is called, so nothing can prompt or open a device chooser. The answers become a bit mask in hex, bit i for entry i (0 when none exists). The order is fixed and new entries are appended.
Why it helps
These APIs differ between engines, between desktop and mobile builds and between browsers built on the same engine, because a browser may remove some of them. Most need a secure context. It adds a little detail about the browser build for your server.
Stability
Policies and browser settings switch these APIs on and off, and new browser versions add them. That is why it is reported outside both ids. Added in library PR #33 (2026-10-27) at signalsVersion 12: it is outside both ids, so the ids did not change. No integrity check uses it: a missing API is never a sign of anything.
Browser support and what blocks it
In the self-hosted CI browser matrix of PR #33 on Linux:
| Browser | Value | Notes |
|---|---|---|
| Chrome, Chromium, Edge, Opera | 3ef9e |
no Bluetooth, Web Share, Contacts or Web NFC there |
| Brave | 3af9e |
no showOpenFilePicker |
| Opera beta (Chromium 142) | 1ef9e |
|
| Firefox | 20408 |
serial, wakeLock, documentPictureInPicture |
| Playwright’s WebKit | 400 |
wakeLock |
Expect other values on Windows, macOS, Android and iOS, where other APIs exist. unavailable / no_api only without a navigator. No browser class excludes it.
Privacy
It reads whether interfaces exist, never their contents: no device list, no prompt, no request. Leave it out with collect({ exclude: ['device_apis'] }) if you do not need it; no id changes.
How WhiskerJS handles noise and failures
| Status | ruleId | When |
|---|---|---|
ok |
a bit mask in hex | |
unavailable |
no_api |
no navigator |
blocked |
threw, security_error |
the read threw |
excluded |
caller_excluded |
you excluded it |
timed_out |
timeout, aborted |
budget or abort |
Example value
"device_apis": { "status": "ok", "value": "3ef9e", "hashed": false, "durationMs": 0.1, "revision": 1 }
Questions and answers
Can device_apis show a permission prompt?
No. It only asks whether a name exists, with the in operator. No getter runs and no method is called, so no device chooser or prompt can appear.
Does a missing API raise a flag?
No. Policies, browser settings and builds remove these APIs for good reasons, so a missing API is never treated as a sign of anything.