# W3C Web Bluetooth Conformance

beacio implements the full [W3C Web Bluetooth specification](https://webbluetoothcg.github.io/web-bluetooth/) on iOS Safari, verified section by section against the spec. This page states exactly what was verified, how, and where the spec leaves choices to the implementation.

Last verified: 2026-06-11.

## Scope

The conformance claim covers the **standard `navigator.bluetooth` surface**:

- the polyfill injected into pages by the beacio Safari Web Extension,
- the native Swift backend that performs all BLE work via CoreBluetooth, and
- the npm polyfill `@beacio/core`, which exposes the same surface.

Vendor extensions on `window.webbleIOS` (background sync, peripheral mode, beacon scanning, and other premium capabilities) are deliberately **outside** the standard surface and are kept off `navigator.bluetooth`. They are covered by the verification only to confirm they do not leak into or alter standard behavior.

## Method

The implementation was verified section by section against the W3C Web Bluetooth specification (Editor's Draft as of 2026-06-10), plus the separate Web Bluetooth Scanning draft where noted:

- **Every IDL member traced to evidence.** Each member of every interface in the spec's IDL index was traced through all three layers — page-visible JavaScript, the extension protocol, and the native Swift backend — and its observed behavior compared against the normative spec text for that member.
- **Findings adversarially re-verified.** Every candidate divergence was independently re-checked against the normative spec sentence it allegedly violated before being accepted; claims that did not survive re-verification were withdrawn or reclassified as documented implementation discretion.
- **Enforcement lives in the native layer.** Filter validation and canonicalization, permission grants (`[[allowedServices]]`), the GATT blocklist (generated from the W3C Web Bluetooth registries), and per-origin device IDs are enforced in native Swift code — not in page-world JavaScript that a page could bypass.
- **Fix program completed.** The verification produced 81 confirmed findings (13 P0, 31 P1, 37 P2). All 81 were fixed and re-verified: the final suites run 1,061 JavaScript tests and 920 native Swift unit tests with zero failures.

## Conformance matrix

Interface-level summary of the verified matrix (93 member rows total; 92 conformant, with the single remaining row annotated under residuals below).

| Interface / area | Members verified | Status |
|---|---|---|
| `Bluetooth` | `requestDevice()`, `getDevices()`, `getAvailability()`, `onavailabilitychanged`, `referringDevice` | Conformant |
| `BluetoothDevice` | `id` (per-origin), `name`, `gatt`, `forget()`, `watchAdvertisements()`, `watchingAdvertisements` | Conformant |
| `BluetoothRemoteGATTServer` | `connected`, `connect()`, `disconnect()`, `device` | Conformant |
| `BluetoothRemoteGATTService` | `uuid`, `isPrimary`, `getCharacteristic(s)()`, `getIncludedService(s)()` | Conformant |
| `BluetoothRemoteGATTCharacteristic` | `uuid`, `properties`, `value`, `readValue()`, all three write methods, `startNotifications()`, `stopNotifications()`, `getDescriptor(s)()` | Conformant |
| `BluetoothRemoteGATTDescriptor` | `uuid`, `value`, `readValue()`, `writeValue()` | Conformant |
| `BluetoothCharacteristicProperties` | All 9 booleans, including extended-properties-derived `reliableWrite` / `writableAuxiliaries` | Conformant |
| `BluetoothUUID` | `getService()`, `getCharacteristic()`, `getDescriptor()`, `canonicalUUID()` — full registry name tables | Conformant |
| `BluetoothAdvertisingEvent` | Constructor, `device`, `uuids`, `name`, `appearance`, `txPower`, `rssi`, `manufacturerData`, `serviceData` (maplike of DataViews) | Conformant (one residual: `appearance` is always `null` — CoreBluetooth platform limitation) |
| Events and bubbling | `availabilitychanged`, `advertisementreceived`, `gattserverdisconnected`, `characteristicvaluechanged`, `serviceadded` / `servicechanged` / `serviceremoved`; bubbling through the device → bluetooth tree; all spec event-handler attributes | Conformant |
| Navigator / global integration | `navigator.bluetooth` ([SecureContext], [SameObject]), interface objects on `window` (including `ValueEvent`), Permissions Policy `"bluetooth"`, Permissions API `"bluetooth"` descriptor, secure-context gating, iframe support | Conformant |

Security and privacy machinery required by the spec is implemented natively: filter validation with the spec's exact `TypeError` / `SecurityError` matrix, OR-across-filters / AND-within-filter matching, `exclusionFilters`, the GATT blocklist generated from the W3C Web Bluetooth registries (services, characteristics, descriptors, and manufacturer data), `[[allowedServices]]` grant enforcement on every GATT access, and per-origin device IDs so no two origins see the same identifier for a device.

## Spec-allowed choices

The spec leaves several decisions to each implementation. beacio's choices, documented for transparency:

- **LE scanning is a separate draft, exposed vendor-side.** `requestLEScan()` belongs to the Web Bluetooth *Scanning* draft, not the main specification. beacio implements it conformant to the draft's filter semantics but exposes it on the vendor surface rather than `navigator.bluetooth`, and constrains scans to service-filtered mode to stay within CoreBluetooth's background-safe scanning rules.
- **Device chooser.** `requestDevice()` shows a static snapshot chooser built from a bounded scan window (1–8 seconds) rather than a live-updating list. The spec explicitly acknowledges chooser presentation as implementation-defined, including showing a chooser with no matching devices.
- **Permission lifetime and eviction caps.** The spec allows the implementation to choose grant persistence and to evict stored grants. beacio persists grants per origin, caps the per-origin device-ID salt map at 64 origins, and evicts least-recently-used entries beyond the cap.
- **User-gesture gate.** `requestDevice()` requires transient activation, checked via `navigator.userActivation` (available on all iOS versions the extension supports).
- **Deprecated `writeValue()`.** Delegates to write-with-response, as the spec permits for `response = "optional"`.
- **Connect timeout.** Native connections time out after 10 seconds with a `NetworkError`, within the spec's allowance for implementation-defined timeouts.

## Known residuals

For full honesty, one item keeps a single matrix row short of conformant:

1. `BluetoothAdvertisingEvent.appearance` is always `null`: CoreBluetooth never exposes the Appearance AD structure to apps (platform limitation).

Previously listed residuals — absent `BluetoothDevice.name` reading `undefined` instead of `null`, included-service `isPrimary` hardcoded `false`, `startNotifications()` re-subscribing instead of early-resolving when already subscribed, and the unverified `window.ValueEvent` global — were fixed and re-verified on 2026-06-11.

This does not affect interoperability of real-world Web Bluetooth code: pages written for Chrome on Android or desktop run unchanged.

## See also

- The W3C Web Bluetooth specification: https://webbluetoothcg.github.io/web-bluetooth/
- The W3C Web Bluetooth registries (GATT blocklist): https://github.com/WebBluetoothCG/registries
- API reference: https://ioswebble.com/docs-md/api-reference.md
- Quickstart: https://ioswebble.com/docs-md/quickstart.md
