// src/web/web-bluetooth-boundary.ts import type { Uuid } from '../backend-contract/primitives' export interface WebBluetoothRequestFilter { readonly services: readonly Uuid[] readonly manufacturerData: readonly WebBluetoothRequestManufacturerDataFilter[] readonly namePrefix: string | null } /** A copied Web Bluetooth manufacturer predicate ready for a browser request. */ export interface WebBluetoothRequestManufacturerDataFilter { readonly companyIdentifier: number readonly dataPrefix: Readonly | null } export interface WebBluetoothRequestDeviceOptions { readonly filters: readonly WebBluetoothRequestFilter[] readonly acceptAllDevices: boolean readonly optionalServices: readonly Uuid[] } export interface WebBluetoothDeviceSelection { readonly device: WebBluetoothDeviceBoundary readonly grantedServices: readonly Uuid[] } export type WebBluetoothPageLifecycleReason = 'page-hidden' | 'page-unloaded' export type WebBluetoothTimerHandle = object export type WebBluetoothNotificationListener = (value: Uint8Array) => void export type WebBluetoothDisconnectListener = () => void export type WebBluetoothAvailabilityListener = () => void export interface WebBluetoothDescriptorBoundary { readonly uuid: string readValue(): Promise writeValue(value: Uint8Array): Promise } export interface WebBluetoothCharacteristicProperties { readonly read: boolean readonly write: boolean readonly writeWithoutResponse: boolean readonly notify: boolean readonly indicate: boolean } export interface WebBluetoothCharacteristicBoundary { readonly uuid: string readonly properties: WebBluetoothCharacteristicProperties getDescriptors(): Promise readValue(): Promise writeValueWithResponse(value: Uint8Array): Promise writeValueWithoutResponse(value: Uint8Array): Promise startNotifications(): Promise stopNotifications(): Promise addNotificationListener(listener: WebBluetoothNotificationListener): void removeNotificationListener(listener: WebBluetoothNotificationListener): void } export interface WebBluetoothServiceBoundary { readonly uuid: string getCharacteristics(): Promise } export interface WebBluetoothGattServerBoundary { readonly connected: boolean connect(): Promise disconnect(): void getPrimaryServices(): Promise } export interface WebBluetoothDeviceBoundary { readonly id: string readonly gatt: WebBluetoothGattServerBoundary addDisconnectListener(listener: WebBluetoothDisconnectListener): void removeDisconnectListener(listener: WebBluetoothDisconnectListener): void } /** * Browser-owned operations injected into the Web backend. Production adapters * wrap navigator.bluetooth and page lifecycle APIs; tests provide isolated * deterministic implementations without mutating globals. */ export interface WebBluetoothBoundary { readonly implementationVersion: string readonly browserEngine: string isSecureContext(): boolean hasTransientUserActivation(): boolean bluetoothAvailable(): Promise requestDevice(options: WebBluetoothRequestDeviceOptions): Promise readonly getAuthorizedDevices?: () => Promise now(): number setTimer(callback: () => void, delayMilliseconds: number): WebBluetoothTimerHandle clearTimer(handle: WebBluetoothTimerHandle): void addPageLifecycleListener(listener: (reason: WebBluetoothPageLifecycleReason) => void): () => void /** * Optional `availabilitychanged` subscription. Present only when the * browser exposes the event (`onavailabilitychanged`). Missing support * makes the backend use a shared bounded `bluetoothAvailable()` poll for * adapter watches. Subscribed watches still start that poll if no event * arrives within the documented bound. The listener is a change signal; * callers re-sample availability and never treat it as physical adapter * power. */ readonly addAvailabilityChangeListener?: (listener: WebBluetoothAvailabilityListener) => () => void }