// src/native-protocol/rn-apple-boundary.ts import { contractError } from '../backend-contract/errors' import { isAuthorizationBlocking } from '../backend-contract/identity' import type { ConnectionControlCapabilities } from '../backend-contract/connection-controls' import type { ConnectionIntent } from '../backend-contract/backend' import type { CoreBluetoothAdapterSnapshot, CoreBluetoothAdvertisement, CoreBluetoothScanPlatformOptions } from '../backends/corebluetooth/corebluetooth-boundary' import { ReactNativeAndroidProtocolBoundary } from './rn-android-boundary' /** * Apple shares the versioned JSI codec, but CoreBluetooth has no caller-directed ATT MTU request. * The explicit capability declaration prevents the core from submitting that impossible command. */ export class ReactNativeAppleProtocolBoundary extends ReactNativeAndroidProtocolBoundary { override readonly connectionIntentCapabilities: Readonly<{ whenAvailable: 'unsupported' }> = Object.freeze({ whenAvailable: 'unsupported' }) override get connectionControlCapabilities(): ConnectionControlCapabilities { return Object.freeze({ rssi: 'available', requestMtu: 'unavailable', effectiveMtu: 'unavailable', phy: 'unavailable' }) } override async effectiveMtu(_nativePeerId: string): Promise { throw contractError('capability.unsupported', 'connection', 'rn-apple-boundary.effective-mtu') } override adapterSnapshot(): CoreBluetoothAdapterSnapshot { const snapshot = super.adapterSnapshot() if (snapshot.safeReason !== 'The Android radio has not emitted its authoritative adapter state yet.') { return snapshot } return Object.freeze({ ...snapshot, safeReason: 'CoreBluetooth has not emitted its authoritative adapter state yet.' }) } override async startScan( onAdvertisement: (advertisement: CoreBluetoothAdvertisement) => void, serviceUuids: readonly string[], deviceAddresses: readonly string[] = [], platform?: CoreBluetoothScanPlatformOptions ): Promise { if (deviceAddresses.length > 0) { throw contractError('capability.unsupported', 'scan', 'rn-apple-boundary.scan.device-addresses') } this.assertAdapterReady('scan.start') if (platform !== undefined) { throw contractError('capability.unsupported', 'scan', 'rn-apple-boundary.scan.platform-options') } return super.startScan(onAdvertisement, serviceUuids) } override async connect(nativePeerId: string, intent: ConnectionIntent = 'direct'): Promise { if (intent !== 'direct') { throw contractError('capability.unsupported', 'connection', 'rn-apple-boundary.connect.when-available') } this.assertAdapterReady('connection.connect') return super.connect(nativePeerId, intent) } private assertAdapterReady(operation: string): void { const snapshot = this.adapterSnapshot() if (snapshot.availability !== 'available' || snapshot.power === 'unsupported') { throw contractError('adapter.unavailable', 'adapter', `rn-apple-boundary.${operation}`) } if (snapshot.authorization === 'denied') { throw contractError('permission.denied', 'adapter', `rn-apple-boundary.${operation}`) } if (snapshot.authorization === 'restricted') { throw contractError('permission.restricted', 'adapter', `rn-apple-boundary.${operation}`) } if (isAuthorizationBlocking(snapshot.authorization)) { throw contractError('permission.not-determined', 'adapter', `rn-apple-boundary.${operation}`) } if (snapshot.power === 'off') { throw contractError('adapter.powered-off', 'adapter', `rn-apple-boundary.${operation}`) } if (snapshot.power === 'resetting') { throw contractError('adapter.resetting', 'adapter', `rn-apple-boundary.${operation}`) } if (snapshot.power !== 'on') { throw contractError('adapter.unavailable', 'adapter', `rn-apple-boundary.${operation}`) } } }