@eventmsg/transport-webble
Version:
EventMsgV3 Web Bluetooth transport for browser-based communication with ESP32 devices
1 lines • 29.7 kB
Source Map (JSON)
{"version":3,"file":"index.cjs","names":["NORDIC_UART_SERVICE: WebBLEServiceConfig","WEB_BLUETOOTH_ERROR_MAP: Record<string, WebBLEErrorCode>","WEB_BLUETOOTH_ERROR_MESSAGES: Record<\n WebBLEErrorCode,\n {\n message: string;\n solution: string;\n }\n>","BaseTransport","ValidationError","SendError","chunks: Uint8Array[]","ConnectionError","DisconnectionError"],"sources":["../src/types/config.ts","../src/types/errors.ts","../src/webble-transport.ts"],"sourcesContent":["import type { TransportConfig } from \"@eventmsg/core\";\n\n/**\n * Web Bluetooth service configuration\n */\nexport interface WebBLEServiceConfig {\n /**\n * Service UUID (e.g., Nordic UART Service)\n */\n uuid: string;\n\n /**\n * TX characteristic UUID (device → browser, notification)\n */\n txCharacteristic: string;\n\n /**\n * RX characteristic UUID (browser → device, write)\n */\n rxCharacteristic: string;\n}\n\n/**\n * Connection behavior configuration\n */\nexport interface WebBLEConnectionConfig {\n /**\n * Connection timeout in milliseconds\n * @default 10_000\n */\n timeout?: number;\n\n /**\n * Preferred MTU size in bytes\n * @default auto-negotiate\n */\n mtu?: number;\n\n /**\n * Whether to cache device for reconnection\n * @default true\n */\n persistDevice?: boolean;\n\n /**\n * Number of automatic reconnection attempts\n * @default 3\n */\n reconnectAttempts?: number;\n\n /**\n * Delay between reconnection attempts in milliseconds\n * @default 1_000\n */\n reconnectDelay?: number;\n}\n\n/**\n * Configuration for WebBLE transport extending base TransportConfig\n */\nexport interface WebBLETransportConfig extends TransportConfig {\n /**\n * BLE service configuration\n */\n service: WebBLEServiceConfig;\n\n /**\n * Optional device filtering for requestDevice()\n */\n filters?: BluetoothLEScanFilter[];\n\n /**\n * Connection behavior configuration\n */\n connection?: WebBLEConnectionConfig;\n}\n\n/**\n * Nordic UART Service preset configuration\n * Standard service used by most ESP32 BLE examples\n */\nexport const NORDIC_UART_SERVICE: WebBLEServiceConfig = {\n uuid: \"6e400001-b5a3-f393-e0a9-e50e24dcca9e\",\n txCharacteristic: \"6e400003-b5a3-f393-e0a9-e50e24dcca9e\", // notify\n rxCharacteristic: \"6e400002-b5a3-f393-e0a9-e50e24dcca9e\", // write\n};\n\n/**\n * Create a WebBLE transport config with Nordic UART defaults\n */\nexport function createNordicUARTConfig(\n localAddress: number,\n groupAddress: number,\n overrides?: Partial<WebBLETransportConfig>\n): WebBLETransportConfig {\n return {\n localAddress,\n groupAddress,\n service: NORDIC_UART_SERVICE,\n connection: {\n timeout: 10_000,\n persistDevice: true,\n reconnectAttempts: 3,\n reconnectDelay: 1000,\n mtu: 480,\n },\n ...overrides,\n };\n}\n","/**\n * Web Bluetooth specific error codes\n */\nexport const WebBLEErrorCode = {\n /** Browser doesn't support Web Bluetooth */\n NOT_SUPPORTED: \"NOT_SUPPORTED\",\n\n /** Not in secure context (HTTPS required) */\n INSECURE_CONTEXT: \"INSECURE_CONTEXT\",\n\n /** User cancelled device selection */\n USER_CANCELLED: \"USER_CANCELLED\",\n\n /** Device not found or not available */\n DEVICE_NOT_FOUND: \"DEVICE_NOT_FOUND\",\n\n /** Failed to connect to GATT server */\n GATT_CONNECTION_FAILED: \"GATT_CONNECTION_FAILED\",\n\n /** Service not found on device */\n SERVICE_NOT_FOUND: \"SERVICE_NOT_FOUND\",\n\n /** Characteristic not found in service */\n CHARACTERISTIC_NOT_FOUND: \"CHARACTERISTIC_NOT_FOUND\",\n\n /** Failed to start notifications */\n NOTIFICATION_FAILED: \"NOTIFICATION_FAILED\",\n\n /** BLE write operation failed */\n WRITE_FAILED: \"WRITE_FAILED\",\n\n /** Device disconnected unexpectedly */\n UNEXPECTED_DISCONNECT: \"UNEXPECTED_DISCONNECT\",\n\n /** Permission denied */\n PERMISSION_DENIED: \"PERMISSION_DENIED\",\n\n /** Operation timed out */\n TIMEOUT: \"TIMEOUT\",\n} as const;\n\nexport type WebBLEErrorCode =\n (typeof WebBLEErrorCode)[keyof typeof WebBLEErrorCode];\n\n/**\n * Mapping of Web Bluetooth DOMException names to WebBLE error codes\n */\nexport const WEB_BLUETOOTH_ERROR_MAP: Record<string, WebBLEErrorCode> = {\n NotFoundError: WebBLEErrorCode.USER_CANCELLED,\n SecurityError: WebBLEErrorCode.INSECURE_CONTEXT,\n NotSupportedError: WebBLEErrorCode.NOT_SUPPORTED,\n NetworkError: WebBLEErrorCode.UNEXPECTED_DISCONNECT,\n TimeoutError: WebBLEErrorCode.TIMEOUT,\n NotAllowedError: WebBLEErrorCode.PERMISSION_DENIED,\n};\n\n/**\n * User-friendly error messages with solutions\n */\nexport const WEB_BLUETOOTH_ERROR_MESSAGES: Record<\n WebBLEErrorCode,\n {\n message: string;\n solution: string;\n }\n> = {\n [WebBLEErrorCode.NOT_SUPPORTED]: {\n message: \"Web Bluetooth is not supported in this browser\",\n solution: \"Use Chrome, Edge, or Opera browser with Web Bluetooth support\",\n },\n [WebBLEErrorCode.INSECURE_CONTEXT]: {\n message: \"Web Bluetooth requires a secure context (HTTPS)\",\n solution: \"Use HTTPS or localhost for development\",\n },\n [WebBLEErrorCode.USER_CANCELLED]: {\n message: \"Device selection was cancelled\",\n solution: \"Please select a device from the list to continue\",\n },\n [WebBLEErrorCode.DEVICE_NOT_FOUND]: {\n message: \"Bluetooth device not found or not available\",\n solution: \"Ensure device is powered on, in range, and advertising\",\n },\n [WebBLEErrorCode.GATT_CONNECTION_FAILED]: {\n message: \"Failed to connect to device GATT server\",\n solution: \"Check device is available and try again\",\n },\n [WebBLEErrorCode.SERVICE_NOT_FOUND]: {\n message: \"Required service not found on device\",\n solution: \"Verify device firmware supports the required service\",\n },\n [WebBLEErrorCode.CHARACTERISTIC_NOT_FOUND]: {\n message: \"Required characteristic not found in service\",\n solution: \"Check device firmware implements the required characteristics\",\n },\n [WebBLEErrorCode.NOTIFICATION_FAILED]: {\n message: \"Failed to enable notifications on characteristic\",\n solution: \"Verify characteristic supports notifications\",\n },\n [WebBLEErrorCode.WRITE_FAILED]: {\n message: \"Failed to write data to characteristic\",\n solution: \"Check connection and characteristic write permissions\",\n },\n [WebBLEErrorCode.UNEXPECTED_DISCONNECT]: {\n message: \"Device disconnected unexpectedly\",\n solution: \"Check device power and Bluetooth range\",\n },\n [WebBLEErrorCode.PERMISSION_DENIED]: {\n message: \"Bluetooth permission denied\",\n solution: \"Allow Bluetooth access in browser settings\",\n },\n [WebBLEErrorCode.TIMEOUT]: {\n message: \"Operation timed out\",\n solution: \"Check device availability and try again\",\n },\n};\n","import {\n BaseTransport,\n ConnectionError,\n DisconnectionError,\n getLogger,\n SendError,\n ValidationError,\n} from \"@eventmsg/core\";\nimport type { WebBLETransportConfig } from \"./types/index.js\";\nimport {\n WEB_BLUETOOTH_ERROR_MAP,\n WEB_BLUETOOTH_ERROR_MESSAGES,\n} from \"./types/index.js\";\n\n// UUID validation regex\nconst UUID_REGEX =\n /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n\n/**\n * WebBLE Transport for EventMsgV3 protocol\n *\n * Provides Web Bluetooth connectivity for browser-based communication\n * with ESP32 devices using BLE/GATT protocol.\n *\n * Features:\n * - Nordic UART Service support by default\n * - Automatic data chunking for BLE MTU limitations\n * - Device caching and automatic reconnection\n * - Browser compatibility checks\n * - Comprehensive error handling\n */\nexport class WebBLETransport extends BaseTransport<\n WebBLETransportConfig,\n BluetoothDevice\n> {\n private readonly logger = getLogger(\"TRANSPORT_WEBBLE\");\n private server: BluetoothRemoteGATTServer | undefined;\n private service: BluetoothRemoteGATTService | undefined;\n private rxCharacteristic: BluetoothRemoteGATTCharacteristic | undefined;\n private txCharacteristic: BluetoothRemoteGATTCharacteristic | undefined;\n\n private mtu = 480;\n private deviceCache: string | null = null;\n\n private receiveBuffer = new Uint8Array(0);\n private readonly MAX_MESSAGE_SIZE = 4096;\n private bufferTimeout?: NodeJS.Timeout | undefined;\n\n constructor(config: WebBLETransportConfig) {\n super(config);\n\n this.checkBrowserSupport();\n this.config.connection = {\n timeout: 10_000,\n persistDevice: true,\n reconnectAttempts: 3,\n reconnectDelay: 1000,\n ...this.config.connection,\n };\n }\n\n /**\n * Check if Web Bluetooth is supported in current browser\n * @throws {ValidationError} If Web Bluetooth is not supported\n */\n private checkBrowserSupport(): void {\n if (!(\"bluetooth\" in navigator)) {\n throw new ValidationError(\n \"Web Bluetooth API not supported in this browser\",\n {\n context: {\n ...this.getErrorContext(\"browser_check\"),\n solution:\n \"Use Chrome, Edge, or Opera browser with Web Bluetooth support\",\n },\n }\n );\n }\n\n if (!window.isSecureContext) {\n throw new ValidationError(\n \"Web Bluetooth requires a secure context (HTTPS)\",\n {\n context: {\n ...this.getErrorContext(\"browser_check\"),\n solution: \"Use HTTPS or localhost for development\",\n },\n }\n );\n }\n }\n\n /**\n * Validate WebBLE-specific configuration\n * @param config Configuration to validate\n * @throws {ValidationError} If configuration is invalid\n */\n protected validateConfig(config: WebBLETransportConfig): void {\n // Call parent validation for base config\n super.validateConfig(config);\n\n // Validate service configuration\n if (!config.service) {\n throw new ValidationError(\"Service configuration is required\", {\n context: this.getErrorContext(\"config_validation\"),\n });\n }\n\n if (!config.service.uuid) {\n throw new ValidationError(\"Service UUID is required\", {\n context: this.getErrorContext(\"config_validation\"),\n });\n }\n\n if (!config.service.txCharacteristic) {\n throw new ValidationError(\"TX characteristic UUID is required\", {\n context: this.getErrorContext(\"config_validation\"),\n });\n }\n\n if (!config.service.rxCharacteristic) {\n throw new ValidationError(\"RX characteristic UUID is required\", {\n context: this.getErrorContext(\"config_validation\"),\n });\n }\n\n // Validate UUIDs format (basic check)\n if (!UUID_REGEX.test(config.service.uuid)) {\n throw new ValidationError(\"Invalid service UUID format\", {\n context: {\n ...this.getErrorContext(\"config_validation\"),\n serviceUuid: config.service.uuid,\n },\n });\n }\n\n if (!UUID_REGEX.test(config.service.txCharacteristic)) {\n throw new ValidationError(\"Invalid TX characteristic UUID format\", {\n context: {\n ...this.getErrorContext(\"config_validation\"),\n txCharacteristic: config.service.txCharacteristic,\n },\n });\n }\n\n if (!UUID_REGEX.test(config.service.rxCharacteristic)) {\n throw new ValidationError(\"Invalid RX characteristic UUID format\", {\n context: {\n ...this.getErrorContext(\"config_validation\"),\n rxCharacteristic: config.service.rxCharacteristic,\n },\n });\n }\n }\n\n /**\n * Transport-specific connection implementation\n * Handles Web Bluetooth device selection and GATT connection\n */\n protected async doConnect(): Promise<void> {\n try {\n this.device = await this.getOrRequestDevice();\n if (!this.device.gatt) {\n throw new Error(\"Device does not support GATT\");\n }\n\n this.server = await this.device.gatt.connect();\n this.service = await this.server.getPrimaryService(\n this.config.service.uuid\n );\n this.rxCharacteristic = await this.service.getCharacteristic(\n this.config.service.rxCharacteristic\n );\n this.txCharacteristic = await this.service.getCharacteristic(\n this.config.service.txCharacteristic\n );\n await this.txCharacteristic.startNotifications();\n this.txCharacteristic.addEventListener(\n \"characteristicvaluechanged\",\n this.handleIncomingData.bind(this)\n );\n this.device.addEventListener(\n \"gattserverdisconnected\",\n this.handleGATTDisconnect.bind(this)\n );\n\n this.negotiateMTU();\n if (this.config.connection?.persistDevice) {\n this.cacheDevice();\n }\n this.logger.info(\"WebBLE connected successfully\", {\n deviceName: this.device?.name || \"Unknown\",\n deviceId: this.device?.id,\n mtu: this.mtu,\n serviceUuid: this.config.service.uuid,\n rxCharacteristic: this.config.service.rxCharacteristic,\n txCharacteristic: this.config.service.txCharacteristic,\n });\n } catch (error) {\n await this.cleanupConnection();\n throw this.wrapBLEError(error, \"connection\");\n }\n }\n\n /**\n * Transport-specific disconnection implementation\n * Cleans up BLE resources and disconnects\n */\n protected async doDisconnect(): Promise<void> {\n try {\n this.logger.info(\"WebBLE disconnecting\", {\n deviceName: this.device?.name || \"Unknown\",\n connected: this.server?.connected,\n });\n await this.cleanupConnection();\n this.logger.info(\"WebBLE disconnected successfully\");\n } catch (error) {\n throw this.wrapBLEError(error, \"disconnection\");\n }\n }\n\n /**\n * Transport-specific send implementation\n * Sends data via BLE characteristic with automatic chunking\n */\n protected async doSend(data: Uint8Array): Promise<void> {\n if (!this.rxCharacteristic) {\n throw new SendError(\"RX characteristic not available\", {\n context: this.getErrorContext(\"send\"),\n });\n }\n\n try {\n // Chunk data for BLE MTU limitations\n const chunks = this.chunkData(data);\n\n this.logger.debug(\"Sending message via WebBLE\", {\n totalSize: data.length,\n chunkCount: chunks.length,\n mtu: this.mtu,\n });\n\n // Send chunks sequentially (required for BLE reliability)\n // Note: BLE often requires sequential sending for proper ordering\n for (const chunk of chunks) {\n await this.sendChunk(chunk);\n }\n\n this.logger.debug(\"Message sent successfully\", {\n totalSize: data.length,\n chunksSent: chunks.length,\n });\n } catch (error) {\n throw this.wrapBLEError(error, \"send\");\n }\n }\n\n /**\n * Get cached device or request new device from user\n */\n private getOrRequestDevice(): Promise<BluetoothDevice> {\n // Try cached device first\n if (this.config.connection?.persistDevice && this.deviceCache) {\n // TODO: Implement device cache retrieval\n // This would require storing device ID and attempting reconnection\n // For now, just clear cache and request new device\n this.deviceCache = null;\n }\n\n // Request new device from user\n return this.requestDevice();\n }\n\n /**\n * Request device selection from user\n */\n private async requestDevice(): Promise<BluetoothDevice> {\n const filters = this.config.filters || [\n { services: [this.config.service.uuid] },\n ];\n\n try {\n const device = await navigator.bluetooth.requestDevice({\n filters,\n optionalServices: [this.config.service.uuid],\n });\n\n return device;\n } catch (error) {\n throw this.wrapBLEError(error, \"device_request\");\n }\n }\n\n /**\n * Handle incoming data from BLE characteristic\n */\n private handleIncomingData(event: Event): void {\n const characteristic = event.target as BluetoothRemoteGATTCharacteristic;\n const value = characteristic.value;\n\n if (!value) {\n return;\n }\n\n // Convert DataView to Uint8Array\n const chunk = new Uint8Array(value.buffer);\n\n this.logger.debug(\"WebBLE chunk received\", {\n chunkSize: chunk.length,\n currentBufferSize: this.receiveBuffer.length,\n newBufferSize: this.receiveBuffer.length + chunk.length,\n });\n\n // Simple concatenation\n const newBuffer = new Uint8Array(this.receiveBuffer.length + chunk.length);\n newBuffer.set(this.receiveBuffer);\n newBuffer.set(chunk, this.receiveBuffer.length);\n this.receiveBuffer = newBuffer;\n\n // Safety check for malformed data\n if (this.receiveBuffer.length > this.MAX_MESSAGE_SIZE) {\n this.logger.warn(\"Message too large, clearing buffer\", {\n bufferSize: this.receiveBuffer.length,\n maxSize: this.MAX_MESSAGE_SIZE,\n chunkSize: chunk.length,\n });\n this.clearReceiveBuffer();\n return;\n }\n\n // Reset timeout on each chunk\n this.resetBufferTimeout();\n\n // Check if we have a complete message (starts with SOH, ends with EOT)\n if (\n this.receiveBuffer.length >= 10 && // Minimum valid message size\n this.receiveBuffer[0] === 0x01 && // SOH\n this.receiveBuffer.at(-1) === 0x04\n ) {\n // EOT\n\n this.logger.info(\"Message reassembled successfully\", {\n finalSize: this.receiveBuffer.length,\n chunksReceived:\n this.receiveBuffer.length <= 500\n ? 1\n : Math.ceil(this.receiveBuffer.length / 500),\n });\n\n // Emit complete message\n this.emit(\"data\", this.receiveBuffer);\n\n // Clear buffer for next message\n this.clearReceiveBuffer();\n } else {\n // Set timeout to clear stale buffer after 5 seconds\n this.bufferTimeout = setTimeout(() => {\n if (this.receiveBuffer.length > 0) {\n this.logger.warn(\"Partial message timeout, clearing buffer\", {\n bufferSize: this.receiveBuffer.length,\n timeoutMs: 5000,\n hasSOH: this.receiveBuffer[0] === 0x01,\n hasEOT: this.receiveBuffer.at(-1) === 0x04,\n });\n this.clearReceiveBuffer();\n }\n }, 5000);\n }\n }\n\n /**\n * Clear receive buffer and timeout\n */\n private clearReceiveBuffer(): void {\n this.receiveBuffer = new Uint8Array(0);\n this.resetBufferTimeout();\n }\n\n /**\n * Reset buffer timeout\n */\n private resetBufferTimeout(): void {\n if (this.bufferTimeout) {\n clearTimeout(this.bufferTimeout);\n this.bufferTimeout = undefined;\n }\n }\n\n /**\n * Handle GATT server disconnection event\n */\n private handleGATTDisconnect(_event: Event): void {\n this.handleUnexpectedDisconnect();\n }\n\n /**\n * Negotiate MTU size with device\n */\n private negotiateMTU(): void {\n // Web Bluetooth doesn't provide direct MTU negotiation\n // Use conservative default for now\n this.mtu = this.config.connection?.mtu || 20;\n }\n\n /**\n * Cache device for reconnection\n */\n private cacheDevice(): void {\n if (this.device) {\n this.deviceCache = this.device.id;\n }\n }\n\n /**\n * Split data into chunks that fit within BLE MTU\n */\n private chunkData(data: Uint8Array): Uint8Array[] {\n const chunkSize = this.mtu;\n const chunks: Uint8Array[] = [];\n\n for (let i = 0; i < data.length; i += chunkSize) {\n chunks.push(data.slice(i, i + chunkSize));\n }\n\n return chunks;\n }\n\n /**\n * Send a single data chunk\n */\n private async sendChunk(chunk: Uint8Array): Promise<void> {\n if (!this.rxCharacteristic) {\n throw new Error(\"RX characteristic not available\");\n }\n\n try {\n await this.rxCharacteristic.writeValueWithResponse(chunk as BufferSource);\n } catch (error) {\n this.logger.error(\"BLE chunk send failed\", {\n error: error instanceof Error ? error.message : String(error),\n chunkSize: chunk.length,\n cause: error instanceof Error ? error.name : \"Unknown\",\n });\n throw error; // Throw original error\n }\n }\n\n /**\n * Clean up BLE connection resources\n */\n private async cleanupConnection(): Promise<void> {\n // Clear receive buffer and timeout\n this.clearReceiveBuffer();\n\n // Stop notifications\n if (this.txCharacteristic) {\n try {\n await this.txCharacteristic.stopNotifications();\n } catch {\n // Ignore errors during cleanup\n }\n this.txCharacteristic = undefined;\n }\n\n // Clear characteristics\n this.rxCharacteristic = undefined;\n this.service = undefined;\n\n // Disconnect GATT server\n if (this.server?.connected) {\n this.server.disconnect();\n }\n this.server = undefined;\n\n this.device = null;\n }\n\n /**\n * Convert Web Bluetooth errors to transport errors\n */\n private wrapBLEError(error: unknown, operation: string): Error {\n if (error instanceof Error) {\n // Map known Web Bluetooth errors\n const bleErrorCode = WEB_BLUETOOTH_ERROR_MAP[error.name];\n\n if (bleErrorCode) {\n const errorInfo = WEB_BLUETOOTH_ERROR_MESSAGES[bleErrorCode];\n\n // Choose appropriate transport error type\n if (operation === \"connection\") {\n return new ConnectionError(errorInfo.message, {\n cause: error,\n context: {\n ...this.getErrorContext(operation),\n solution: errorInfo.solution,\n },\n });\n }\n if (operation === \"disconnection\") {\n return new DisconnectionError(errorInfo.message, {\n cause: error,\n context: {\n ...this.getErrorContext(operation),\n solution: errorInfo.solution,\n },\n });\n }\n return new SendError(errorInfo.message, {\n cause: error,\n context: {\n ...this.getErrorContext(operation),\n solution: errorInfo.solution,\n },\n });\n }\n }\n\n // Fallback for unknown errors\n const message = error instanceof Error ? error.message : String(error);\n return new ConnectionError(`BLE ${operation} failed: ${message}`, {\n cause: error instanceof Error ? error : new Error(String(error)),\n context: this.getErrorContext(operation),\n });\n }\n}\n"],"mappings":"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"}