import { B as BeacioErrorCode } from './error-taxonomy-CWrJx3aZ.js'; /** * Configuration for {@link withRetry}. * * **Backoff formula:** `delay = delayMs * backoffMultiplier^(attempt - 1)` * * All fields are required with documented sentinel defaults (no optional arguments). * Pass {@link DEFAULT_RETRY_OPTIONS} (optionally spread with overrides) rather than a * partial object. A sentinel in any field resolves to that field's documented default. * * @see {@link withRetry} * @see {@link DEFAULT_RETRY_OPTIONS} */ interface RetryOptions { /** Total attempts including the first call. Sentinel: `0` (use default 3). Otherwise must be a positive integer. */ maxAttempts: number; /** Base delay between retries in milliseconds. Sentinel: any negative value (use default 250). Otherwise must be non-negative. */ delayMs: number; /** Multiplier applied after each failed attempt. Sentinel: any value `< 1` (use default 1.5). Otherwise must be >= 1. */ backoffMultiplier: number; } /** * Canonical sentinel {@link RetryOptions} bag. Pass this (optionally spread with * overrides) to {@link withRetry} / `device.connectWithRetry` instead of building a * partial object: `withRetry(fn, { ...DEFAULT_RETRY_OPTIONS, maxAttempts: 5 })`. Each * field carries its documented default (3 attempts, 250 ms base delay, 1.5x backoff). */ declare const DEFAULT_RETRY_OPTIONS: RetryOptions; /** * Error class for all Beacio operations. Contains a machine-readable `code` * and a human/agent-readable `suggestion` for how to fix the issue. */ declare class BeacioError extends Error { /** Machine-readable error code for programmatic handling. */ readonly code: BeacioErrorCode; /** Actionable fix instruction — useful for agents and error UIs. */ readonly suggestion: string; /** Whether the operation is safe to retry automatically. */ readonly isRetriable: boolean; /** Suggested backoff before retrying, when known. */ readonly retryAfterMs?: number; constructor(code: BeacioErrorCode, message?: string, options?: { retryAfterMs?: number; }); /** * Convert a native error (DOMException, Error, string) to a BeacioError with * automatic code detection. The DECISION is delegated to the shared * ./error-taxonomy seam (`classifyThrown` takes the thrown value directly — * the same one the branded card classifies through) so this method holds only * what is genuinely SDK-side: which message survives onto `.message`, and the * per-code backoff hint. */ static from(error: T, code?: BeacioErrorCode): BeacioError; } /** * Retry an async operation with exponential backoff. Only retries errors * whose `isRetriable` flag is `true` (see {@link BeacioError}). * * **Retriable error codes:** `DEVICE_DISCONNECTED`, `CONNECTION_TIMEOUT`, * `GATT_OPERATION_FAILED`, `TIMEOUT`, `SCAN_ALREADY_IN_PROGRESS`, `WRITE_INCOMPLETE`. * * **Backoff formula:** `delay = delayMs * backoffMultiplier^(attempt - 1)`. * If the error includes `retryAfterMs`, that value overrides the calculated delay. * * @param fn - Async function to retry. Receives the current attempt number (1-based). * @param options - Retry configuration (defaults: 3 attempts, 250ms delay, 1.5x backoff). * @returns The result of the first successful call. * * @throws {BeacioError} The last error if all attempts fail or the error is not retriable. * @throws {BeacioError} `INVALID_PARAMETER` if options contain invalid values. * * @example * ```typescript * import { withRetry } from '@beacio/core' * * const value = await withRetry(async (attempt) => { * console.log(`Attempt ${attempt}`) * return await device.read('battery_service', 'battery_level') * }, { maxAttempts: 5, delayMs: 500, backoffMultiplier: 2 }) * ``` * * @see {@link RetryOptions} * @see {@link BeacioError.isRetriable} */ declare function withRetry(fn: (attempt: number) => Promise, options?: RetryOptions): Promise; /** * Runtime platform where Beacio is executing. * * - `'auto'` -- Sentinel: resolve the real platform at runtime via {@link detectPlatform}. * This is the default value of {@link BeacioOptions.platform}; it never appears as a * resolved `Beacio.platform` value (that is always one of the three concrete states below). * - `'safari-extension'` -- iOS Safari with the Beacio extension installed * - `'native'` -- Browser with built-in Web Bluetooth (Chrome, Edge, etc.) * - `'unsupported'` -- No Web Bluetooth capability detected */ type Platform = 'auto' | 'safari-extension' | 'native' | 'unsupported'; /** * Configuration options for the {@link Beacio} constructor. * * All fields are required with documented sentinel defaults so the public surface has * no optional arguments. {@link DEFAULT_BEACIO_OPTIONS} is the canonical sentinel bag — * pass it (optionally spread with overrides) rather than constructing a partial object. */ interface BeacioOptions { /** * Force a specific platform, or `'auto'` to auto-detect via {@link detectPlatform}. * Sentinel: `'auto'` (auto-detect). */ platform: Platform; /** * Maximum concurrently connected SDK-managed devices for this Beacio instance. * Throws `CONNECTION_LIMIT_REACHED` when exceeded. Sentinel: `0` (unlimited). */ maxConnections: number; /** * Service UUIDs to seed the instance-wide optionalServices registry once, so * they are merged into every {@link Beacio.requestDevice} call's effective * `optionalServices` without per-call boilerplate. Accepts names (`'battery_service'`), * 4/8-hex, or full 128-bit UUIDs — each is resolved via {@link resolveUUID} and * de-duped. Equivalent to calling {@link Beacio.registerServices} in the constructor. * This NEVER widens the device picker (it does not touch `filters` or synthesize * `acceptAllDevices`) — it only declares post-connection GATT access intent. * Sentinel: `[]` (none). */ defaultOptionalServices: string[]; } /** * Canonical sentinel {@link BeacioOptions} bag. Pass this (optionally spread with * overrides) to the {@link Beacio} constructor instead of building a partial object: * `new Beacio({ ...DEFAULT_BEACIO_OPTIONS, maxConnections: 4 })`. Each field carries * the documented default: `'auto'` platform detection, `0` (unlimited) connections, * and an empty default-optional-services registry. */ declare const DEFAULT_BEACIO_OPTIONS: BeacioOptions; /** * Declarative native periodic-write keep-warm request (SB-NAT-04). * * Poll-driven devices (e.g. S&B Venty/Veazy) emit no unsolicited notifications — * their telemetry only advances because the page writes a status-request frame * every ~500 ms. A backgrounded Safari tab issues no writes, so the session goes * stale. Declaring this lets the native iOS side re-issue the frame on a * **best-effort, battery-safe CLAMPED cadence** while it holds the connection in * the background, then route the reply through the same condition pipeline as a * pushed notification. The page only DECLARES intent — the cadence floor and * frame interpretation live in the native layer (a BLE invariant). */ interface PeriodicWriteOptions { /** GATT service UUID owning the writable control characteristic. Accepts names, 4/8-hex, or full 128-bit UUIDs. */ serviceUUID: BluetoothServiceUUID; /** Writable characteristic the keep-warm frame is written to. Accepts names, 4/8-hex, or full 128-bit UUIDs. */ characteristicUUID: BluetoothCharacteristicUUID; /** Exact bytes to write each tick (e.g. the device's status-request command). An empty array is a no-op. */ payload: number[]; /** * Requested interval in milliseconds. NOTE: the native side clamps any * sub-floor value UP to a battery-safe minimum and only polls within the * granted iOS background window, so the page's foreground cadence (e.g. 500 ms) * is **never guaranteed** in the background — this is a best-effort poll, not a * real-time loop. */ intervalMs: number; } /** Options for registering a background keep-alive connection. */ interface BackgroundConnectionOptions { /** Unique identifier of the device to maintain a background connection to (from `BeacioDevice.id`). */ deviceId: string; /** * Optional native periodic-write keep-warm poll (SB-NAT-04). Omit to hold the * connection passively (the native side relays only values the device pushes). * Supply it for poll-driven devices so telemetry keeps advancing while Safari * is backgrounded, on a clamped best-effort cadence (see {@link PeriodicWriteOptions}). */ periodicWrite?: PeriodicWriteOptions; } /** * Notification permission state, mirroring the Notification API. * * - `'granted'` -- User has allowed notifications * - `'denied'` -- User has blocked notifications * - `'prompt'` -- User has not yet been asked */ type NotificationPermissionState = 'granted' | 'denied' | 'prompt'; /** * Template for iOS notifications delivered by background sync. * Supports placeholder interpolation: `{{value.utf8}}`, `{{value.hex}}`, `{{deviceName}}`, `{{timestamp}}`. */ interface NotificationTemplate { /** Notification title. Supports `{{placeholder}}` interpolation. */ title: string; /** Notification body text. Supports `{{placeholder}}` interpolation. */ body: string; /** Deep-link URL opened when the user taps the notification. */ url?: string; /** Play the default notification sound. Defaults to true when omitted. */ sound?: boolean; } /** Configuration for an interactive reply action on a notification. */ interface ReplyActionConfig { /** Button label shown in the notification (e.g. "Reply", "Send Command"). */ actionTitle: string; /** Placeholder text in the reply text field. */ placeholder?: string; } /** * Decoder format applied to raw characteristic bytes before evaluating a {@link NotificationCondition}. * Determines how the raw `DataView` bytes are interpreted as a numeric value. */ type ConditionDecoder = 'uint8' | 'int16be' | 'int16le' | 'int32be' | 'float32le' | 'float32be'; /** * Comparison operator for evaluating whether a characteristic value should trigger a notification. * * - `'changed'` -- Fire when the decoded value differs from the previous reading * - `'always'` -- Fire on every characteristic update regardless of value * - Numeric operators compare the decoded value against {@link NotificationCondition.threshold} */ type ConditionOperator = 'gt' | 'lt' | 'gte' | 'lte' | 'eq' | 'neq' | 'changed' | 'always'; /** Condition that must be met for a background characteristic notification to fire. */ type NotificationCondition = { /** How to decode the raw characteristic bytes into a number. */ decode: ConditionDecoder; /** * Byte offset into the characteristic value where the scalar is decoded. * Defaults to `0`. Lets a condition read past the start of a vendor frame — * e.g. the S&B Venty/Veazy CMD `0x01` reply puts the settings bitmask at * byte 14 and the auto-shutoff countdown at byte 9. */ byteOffset?: number; /** * Optional bitwise-AND mask applied to the decoded **integer** before the * comparison, to isolate a single status bit in a packed flags byte. For * example, `{ decode: 'uint8', byteOffset: 14, mask: 0x02, operator: 'eq', * threshold: 2 }` fires on Venty/Veazy "setpoint reached" (byte 14, bit 1). * Ignored for the `float32*` decoders. */ mask?: number; /** * Optional multiplier applied to the decoded value before the comparison, so * a raw integer can be compared in engineering units (e.g. a uint16 that is * `÷10 = °C` uses `scale: 0.1`, letting `threshold` be a real temperature). */ scale?: number; } & ({ /** Comparison operator for edge-triggered notifications that do not compare against a fixed threshold. */ operator: 'changed' | 'always'; } | { /** Comparison operator to evaluate against `threshold`. */ operator: Exclude; /** Reference value for numeric comparison operators. */ threshold: number; }); /** * Options for registering background characteristic notifications. * When the condition is met, an iOS notification is delivered using the template. */ interface CharacteristicNotificationOptions { /** Device identifier (from `BeacioDevice.id`). */ deviceId: string; /** GATT service UUID containing the characteristic. Accepts names, 4/8-hex, or full 128-bit UUIDs. */ serviceUUID: BluetoothServiceUUID; /** GATT characteristic UUID to monitor. Accepts names, 4/8-hex, or full 128-bit UUIDs. */ characteristicUUID: BluetoothCharacteristicUUID; /** Notification content template with placeholder support. */ template: NotificationTemplate; /** Condition that must be satisfied for the notification to fire. */ condition: NotificationCondition; /** Optional interactive reply action attached to the notification. */ replyAction?: ReplyActionConfig; /** Minimum seconds between consecutive notifications for this registration. Prevents notification spam. */ cooldownSeconds?: number; } /** Filter criteria for beacon scanning. Multiple filters are OR-combined. */ interface BeaconScanFilter { /** Service UUIDs to match in advertisement data. */ services?: BluetoothServiceUUID[]; /** Match devices whose name starts with this prefix (case-sensitive). */ namePrefix?: string; } /** Options for registering a background beacon scan that delivers iOS notifications on discovery. */ interface BeaconScanningOptions { /** One or more filters to match against BLE advertisements. Filters are OR-combined. */ filters: BeaconScanFilter[]; /** Minimum seconds between consecutive beacon notifications. Prevents notification spam. */ cooldownSeconds?: number; /** Notification content template delivered when a matching beacon is found. */ template: NotificationTemplate; } /** * Discriminator for background sync registration types. * * - `'connection'` -- Keep-alive device connection * - `'characteristic-notification'` -- Characteristic value monitoring with iOS notifications * - `'beacon-scan'` -- BLE advertisement scanning with iOS notifications */ type BackgroundRegistrationType = 'connection' | 'characteristic-notification' | 'beacon-scan'; /** Handle for an active background sync registration. Use to update or cancel the registration. */ interface BackgroundRegistration { /** Unique identifier for this registration. */ readonly id: string; /** The kind of background operation this registration represents. */ readonly type: BackgroundRegistrationType; /** Unix timestamp (ms) when the registration was created. */ readonly createdAt: number; /** Unix timestamp (ms) of the last time this registration triggered a notification. */ readonly lastTriggeredAt?: number; /** Cancel this registration and stop the background operation. */ unregister(): Promise; /** Update the notification template for this registration. */ update(template: Partial): Promise; } /** * Background sync API for maintaining BLE connections and delivering iOS notifications * when Safari is not in the foreground. * * Access via `ble.backgroundSync`. Requires the companion app to be running in IPC relay mode. * Falls back to a stub that throws `BLUETOOTH_UNAVAILABLE` when Bluetooth is unavailable * or `GATT_OPERATION_FAILED` when the extension runtime is missing. */ interface BeacioBackgroundSync { /** Request iOS notification permission. Must be granted before registering notification-based syncs. */ requestPermission(): Promise; /** Register a keep-alive background connection to a device. */ requestBackgroundConnection(options: BackgroundConnectionOptions): Promise; /** Register characteristic monitoring with iOS notification delivery. */ registerCharacteristicNotifications(options: CharacteristicNotificationOptions): Promise; /** Register beacon scanning with iOS notification delivery on discovery. */ registerBeaconScanning(options: BeaconScanningOptions): Promise; /** List all active background sync registrations for the current origin. */ getRegistrations(): Promise; /** Cancel a registration by ID. */ unregister(registrationId: string): Promise; /** Update the notification template of an existing registration. */ update(registrationId: string, template: Partial): Promise; /** Release all resources held by the background sync manager. */ destroy(): void; /** Alias for {@link requestBackgroundConnection}. */ connect(options: BackgroundConnectionOptions): Promise; /** Alias for {@link registerCharacteristicNotifications}. */ subscribe(options: CharacteristicNotificationOptions): Promise; /** Alias for {@link registerBeaconScanning}. */ scan(options: BeaconScanningOptions): Promise; /** Alias for {@link getRegistrations}. */ list(): Promise; } /** Options for starting BLE peripheral advertising. */ interface BeacioPeripheralAdvertisingOptions { /** Local name included in advertisement data. */ localName?: string; /** Services to register before advertising begins. */ services?: BeacioPeripheralServiceDefinition[]; /** Service UUIDs to include in advertisement packets. */ serviceUUIDs?: BluetoothServiceUUID[]; /** Manufacturer-specific data included in advertisements. */ manufacturerData?: Array<{ /** Bluetooth SIG company identifier (e.g. 0x004C for Apple). */ companyIdentifier: number; /** Raw manufacturer data payload. */ data: BufferSource; }>; /** Service-specific data included in advertisements. */ serviceData?: Array<{ /** Service UUID this data is associated with. */ service: BluetoothServiceUUID; /** Raw service data payload. */ data: BufferSource; }>; /** Whether the peripheral accepts incoming connections. Defaults to true. */ connectable?: boolean; /** Transmit power level in dBm included in advertisements. */ txPower?: number; } /** Incoming write request from a connected central device. */ interface BeacioPeripheralWriteRequest { /** Platform device identifier of the central. */ deviceId?: string; /** CoreBluetooth central UUID. */ centralUUID?: string; /** Target service UUID. */ serviceUuid?: string; /** Target characteristic UUID. */ characteristicUuid?: string; /** Written value (type depends on platform encoding). */ value?: ArrayBuffer | DataView | number[]; /** Byte offset for prepared writes. */ offset?: number; /** True if the central used write-without-response. */ withoutResponse?: boolean; } /** Connection state change event from a central device. */ interface BeacioPeripheralConnectionStateChange { /** Platform device identifier of the central. */ deviceId?: string; /** CoreBluetooth central UUID. */ centralUUID?: string; /** Whether the central is now connected. */ connected?: boolean; /** Total number of currently subscribed centrals across all characteristics. */ subscriberCount?: number; } /** Subscription (notify/indicate) state change from a central device. */ interface BeacioPeripheralSubscriptionChange { /** Platform device identifier of the central. */ deviceId?: string; /** CoreBluetooth central UUID. */ centralUUID?: string; /** Service UUID of the subscribed characteristic. */ serviceUuid?: string; /** Characteristic UUID the central subscribed to or unsubscribed from. */ characteristicUuid?: string; /** Whether the central is now subscribed (true) or unsubscribed (false). */ subscribed?: boolean; /** Total subscriber count for this characteristic after the change. */ subscriberCount?: number; } /** Options for sending a notification/indication to subscribed centrals. */ interface BeacioPeripheralSendOptions { /** Service UUID containing the characteristic. */ serviceUuid: string; /** Characteristic UUID to send the value update on. */ characteristicUuid: string; /** Raw value to send as a notification/indication. */ value: BufferSource; } /** * GATT characteristic property for peripheral-mode services. * Determines which operations centrals can perform on the characteristic. */ type BeacioPeripheralCharacteristicProperty = 'read' | 'write' | 'writeWithoutResponse' | 'notify' | 'indicate'; /** Definition of a characteristic within a peripheral-mode GATT service. */ interface BeacioPeripheralCharacteristicDefinition { /** Characteristic UUID. Mutually exclusive with `uuid` (provide one). */ characteristicUuid?: BluetoothCharacteristicUUID; /** Characteristic UUID (alias). Mutually exclusive with `characteristicUuid`. */ uuid?: BluetoothCharacteristicUUID; /** Supported operations. Array form or object form (`{ read: true, notify: true }`). */ properties?: BeacioPeripheralCharacteristicProperty[] | Partial>; /** ATT permission set. Accepts synonyms: `'writeable'`/`'writable'` are equivalent to `'write'`. */ permissions?: Array<'read' | 'readable' | 'write' | 'writeable' | 'writable'>; /** Initial static value for read requests before any writes occur. */ value?: BufferSource; } /** Definition of a GATT service to register in peripheral mode. */ interface BeacioPeripheralServiceDefinition { /** Service UUID. Mutually exclusive with `uuid` (provide one). */ serviceUuid?: BluetoothServiceUUID; /** Service UUID (alias). Mutually exclusive with `serviceUuid`. */ uuid?: BluetoothServiceUUID; /** Whether this is a primary service. Defaults to true. */ isPrimary?: boolean; /** Characteristics to include in this service. */ characteristics?: BeacioPeripheralCharacteristicDefinition[]; } /** Snapshot of a registered peripheral characteristic at a point in time. */ interface BeacioPeripheralCharacteristicRecord { /** Canonical service UUID this characteristic belongs to. */ serviceUuid: string; /** Canonical characteristic UUID. */ characteristicUuid: string; /** List of property names this characteristic supports. */ properties: string[]; /** Current characteristic value as raw bytes. */ value: Uint8Array; /** Number of centrals currently subscribed to this characteristic. */ subscriberCount: number; /** Number of notification updates queued but not yet delivered. */ pendingNotifications: number; } /** Snapshot of a registered peripheral service at a point in time. */ interface BeacioPeripheralServiceRecord { /** Canonical service UUID. */ serviceUuid: string; /** Whether this is a primary service. */ isPrimary: boolean; /** Characteristics registered within this service. */ characteristics: BeacioPeripheralCharacteristicRecord[]; } /** Result of sending a notification/indication to subscribed centrals. */ interface BeacioPeripheralSendResult { /** Whether the update was accepted by the platform (passed initial validation). */ accepted: boolean; /** Whether the value was immediately transmitted to at least one central. */ sent: boolean; /** Whether the update was queued for later delivery (e.g. central's transmit window full). */ queued: boolean; /** Number of pending (queued but undelivered) updates for this characteristic. */ pendingCount: number; /** Number of centrals currently subscribed to this characteristic. */ subscriberCount: number; /** Service UUID of the updated characteristic. */ serviceUuid: string; /** Characteristic UUID that was updated. */ characteristicUuid: string; } /** Event type map for the peripheral `addEventListener` interface. */ interface BeacioPeripheralEventMap { /** Fired when a connected central writes to a characteristic. */ writerequest: CustomEvent; /** Fired when a central subscribes to or unsubscribes from notifications. */ subscriptionchange: CustomEvent; /** Fired when a central connects or disconnects. */ connectionstatechange: CustomEvent; /** Fired when advertising state changes (started/stopped). */ advertisingstatechange: CustomEvent<{ advertising?: boolean; localName?: string | null; serviceUUIDs?: string[]; }>; /** Fired when a queued notification has been delivered and the characteristic is ready for more. */ notificationready: CustomEvent; } /** Detail payload for the `notificationready` event. */ interface BeacioPeripheralNotificationReady { /** Service UUID of the characteristic that became ready. */ serviceUuid?: string; /** Characteristic UUID that is ready to accept more notifications. */ characteristicUuid?: string; /** Number of remaining queued updates after this delivery. */ pendingCount?: number; } /** * Peripheral-mode API for acting as a BLE GATT server. * Access via `ble.peripheral`. Supports service registration, advertising, and notification delivery. * Falls back to a stub that throws `GATT_OPERATION_FAILED` on unsupported platforms. */ interface BeacioPeripheral { /** Whether the peripheral is currently advertising. */ readonly advertising: boolean; /** Start advertising with the given options. Registers any included services first. */ advertise(options: BeacioPeripheralAdvertisingOptions): Promise; /** Register a GATT service. Must be called before advertising if not included in advertise options. */ addService(service: BeacioPeripheralServiceDefinition): Promise; /** Stop advertising. Does not unregister services. */ stopAdvertising(): Promise; /** Send a notification/indication value update to all subscribed centrals. */ send(options: BeacioPeripheralSendOptions): Promise; /** Release all resources, stop advertising, and unregister services. */ destroy(): void; /** Register an event listener. See {@link BeacioPeripheralEventMap} for event types. */ addEventListener(type: string, listener: EventListenerOrEventListenerObject | null, options?: boolean | AddEventListenerOptions): void; /** Remove a previously registered event listener. */ removeEventListener(type: string, listener: EventListenerOrEventListenerObject | null, options?: boolean | EventListenerOptions): void; /** Called when a central writes to a characteristic. */ onwriterequest: ((this: BeacioPeripheral, ev: Event) => void) | null; /** Called when a central subscribes to or unsubscribes from notifications. */ onsubscriptionchange: ((this: BeacioPeripheral, ev: Event) => void) | null; /** Called when a central connects or disconnects. */ onconnectionstatechange: ((this: BeacioPeripheral, ev: Event) => void) | null; /** Called when advertising state changes. */ onadvertisingstatechange: ((this: BeacioPeripheral, ev: Event) => void) | null; /** Called when a queued notification has been delivered and the characteristic is ready for more. */ onnotificationready: ((this: BeacioPeripheral, ev: Event) => void) | null; } /** * Filter criteria for BLE device discovery. Within a single filter, all specified * fields must match (AND logic). Multiple filters in `RequestDeviceOptions.filters` * are OR-combined. */ interface BluetoothLEScanFilter { /** Service UUIDs the device must advertise. Accepts names, 4/8-hex, or full 128-bit UUIDs. */ services?: string[]; /** Exact device name to match (case-sensitive). */ name?: string; /** Match devices whose name starts with this prefix (case-sensitive). */ namePrefix?: string; /** Filter by manufacturer-specific data. */ manufacturerData?: { companyIdentifier: number; dataPrefix?: BufferSource; }[]; /** Filter by service data. */ serviceData?: { service: string; dataPrefix?: BufferSource; }[]; } /** * Options for `requestDevice()`. Define which BLE devices appear in the picker. * * **Filter semantics:** * - `filters` array entries are OR-combined (device matches if ANY filter matches) * - Within a single filter, all specified fields are AND-combined (device must match ALL) * - `exclusionFilters` are applied after `filters` to remove unwanted devices * - `acceptAllDevices: true` cannot be combined with `filters` * * **Service access:** Only services listed in `filters[].services` or `optionalServices` * can be accessed after connection. `optionalServices` does NOT affect the device picker -- * it only declares post-connection GATT access intent. * * @see {@link BluetoothLEScanFilter} */ interface RequestDeviceOptions { /** Device filters. OR-combined; within each filter, fields are AND-combined. */ filters?: BluetoothLEScanFilter[]; /** Filters applied after `filters` to exclude specific devices from results. */ exclusionFilters?: BluetoothLEScanFilter[]; /** Additional service UUIDs the app needs access to post-connection (does NOT affect picker). */ optionalServices?: string[]; /** Manufacturer data IDs to request access to post-connection. */ optionalManufacturerData?: number[]; /** Accept any device without filtering. Cannot be combined with `filters`. */ acceptAllDevices?: boolean; } /** Emitted when the notification queue exceeds `maxQueueSize`. */ interface QueueOverflowEvent { /** Service UUID of the overflowing characteristic. */ service: string; /** Characteristic UUID of the overflowing notification stream. */ characteristic: string; /** The overflow strategy in effect when the overflow occurred. */ strategy: NotificationOverflowStrategy; /** Maximum queue size that was exceeded. */ queueSize: number; /** Cumulative count of dropped notifications since the subscription started. */ droppedCount: number; } /** * Eviction metadata for a NATIVE notification-queue overflow, decoded from the * `beacio:overflow` CustomEvent the polyfill dispatches on a * `BluetoothRemoteGATTCharacteristic` when Safari's bounded Swift `EventQueue` * evicts notifications under sustained high-frequency load. * * **Distinct from {@link QueueOverflowEvent}.** That event describes the *JS* * `device.notifications()` async-iterator queue overflowing (a client-side * backpressure mechanism the page configures). This event describes the * *native* bridge's own bounded queue dropping samples before they ever reach * JS — so the page learns it has a silent gap in its * `characteristicvaluechanged` stream and can re-read to resynchronise. The * fields differ accordingly; do not conflate the two. * * Each field is `undefined` only if the native bridge omitted it from the * signal (a forward-compat guard); a conforming bridge always supplies all four. * * @see {@link BeacioDevice.onCharacteristicOverflow} */ interface NativeOverflowEvent { /** Number of notifications the native queue evicted in this overflow. */ evictedCount?: number; /** Capacity of the native bounded queue that was exceeded. */ queueCapacity?: number; /** Next expected notification sequence number, so the page can quantify the gap. */ seq?: number; /** Epoch-millis timestamp the native bridge stamped on the overflow. */ timestamp?: number; } /** Context information attached to device-level error events. */ interface DeviceErrorContext { /** The operation that triggered the error (e.g. `'device.subscribe.onError'`, `'notification.recover'`). */ operation: string; /** Service UUID involved in the failed operation, when applicable. */ service?: string; /** Characteristic UUID involved in the failed operation, when applicable. */ characteristic?: string; /** Additional diagnostic details. */ details?: { [key: string]: string | number | boolean | null; }; } /** * Reason for a device disconnection. * * - `'intentional'` -- `disconnect()` was called by the application * - `'unexpected'` -- Connection dropped (out of range, device powered off, etc.) * - `'service-change'` -- Device's GATT database changed (firmware update, etc.) */ type DisconnectReason = 'intentional' | 'unexpected' | 'service-change'; /** * Backoff parameters for automatic reconnection after unexpected disconnects. * * **Backoff formula:** `delay = min(initialDelayMs * backoffMultiplier^(attempt-1), maxDelayMs)` * * @see {@link ConnectOptions.autoReconnect} */ interface AutoReconnectOptions { /** Maximum reconnection attempts before giving up. Defaults to Infinity. */ maxAttempts?: number; /** Initial delay in ms before first reconnection attempt. Defaults to 1000. */ initialDelayMs?: number; /** Maximum delay in ms between attempts (exponential backoff cap). Defaults to 30000. */ maxDelayMs?: number; /** Backoff multiplier applied after each failed attempt. Defaults to 2. */ backoffMultiplier?: number; } /** * Options for `device.connect()`. * * @see {@link AutoReconnectOptions} */ interface ConnectOptions { /** * Automatically reconnect on unexpected disconnects using exponential backoff. * Pass `true` for defaults (1s initial, 30s max, 2x multiplier, infinite attempts) * or an {@link AutoReconnectOptions} object to customize. * Auto-reconnect is stopped by calling `disconnect()`, reaching `maxAttempts`, * or calling `connect()` again with new options. */ autoReconnect?: boolean | AutoReconnectOptions; } /** Emitted when a subscription could not be recovered after reconnection (e.g. characteristic no longer exists). */ interface SubscriptionLostEvent { /** Service UUID of the lost subscription. */ service: string; /** Characteristic UUID of the lost subscription. */ characteristic: string; /** The error that prevented recovery. */ error: Error; } /** Snapshot of an active notification subscription's state. */ interface ActiveSubscription { /** Service UUID this subscription is registered on. */ service: string; /** Characteristic UUID this subscription is receiving notifications from. */ characteristic: string; /** Number of callback functions currently receiving notifications for this characteristic. */ callbackCount: number; /** Whether this subscription will be automatically restored after reconnection. */ autoRecovering: boolean; /** Whether the native BLE notification is currently active on the platform. */ nativeActive: boolean; } /** Options for `device.read()`. */ interface ReadOptions { /** Timeout in ms for the read operation. No timeout if omitted. */ timeoutMs?: number; } /** * Write mode for GATT characteristic writes. * * - `'with-response'` -- Write with acknowledgment (ATT Write Request). Slower but reliable. * - `'without-response'` -- Fire-and-forget write (ATT Write Command). Faster but no error feedback. */ type WriteMode = 'with-response' | 'without-response'; /** Options for single-packet `device.write()` operations. */ interface WriteOptions { /** Write mode. Defaults to `'with-response'`. */ mode?: WriteMode; /** Timeout in ms for the write operation. No timeout if omitted. */ timeoutMs?: number; } /** * Options for `device.writeLarge()` -- chunked writes without per-chunk retry. * * **Chunk size determination order:** * 1. Explicit `chunkSize` if provided * 2. Platform-reported write limits (`getWriteLimits()`) * 3. `MTU - 3` (ATT header overhead) * 4. 20-byte conservative fallback * * @see {@link WriteFragmentedOptions} for chunked writes with per-chunk retry */ interface WriteLargeOptions extends WriteOptions { /** * Explicit chunk size in bytes for segmented writes. * When omitted, SDK uses platform write limits when available. */ chunkSize?: number; } /** Result of a `device.writeLarge()` operation. */ interface WriteLargeResult { /** Total bytes successfully written. */ bytesWritten: number; /** Total bytes in the original payload. */ totalBytes: number; /** Chunk size used for segmentation. */ chunkSize: number; /** Number of write operations performed. */ chunkCount: number; } /** * Options for `device.writeFragmented()` -- chunked writes with per-chunk retry. * * Extends {@link WriteLargeOptions} with retry semantics and an explicit MTU override. * The MTU-3 formula applies: BLE ATT header = 3 bytes, so max payload = MTU - 3. * * @see {@link WriteLargeOptions} for chunk size determination order * @see {@link WriteAutoOptions} for automatic fragmentation decisions */ interface WriteFragmentedOptions extends WriteLargeOptions { /** * Optional MTU override. When provided, chunk size becomes `mtu - 3` * unless `chunkSize` is set explicitly. */ mtu?: number; /** Retries per chunk before failing the whole write. Defaults to 0 (no retries). */ maxRetries?: number; /** Delay in ms between chunk retries. Defaults to 0. */ retryDelayMs?: number; } /** Result of a `device.writeFragmented()` operation. Extends {@link WriteLargeResult} with retry count. */ interface WriteFragmentedResult extends WriteLargeResult { /** Total number of retries across all chunks. */ retryCount: number; } /** Options for `device.writeAuto()`. Inherits all fragmentation and retry options. */ interface WriteAutoOptions extends WriteFragmentedOptions { } /** Result of a `device.writeAuto()` operation. Indicates whether fragmentation was used. */ interface WriteAutoResult extends WriteFragmentedResult { /** Whether the payload was split into multiple chunks (true) or sent as a single write (false). */ fragmented: boolean; } /** * Platform-reported write payload limits and negotiated ATT MTU. * Fields are `null` when the platform does not expose that information. * * @see {@link BeacioDevice.getWriteLimits} */ interface WriteLimits { /** Maximum payload bytes for write-with-response, or `null` if unknown. */ withResponse: number | null; /** Maximum payload bytes for write-without-response, or `null` if unknown. */ withoutResponse: number | null; /** Negotiated ATT MTU in bytes, or `null` if unknown. Max payload = `mtu - 3`. */ mtu: number | null; } /** Callback function invoked with each characteristic notification value. */ type NotificationCallback = (value: DataView) => void; /** * Strategy for handling notification queue overflow. * * - `'error'` -- Throw an error and stop the notification stream (fail-fast) * - `'drop-oldest'` -- Discard the oldest buffered value to make room (lossy FIFO) * - `'drop-newest'` -- Discard the incoming value (backpressure) */ type NotificationOverflowStrategy = 'error' | 'drop-oldest' | 'drop-newest'; /** * Options for `device.notifications()` async iterator. * * @see {@link NotificationOverflowStrategy} */ interface NotificationOptions { /** * Maximum buffered notification events before overflow handling applies. * Defaults to 256. Increase for high-throughput characteristics. */ maxQueueSize?: number; /** Strategy for handling overflow. Defaults to `'error'`. */ overflowStrategy?: NotificationOverflowStrategy; /** Callback invoked on every overflow event, regardless of strategy. */ onOverflow?: (event: QueueOverflowEvent) => void; } /** * Options for `device.subscribe()` and `device.subscribeAsync()`. * * @see {@link BeacioDevice.subscribe} * @see {@link BeacioDevice.subscribeAsync} */ interface SubscribeOptions { /** Automatically re-subscribe after reconnection. Defaults to `true`. */ autoRecover?: boolean; /** Error callback for asynchronous setup failures. Only used by `subscribe()` (not `subscribeAsync()`). */ onError?: (error: Error) => void; } type DeviceHooks = { beforeConnect?: (device: BeacioDevice) => void; onConnectionChange?: (device: BeacioDevice) => void; }; type DisconnectListener = (reason: DisconnectReason) => void; type Listener = () => void; type QueueOverflowListener = (event: QueueOverflowEvent) => void; type SubscriptionLostListener = (event: SubscriptionLostEvent) => void; type ErrorListener = (error: Error, context: DeviceErrorContext) => void; /** * Represents a connected BLE device. Provides methods to read, write, * and subscribe to GATT characteristics using human-readable UUID names. * * @example * ```typescript * const device = await ble.requestDevice({ filters: [{ services: ['heart_rate'] }] }) * await device.connect() * * // Read a value * const battery = await device.read('battery_service', 'battery_level') * * // Subscribe to notifications * const unsub = device.subscribe('heart_rate', 'heart_rate_measurement', (data) => { * console.log('Heart rate:', data.getUint8(1)) * }) * * // Cleanup * unsub() * device.disconnect() * ``` */ declare class BeacioDevice { readonly id: string; /** Device name, mirroring the WebIDL `DOMString?` shape: null when the * device has no name (never undefined). */ readonly name: string | null; readonly raw: BluetoothDevice; private server; private primaryServicesCache; private serviceCache; private charCache; private recoveryRegistry; private disconnectListeners; private reconnectedListeners; private queueOverflowListeners; private subscriptionLostListeners; private errorListeners; private reconnectGate; private intentionalDisconnect; private lastDisconnectReason; private autoReconnectConfig; private autoReconnectAbort; private readonly writeChunker; private readonly notificationManager; private readonly hooks; constructor(device: BluetoothDevice, hooks?: DeviceHooks); get connected(): boolean; /** * Connect to the device's GATT server. Must be called before any read/write/subscribe * operation. No-op if already connected. * * **Auto-reconnect lifecycle:** * Pass `autoReconnect: true` for default exponential backoff (1s initial, 30s max, * 2x multiplier, infinite attempts), or pass an {@link AutoReconnectOptions} object * to customize. On unexpected disconnect, the SDK automatically retries connection * and recovers all subscriptions registered with `autoRecover: true`. * * Auto-reconnect stops when: * - `disconnect()` is called (intentional disconnect) * - `maxAttempts` is exhausted (emits error via `addErrorListener`) * - `connect({ autoReconnect: ... })` is called again while disconnected and replaces the reconnect config * * @param options - Connection options including auto-reconnect configuration. * * @throws {BeacioError} `GATT_OPERATION_FAILED` -- device has no GATT server * @throws {BeacioError} `CONNECTION_LIMIT_REACHED` -- `Beacio.maxConnections` exceeded * * @example * ```typescript * // Simple connection * await device.connect() * * // With auto-reconnect (default backoff) * await device.connect({ autoReconnect: true }) * * // Custom backoff: 500ms initial, 10s max, 3x multiplier, 5 attempts * await device.connect({ * autoReconnect: { * initialDelayMs: 500, * maxDelayMs: 10000, * backoffMultiplier: 3, * maxAttempts: 5, * }, * }) * ``` * * @see {@link ConnectOptions} * @see {@link AutoReconnectOptions} * @see {@link connectWithRetry} */ connect(options?: ConnectOptions): Promise; /** * Disconnect from the device, stop auto-reconnect, and clean up all subscriptions. * Clears all notification recovery registrations, aborts in-flight writes, and * releases the GATT server. Fires `'disconnected'` event with reason `'intentional'`. * * Safe to call multiple times or when already disconnected. */ disconnect(): void; /** * Connect with automatic retry using {@link withRetry}. * Convenience wrapper that combines `connect()` with the SDK's retry utility. * * Use this for one-shot retry on initial connection. For persistent auto-reconnect * after unexpected disconnects, use `connect({ autoReconnect: true })` instead. * * @param options - Retry options (defaults: 3 attempts, 250ms delay, 1.5x backoff). * * @throws {BeacioError} Last error from the final failed attempt * * @see {@link connect} * @see {@link withRetry} */ connectWithRetry(options?: RetryOptions): Promise; /** * Read a characteristic value. Return raw `DataView` bytes, or apply a parse * function to get a typed result. * * Service and characteristic names (e.g. `'battery_service'`, `'battery_level'`) * are resolved to full 128-bit UUIDs via {@link resolveUUID}. * * For typed reads, use the overload with a parse function (e.g. from `@beacio/core/profiles`): * ```typescript * const hr = await device.read('heart_rate', 'heart_rate_measurement', parseHeartRate) * console.log(hr.bpm) // typed HeartRateData * ``` * * @param service - Service UUID or name (e.g. `'heart_rate'`, `'180d'`). * @param characteristic - Characteristic UUID or name (e.g. `'battery_level'`). * @param options - Read options including timeout. * @returns Raw characteristic value as a `DataView`. * * @throws {BeacioError} `DEVICE_DISCONNECTED` -- not connected * @throws {BeacioError} `SERVICE_NOT_FOUND` -- service UUID not found on device * @throws {BeacioError} `CHARACTERISTIC_NOT_FOUND` -- characteristic UUID not found * @throws {BeacioError} `CHARACTERISTIC_NOT_READABLE` -- characteristic does not support read * @throws {BeacioError} `TIMEOUT` -- read did not complete within `timeoutMs` * * @example * ```typescript * // Raw DataView read * const data = await device.read('battery_service', 'battery_level') * const level = data.getUint8(0) // 0-100 * * // Typed read with parse function * const hr = await device.read('heart_rate', 'heart_rate_measurement', parseHeartRate) * console.log(hr.bpm) * * // With timeout * const data = await device.read('my_service', 'my_char', { timeoutMs: 5000 }) * ``` * * @see {@link ReadOptions} * @see {@link subscribe} for continuous notifications */ read(service: string, characteristic: string, options?: ReadOptions): Promise; /** * Read a characteristic value and apply a parse function to get a typed result. * * @param service - Service UUID or name. * @param characteristic - Characteristic UUID or name. * @param parse - Function to transform the raw `DataView` into a typed value. * @param options - Read options including timeout. * @returns Parsed value of type `T`. */ read(service: string, characteristic: string, parse: (dv: DataView) => T | Promise, options?: ReadOptions): Promise; /** * Write a single packet to a characteristic. Default mode is write-with-response * (acknowledged). For payloads larger than one ATT packet, use {@link writeLarge}, * {@link writeFragmented}, or {@link writeAuto}. * * @param service - Service UUID or name. * @param characteristic - Characteristic UUID or name. * @param value - Data to write (ArrayBuffer, TypedArray, or DataView). * @param options - Write mode and timeout options. * * @throws {BeacioError} `DEVICE_DISCONNECTED` -- not connected * @throws {BeacioError} `CHARACTERISTIC_NOT_WRITABLE` -- characteristic does not support write * @throws {BeacioError} `WRITE_INCOMPLETE` -- disconnected before write completed * @throws {BeacioError} `TIMEOUT` -- write did not complete within `timeoutMs` * * @example * ```typescript * await device.write('my_service', 'my_char', new Uint8Array([0x01])) * await device.write('my_service', 'my_char', data, { mode: 'without-response' }) * ``` * * @see {@link writeLarge} for chunked writes without retry * @see {@link writeFragmented} for chunked writes with per-chunk retry * @see {@link writeAuto} for automatic fragmentation decisions * @see {@link writeWithoutResponse} for convenience fire-and-forget writes */ write(service: string, characteristic: string, value: BufferSource, options?: WriteOptions): Promise; /** * Write a large payload by chunking it across multiple write operations, * with optional per-chunk retry on failure. * * **Chunk size determination order:** * 1. Explicit `chunkSize` if provided * 2. `mtu - 3` if `mtu` option is provided (BLE ATT header = 3 bytes) * 3. Platform-reported write limits via `getWriteLimits()` * 4. 20-byte conservative fallback * * Unlike {@link writeLarge}, failed chunks are retried up to `maxRetries` times * before the entire operation fails with `WRITE_INCOMPLETE`. * * @param service - Service UUID or name. * @param characteristic - Characteristic UUID or name. * @param value - Full payload to write (will be chunked automatically). * @param options - Fragmentation, retry, and write mode options. * @returns Result with byte counts, chunk info, and total retry count. * * @throws {BeacioError} `WRITE_INCOMPLETE` -- partial write after chunk retries exhausted * @throws {BeacioError} `DEVICE_DISCONNECTED` -- disconnected during write * * @see {@link writeLarge} for chunked writes without retry * @see {@link writeAuto} for automatic fragmentation decisions * @see {@link WriteFragmentedOptions} */ writeFragmented(service: string, characteristic: string, value: BufferSource, options?: WriteFragmentedOptions): Promise; /** * Write a large payload by chunking it across multiple write operations. * No per-chunk retry -- any chunk failure aborts the entire write. * * **Chunk size determination order:** * 1. Explicit `chunkSize` if provided * 2. Platform-reported write limits via `getWriteLimits()` * 3. `MTU - 3` (ATT header overhead) * 4. 20-byte conservative fallback * * @param service - Service UUID or name. * @param characteristic - Characteristic UUID or name. * @param value - Full payload to write (will be chunked automatically). * @param options - Chunk size and write mode options. * @returns Result with byte counts and chunk info. * * @throws {BeacioError} `WRITE_INCOMPLETE` -- not all bytes were transferred * @throws {BeacioError} `DEVICE_DISCONNECTED` -- disconnected during write * * @see {@link writeFragmented} for chunked writes with per-chunk retry * @see {@link writeAuto} for automatic fragmentation decisions * @see {@link WriteLargeOptions} */ writeLarge(service: string, characteristic: string, value: BufferSource, options?: WriteLargeOptions): Promise; /** * Write a value to a characteristic without waiting for acknowledgment (fire-and-forget). * Convenience wrapper for `write(service, char, value, { mode: 'without-response' })`. * * @param service - Service UUID or name. * @param characteristic - Characteristic UUID or name. * @param value - Data to write. * @param options - Write options (mode is forced to `'without-response'`). * * @see {@link write} */ writeWithoutResponse(service: string, characteristic: string, value: BufferSource, options?: Omit): Promise; /** * Return the platform-reported writable payload limits and negotiated ATT MTU. * * On standard browser Web Bluetooth stacks (Chrome, Edge), these values are typically * unavailable and return `null`. The Safari Beacio extension reports actual negotiated values. * * @returns Object with `withResponse`, `withoutResponse`, and `mtu` fields (each `null` when unavailable). * * @throws {BeacioError} `DEVICE_DISCONNECTED` -- not connected * * @see {@link WriteLimits} * @see {@link getMtu} for just the MTU value * @see {@link getEffectiveMtu} for a guaranteed non-null MTU (falls back to 23) */ getWriteLimits(): Promise; /** * Return the negotiated ATT MTU when the underlying platform exposes it. * Returns `null` when unavailable. Max write payload = MTU - 3 (ATT header). * * @see {@link getEffectiveMtu} for a guaranteed non-null value (falls back to 23) * @see {@link getWriteLimits} for full write limit details */ getMtu(): Promise; /** * Smart write that automatically decides between single-packet and fragmented writes. * * If the payload fits within the platform write limit, send it as a single `write()`. * Otherwise, delegate to `writeFragmented()` with per-chunk retry support. * * **Decision logic:** * 1. Determine max single-write size (same as chunk size derivation) * 2. If `payload.byteLength <= limit`, use single `write()` * 3. Otherwise, use `writeFragmented()` and set `result.fragmented = true` * * @param service - Service UUID or name. * @param characteristic - Characteristic UUID or name. * @param value - Data to write (any size). * @param options - All fragmentation, retry, and write mode options. * @returns Result indicating whether fragmentation was used. * * @throws {BeacioError} `WRITE_INCOMPLETE` -- partial write after chunk retries exhausted * @throws {BeacioError} `DEVICE_DISCONNECTED` -- disconnected during write * * @see {@link write} for single-packet writes * @see {@link writeFragmented} for explicit fragmentation * @see {@link WriteAutoResult} */ writeAuto(service: string, characteristic: string, value: BufferSource, options?: WriteAutoOptions): Promise; /** * Subscribe to characteristic notifications. Return an unsubscribe function. * * **Notification deduplication:** Multiple callbacks on the same characteristic share * a single native BLE listener. The first `subscribe()` call starts notifications; * subsequent calls just add callbacks. The native listener stops only when all * callbacks are unsubscribed. * * **Auto-recovery:** By default (`autoRecover: true`), subscriptions are automatically * re-established after reconnection. The callback continues receiving values * transparently after the device reconnects. * * **Error handling:** Setup errors are delivered via `options.onError` (not thrown), * since `subscribe()` returns synchronously. Use {@link subscribeAsync} if you need * to `await` setup completion and catch errors directly. * * @param service - Service UUID or name. * @param characteristic - Characteristic UUID or name. * @param callback - Function called with each notification `DataView` value. * @param options - Auto-recovery and error handling options. * @returns Unsubscribe function -- call it to stop receiving notifications. * * @example * ```typescript * const unsub = device.subscribe('heart_rate', 'heart_rate_measurement', (data) => { * console.log(`Heart rate: ${data.getUint8(1)} BPM`) * }) * * // Later: stop notifications * unsub() * ``` * * @see {@link subscribeAsync} for awaitable setup with error throwing * @see {@link notifications} for async iterator interface * @see {@link SubscribeOptions} */ subscribe(service: string, characteristic: string, callback: NotificationCallback, options?: SubscribeOptions): () => void; /** * Subscribe to characteristic notifications with awaitable setup. * * Unlike {@link subscribe}, this method awaits the native notification setup and * throws on failure instead of routing errors to `onError`. The returned promise * resolves with an unsubscribe function once notifications are active. * * Shares the same underlying notification state as `subscribe()` -- multiple * callbacks on the same characteristic share one native listener. * * @param service - Service UUID or name. * @param characteristic - Characteristic UUID or name. * @param callback - Function called with each notification value. * @param options - Auto-recovery options. `onError` is NOT called (errors are thrown). * @returns Unsubscribe function (resolves only after setup completes). * * @throws {BeacioError} `CHARACTERISTIC_NOT_NOTIFIABLE` -- characteristic does not support notify * @throws {BeacioError} `DEVICE_DISCONNECTED` -- not connected * * @see {@link subscribe} for fire-and-forget subscription * @see {@link notifications} for async iterator interface */ subscribeAsync(service: string, characteristic: string, callback: NotificationCallback, options?: SubscribeOptions): Promise<() => void>; /** * Observe NATIVE notification-queue overflows for a characteristic. Returns an * unsubscribe function synchronously (like {@link subscribe}); the underlying * characteristic lookup + listener attach happen asynchronously. * * Safari's bounded Swift `EventQueue` evicts notifications under sustained * high-frequency load rather than letting the page silently miss samples; the * polyfill re-surfaces each eviction as a `beacio:overflow` `CustomEvent` * dispatched on the underlying `BluetoothRemoteGATTCharacteristic`. This method * resolves that characteristic (via the same cached lookup `read`/`subscribe` * use) and forwards each `beacio:overflow` event to `listener`. The * `CustomEvent.detail` carries the eviction metadata (`evictedCount`, * `queueCapacity`, `seq`, `timestamp`); `@beacio/core/profiles` decodes it into a * typed {@link NativeOverflowEvent} for you. The recommended response is a * fresh `read()` to resynchronise any UI that was tracking the last notified * value. * * This is distinct from `notifications().onOverflow` / * `on('queue-overflow')`, which report the *JS* async-iterator queue (see * {@link QueueOverflowEvent}). On browser stacks that never emit * `beacio:overflow` (Chrome/Edge), the listener simply never fires. * * Setup errors (e.g. not connected) are routed to any registered * {@link addErrorListener} — never thrown — since this returns synchronously. * * @param service - Service UUID or name. * @param characteristic - Characteristic UUID or name. * @param listener - Called with the raw `beacio:overflow` event on each overflow. * @returns Unsubscribe function -- detaches the underlying event listener * (also cancels a still-pending attach). * * @see {@link NativeOverflowEvent} * @see {@link decodeNativeOverflow} * @see {@link subscribe} */ onCharacteristicOverflow(service: string, characteristic: string, listener: (event: Event) => void): () => void; /** * Async iterator for characteristic notifications. Use with `for await...of`. * * **Overflow strategies:** When the consumer cannot keep up with incoming notifications, * the internal queue fills up. Choose a strategy via `options.overflowStrategy`: * - `'error'` (default) -- Throw `GATT_OPERATION_FAILED` and terminate the iterator * - `'drop-oldest'` -- Discard the oldest buffered value (lossy FIFO) * - `'drop-newest'` -- Discard the incoming value (backpressure) * * **Reconnect behavior:** On unexpected disconnect, the iterator pauses (does not * terminate) and waits for auto-reconnect to complete. Subscriptions are auto-recovered * and the iterator resumes yielding values transparently. On intentional disconnect, * the iterator terminates. * * @param service - Service UUID or name. * @param characteristic - Characteristic UUID or name. * @param options - Queue size, overflow strategy, and overflow callback. * @returns Async iterable of `DataView` notification values. * * @example * ```typescript * for await (const data of device.notifications('heart_rate', 'heart_rate_measurement')) { * console.log('BPM:', data.getUint8(1)) * } * * // With overflow handling * const stream = device.notifications('sensor', 'data', { * maxQueueSize: 64, * overflowStrategy: 'drop-oldest', * onOverflow: (e) => console.warn(`Dropped ${e.droppedCount} values`), * }) * ``` * * @see {@link subscribe} for callback-based notifications * @see {@link NotificationOptions} * @see {@link NotificationOverflowStrategy} */ notifications(service: string, characteristic: string, options?: NotificationOptions): AsyncIterable; /** * Start watching for BLE advertisements from this device. * Not supported on all platforms. * * @throws {BeacioError} `GATT_OPERATION_FAILED` -- platform does not support watchAdvertisements */ watchAdvertisements(): Promise; /** * Stop watching for BLE advertisements from this device. * * @throws {BeacioError} `GATT_OPERATION_FAILED` -- platform does not support unwatchAdvertisements */ unwatchAdvertisements(): Promise; /** * Request the browser to forget this device and revoke its permissions. * After calling, the device must be re-selected via `requestDevice()`. * * @throws {BeacioError} `GATT_OPERATION_FAILED` -- platform does not support forget */ forget(): Promise; /** * Discover all primary GATT services on the connected device. * Results are cached until disconnect. * * @returns Array of `BluetoothRemoteGATTService` objects. * @throws {BeacioError} `DEVICE_DISCONNECTED` -- not connected */ getPrimaryServices(): Promise; /** * Return the effective ATT MTU, guaranteed non-null. * Falls back through: reported MTU > withResponse + 3 > withoutResponse + 3 > 23 (BLE default). * * @returns MTU value in bytes (minimum 23). * * @see {@link getMtu} for nullable version * @see {@link getWriteLimits} for full limit details */ getEffectiveMtu(): Promise; /** * Return the reason for the most recent disconnection, or `null` if the device * has never disconnected during this session. * * @see {@link DisconnectReason} */ getLastDisconnectReason(): DisconnectReason | null; /** * Return a snapshot of all active notification subscriptions on this device. * Includes both currently active native subscriptions and those registered * for auto-recovery after reconnection. * * @returns Array of {@link ActiveSubscription} snapshots. */ getActiveSubscriptions(): ActiveSubscription[]; /** * Register a listener for device lifecycle events. Return an unsubscribe function. * * **Events:** * - `'disconnected'` -- Fired on any disconnect (intentional or unexpected), with reason. * - `'reconnected'` -- Fired after a successful connection while reconnect recovery listeners are registered. * - `'queue-overflow'` -- Fired when a notification queue exceeds its max size. * - `'subscription-lost'` -- Fired when a subscription cannot be recovered after reconnect. * * @param event - Event name. * @param fn - Listener function. * @returns Unsubscribe function. */ on(event: 'reconnected', fn: Listener): () => void; on(event: 'disconnected', fn: DisconnectListener): () => void; on(event: 'queue-overflow', fn: QueueOverflowListener): () => void; on(event: 'subscription-lost', fn: SubscriptionLostListener): () => void; /** Remove a previously registered event listener. No-op if the listener is not found. */ off(event: 'reconnected', fn: Listener): void; off(event: 'disconnected', fn: DisconnectListener): void; off(event: 'queue-overflow', fn: QueueOverflowListener): void; off(event: 'subscription-lost', fn: SubscriptionLostListener): void; /** * Register a global error listener for this device. Receives errors from internal * operations (notification recovery, listener failures, auto-reconnect exhaustion). * * @param listener - Error callback with error and context. * @returns Unsubscribe function. */ addErrorListener(listener: ErrorListener): () => void; /** Remove a previously registered error listener. */ removeErrorListener(listener: ErrorListener): void; private handleDisconnect; private startAutoReconnect; private withOptionalTimeout; private validateTimeoutMs; /** * Invoke each listener with `event`, routing any listener exception to * {@link emitError} tagged with `operation`. Failures are isolated so one * throwing listener does not prevent the rest from running. */ private fanout; private emitQueueOverflow; private emitSubscriptionLost; private emitError; private getCharacteristic; private getService; private charKey; } type AnyCharacteristicDefinition = CharacteristicDefinition; declare function parseRawBytes(value: BufferSource): DataView; type UUIDLike = string; type Capability = 'read' | 'write' | 'writeWithoutResponse' | 'notify'; type CharacteristicReadConfig = { capabilities: readonly ['read'] | readonly ['read', ...Capability[]]; parse: (dv: DataView) => T; }; type CharacteristicWriteConfig = { capabilities: readonly ['write'] | readonly ['writeWithoutResponse'] | readonly ['write', ...Capability[]] | readonly ['writeWithoutResponse', ...Capability[]]; serialize: (value: W) => BufferSource; }; type CharacteristicReadWriteConfig = { capabilities: readonly ['read', 'write'] | readonly ['read', 'writeWithoutResponse'] | readonly ['write', 'read'] | readonly ['writeWithoutResponse', 'read'] | readonly ['read', 'write', ...Capability[]] | readonly ['read', 'writeWithoutResponse', ...Capability[]] | readonly ['write', 'read', ...Capability[]] | readonly ['writeWithoutResponse', 'read', ...Capability[]]; parse: (dv: DataView) => T; serialize: (value: W) => BufferSource; }; type CharacteristicDefinition = { uuid: UUIDLike; } & (CharacteristicReadConfig | CharacteristicWriteConfig | CharacteristicReadWriteConfig); interface ProfileConfig> { name: string; service: UUIDLike; characteristics: C; } type CapabilityOf = T['capabilities'][number]; type ReadableKeys> = { [K in keyof C]: 'read' extends CapabilityOf ? K : never; }[keyof C] & string; type WritableKeys> = { [K in keyof C]: 'write' extends CapabilityOf ? K : 'writeWithoutResponse' extends CapabilityOf ? K : never; }[keyof C] & string; type NotifiableKeys> = { [K in keyof C]: 'notify' extends CapabilityOf ? K : never; }[keyof C] & string; type ReadValue = T extends { parse: (dv: DataView) => infer TResult; } ? TResult : never; type WriteValue = T extends { serialize: (value: infer TValue) => BufferSource; } ? TValue : never; declare abstract class BaseProfile { protected device: BeacioDevice; protected abstract readonly service: string; private cleanups; constructor(device: BeacioDevice); connect(): Promise; stop(): void; dispose(): void; protected read(characteristic: string): Promise; protected write(characteristic: string, value: BufferSource): Promise; protected writeWithoutResponse(characteristic: string, value: BufferSource): Promise; /** * Send a payload of any size to `characteristic`, fragmenting it into * MTU-sized chunks. This is a thin passthrough to {@link BeacioDevice.writeFragmented}, * which owns the (already-clamped) chunk-size derivation via the branded * `ChunkSize` smart-constructors in the core write-chunker — so the stride is * guaranteed `>= 1` and a zero-stride infinite loop is unrepresentable. * * Profiles MUST use this instead of hand-rolling a `for (offset += step)` / * `subarray()` / `writeWithoutResponse()` chunk loop (enforced by the * `no-restricted-syntax` guard scoped to `packages/profiles/src`). Defaults to * `mode: 'without-response'` — the serial-pipe convention (Nordic UART, HM-10). * * @param characteristic - Target characteristic UUID or alias on this profile's service. * @param value - Bytes to send. Accepts any {@link BufferSource}. * @param options - Fragmentation/retry overrides; `mode` defaults to `'without-response'`. * @returns The {@link WriteFragmentedResult} (bytes written, chunk size/count, retries). */ protected sendChunked(characteristic: string, value: BufferSource, options?: WriteFragmentedOptions): Promise; protected writeValue(characteristic: string, value: BufferSource, options?: WriteOptions): Promise; protected getWriteLimits(): Promise; protected getMtu(): Promise; protected subscribe(characteristic: string, callback: NotificationCallback): () => void; /** * Observe NATIVE notification-queue overflows for `characteristic` on this * profile's service. The bounded Swift `EventQueue` evicts notifications under * sustained high-frequency load and the polyfill surfaces each eviction as a * `beacio:overflow` `CustomEvent` on the characteristic; this decodes that * event's `detail` into a typed {@link NativeOverflowEvent} and forwards it to * `callback`. * * Lifecycle parity with {@link subscribe}: the returned unsubscribe is also * registered into the profile's cleanup set, so {@link stop}/{@link dispose} * detach the listener too. A staleness `callback` should typically re-read the * affected characteristic to resynchronise any UI tracking the last notified * value rather than trusting that (now-stale) value. * * @param characteristic - Characteristic UUID or alias on this profile's service. * @param callback - Called with the decoded eviction metadata on each overflow. * @returns Unsubscribe function. */ protected onOverflow(characteristic: string, callback: (event: NativeOverflowEvent) => void): () => void; } type DefinedProfileInstance> = BaseProfile & { readChar>(name: K): Promise>; subscribeChar, NotifiableKeys>>(name: K, cb: (value: ReadValue) => void): () => void; writeChar>(name: K, value: WriteValue, options?: WriteOptions): Promise; getCharacteristicCapabilities(name: K): ReadonlyArray; getCharacteristicUUID(name: K): string; getServiceUUID(): string; getWriteLimits(): Promise; getMtu(): Promise; }; interface DefinedProfile> { new (device: BeacioDevice): DefinedProfileInstance; readonly profileName: string; readonly serviceUUID: string; readonly characteristics: { [K in keyof C]: Omit & { uuid: string; }; }; } declare function defineProfile>(config: ProfileConfig): DefinedProfile; export { type WriteFragmentedResult as $, type ActiveSubscription as A, BaseProfile as B, type CharacteristicNotificationOptions as C, type ConditionOperator as D, type ConnectOptions as E, DEFAULT_BEACIO_OPTIONS as F, DEFAULT_RETRY_OPTIONS as G, type DeviceErrorContext as H, type DisconnectReason as I, type NotificationCallback as J, type NotificationCondition as K, type NotificationOptions as L, type NotificationOverflowStrategy as M, type NativeOverflowEvent as N, type NotificationPermissionState as O, type Platform as P, type NotificationTemplate as Q, type RequestDeviceOptions as R, type QueueOverflowEvent as S, type ReadOptions as T, type ReplyActionConfig as U, type RetryOptions as V, type SubscribeOptions as W, type SubscriptionLostEvent as X, type WriteAutoOptions as Y, type WriteAutoResult as Z, type WriteFragmentedOptions as _, type BeacioOptions as a, type WriteLargeOptions as a0, type WriteLargeResult as a1, type WriteLimits as a2, type WriteMode as a3, type WriteOptions as a4, defineProfile as a5, parseRawBytes as a6, withRetry as a7, type BeacioBackgroundSync as b, type BeacioPeripheral as c, BeacioDevice as d, type AutoReconnectOptions as e, type BackgroundConnectionOptions as f, type BackgroundRegistration as g, type BackgroundRegistrationType as h, BeacioError as i, type BeacioPeripheralAdvertisingOptions as j, type BeacioPeripheralCharacteristicDefinition as k, type BeacioPeripheralCharacteristicProperty as l, type BeacioPeripheralCharacteristicRecord as m, type BeacioPeripheralConnectionStateChange as n, type BeacioPeripheralEventMap as o, type BeacioPeripheralNotificationReady as p, type BeacioPeripheralSendOptions as q, type BeacioPeripheralSendResult as r, type BeacioPeripheralServiceDefinition as s, type BeacioPeripheralServiceRecord as t, type BeacioPeripheralSubscriptionChange as u, type BeacioPeripheralWriteRequest as v, type BeaconScanFilter as w, type BeaconScanningOptions as x, type BluetoothLEScanFilter as y, type ConditionDecoder as z };