// src/backends/corebluetooth/corebluetooth-provider.ts // src/backends/corebluetooth/corebluetooth-provider.ts import { contractError } from '../../backend-contract/errors' import type { AdapterDescriptor, BackendProvider, HostNeutralBackendIdentity } from '../../backend-contract/identity' import { monotonicTimestamp, opaqueId, version, versionRange, type BackendCompatibilityOffer } from '../../backend-contract/primitives' import { CoreBluetoothBackend } from './corebluetooth-backend' import type { CoreBluetoothBoundary } from './corebluetooth-boundary' export { COREBLUETOOTH_BACKEND_ID, COREBLUETOOTH_IMPLEMENTATION_VERSION, COREBLUETOOTH_PLATFORM_ID } from './corebluetooth-identity' export const coreBluetoothCompatibility: BackendCompatibilityOffer = Object.freeze({ backendContract: versionRange(version('backend-contract', 1), version('backend-contract', 1)), capabilitySchema: versionRange(version('capability-schema', 1), version('capability-schema', 1)), eventSchema: versionRange(version('event-schema', 1), version('event-schema', 1)), traceFormat: versionRange(version('trace-format', 1), version('trace-format', 1)) }) export interface CoreBluetoothBackendProviderOptions { readonly boundaryFactory: () => CoreBluetoothBoundary readonly prepareBoundary?: (boundary: CoreBluetoothBoundary) => Promise readonly now: () => number readonly hostKind: 'node' | 'desktop-native' } /** Creates a provider for the one explicitly selected CoreBluetooth central adapter. */ export function createCoreBluetoothBackendProvider( options: CoreBluetoothBackendProviderOptions ): BackendProvider> { return { descriptor: Object.freeze({ providerId: 'unified-ble:corebluetooth-provider', hostKind: options.hostKind, loadability: 'loadable', compatibility: coreBluetoothCompatibility }), listAdapters: async () => { const boundary = options.boundaryFactory() try { await options.prepareBoundary?.(boundary) return Object.freeze([adapterDescriptor(boundary, options.now)]) } finally { await boundary.destroy() } }, create: async selection => { const selected = String(selection.selectedAdapterId) if (selected !== 'corebluetooth-default-adapter') { throw contractError('adapter.unavailable', 'adapter', 'direct-gatt.provider.select-adapter') } const boundary = options.boundaryFactory() try { await options.prepareBoundary?.(boundary) return new CoreBluetoothBackend(boundary, options.now, options.hostKind) } catch (error) { await boundary.destroy() throw error } } } } function adapterDescriptor(boundary: CoreBluetoothBoundary, now: () => number): AdapterDescriptor { const state = boundary.adapterSnapshot() return Object.freeze({ adapterId: opaqueId('corebluetooth-default-adapter', 'adapter', 'corebluetooth'), displayName: 'CoreBluetooth default adapter', state: Object.freeze({ availability: state.availability, authorization: state.authorization, power: state.power, backendGeneration: opaqueId('1', 'backend-generation', 'corebluetooth'), updatedAt: monotonicTimestamp(now()), safeReason: state.safeReason }), adapterGeneration: opaqueId('1', 'adapter-generation', 'corebluetooth'), limitations: adapterLimitations(boundary) }) } function adapterLimitations(boundary: CoreBluetoothBoundary): readonly string[] { const limitations = ['CoreBluetooth exposes one selected default central adapter through this host boundary'] if (boundary.descriptorOperationsAvailable !== true) { limitations.push( 'Descriptor operations are unavailable because this boundary does not publish descriptor callbacks' ) } return Object.freeze(limitations) }