# Device disconnects

A previously connected BLE peripheral fires `gattserverdisconnected` unexpectedly, or subsequent GATT operations throw `NetworkError`. The disconnect itself is rarely the bug — it is almost always caused by one of the conditions below. Addressing them keeps the connection stable.

## Symptom

```js
device.addEventListener('gattserverdisconnected', () => {
  // fires shortly after connect()
});

// or
await service.getCharacteristic('…'); // throws NetworkError
```

## 1. Safari backgrounded the tab

When Safari is backgrounded (switched tabs, locked phone, home button), the extension's BLE session may be suspended. This is iOS behavior, not a WebBLE bug.

**Fix — foreground only:** keep the tab active while doing BLE work.

**Fix — background-safe:** register a keep-alive connection via the premium API. The WebBLE companion app maintains the GATT link regardless of Safari's foreground state.

```js
if ('webbleIOS' in window) {
  await window.webbleIOS.backgroundSync.requestBackgroundConnection({
    deviceId: device.id
  });
}
```

See [Premium APIs](../premium.md).

## 2. Peripheral went out of range / battery died

BLE links drop when the peripheral is out of range (typically 10 m / 30 ft unobstructed) or the peripheral's battery dies. Nothing to fix on the web side — reconnect when the device is available.

**Fix:** listen for `gattserverdisconnected` and call `device.gatt.connect()` again when the user re-triggers.

```js
device.addEventListener('gattserverdisconnected', async () => {
  try {
    await device.gatt.connect();
  } catch (e) {
    console.warn('reconnect failed', e);
  }
});
```

Avoid automatic reconnect loops without a user gesture — they drain battery and can trip iOS BLE rate limits.

## 3. Another app or tab claimed the peripheral

iOS serializes peripheral ownership. If the Health app (for a heart-rate monitor), a vendor app, or another Safari tab connects to the same peripheral, the WebBLE session drops.

**Fix:** disconnect from competing apps before running the web session. A deliberate `device.gatt.disconnect()` from every stale WebBLE tab also helps.

## 4. Peripheral supervision timeout exceeded

BLE's link-layer supervision timeout triggers a disconnect when a connection goes silent longer than the negotiated interval (typically 1–6 s). Extended idle periods — or a peripheral that fails to send empty data packets — surface as spurious disconnects.

**Fix:** keep characteristic notifications subscribed during the session; the periodic notification traffic keeps the link alive. If the peripheral is truly idle, subscribe to a heartbeat characteristic if one is available.

## 5. iOS woke from a deep sleep

The iPhone radio enters low-power modes and may drop idle BLE links on wake. This is indistinguishable from a real disconnect at the API level.

**Fix:** treat the event as normal; reconnect on user action.

## Still stuck?

Collect a Safari Web Inspector console trace covering `gattserverdisconnected`, attach any native logs from the WebBLE app's diagnostics view, and open an issue at [github.com/wklm/ioswebble-sdk/issues](https://github.com/wklm/ioswebble-sdk/issues).
