@voltras/node-sdk 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (203) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +272 -0
  3. package/dist/cjs/bluetooth/adapters/base.js +116 -0
  4. package/dist/cjs/bluetooth/adapters/base.js.map +1 -0
  5. package/dist/cjs/bluetooth/adapters/index.js +58 -0
  6. package/dist/cjs/bluetooth/adapters/index.js.map +1 -0
  7. package/dist/cjs/bluetooth/adapters/native.js +473 -0
  8. package/dist/cjs/bluetooth/adapters/native.js.map +1 -0
  9. package/dist/cjs/bluetooth/adapters/node.js +228 -0
  10. package/dist/cjs/bluetooth/adapters/node.js.map +1 -0
  11. package/dist/cjs/bluetooth/adapters/types.js +11 -0
  12. package/dist/cjs/bluetooth/adapters/types.js.map +1 -0
  13. package/dist/cjs/bluetooth/adapters/web-bluetooth-base.js +187 -0
  14. package/dist/cjs/bluetooth/adapters/web-bluetooth-base.js.map +1 -0
  15. package/dist/cjs/bluetooth/adapters/web.js +112 -0
  16. package/dist/cjs/bluetooth/adapters/web.js.map +1 -0
  17. package/dist/cjs/bluetooth/controllers/scanner-controller.js +145 -0
  18. package/dist/cjs/bluetooth/controllers/scanner-controller.js.map +1 -0
  19. package/dist/cjs/bluetooth/index.js +27 -0
  20. package/dist/cjs/bluetooth/index.js.map +1 -0
  21. package/dist/cjs/bluetooth/models/connection.js +68 -0
  22. package/dist/cjs/bluetooth/models/connection.js.map +1 -0
  23. package/dist/cjs/bluetooth/models/device.js +26 -0
  24. package/dist/cjs/bluetooth/models/device.js.map +1 -0
  25. package/dist/cjs/bluetooth/models/environment.js +106 -0
  26. package/dist/cjs/bluetooth/models/environment.js.map +1 -0
  27. package/dist/cjs/errors.js +167 -0
  28. package/dist/cjs/errors.js.map +1 -0
  29. package/dist/cjs/index.js +116 -0
  30. package/dist/cjs/index.js.map +1 -0
  31. package/dist/cjs/react/hooks.js +262 -0
  32. package/dist/cjs/react/hooks.js.map +1 -0
  33. package/dist/cjs/react/index.js +18 -0
  34. package/dist/cjs/react/index.js.map +1 -0
  35. package/dist/cjs/sdk/index.js +14 -0
  36. package/dist/cjs/sdk/index.js.map +1 -0
  37. package/dist/cjs/sdk/types.js +8 -0
  38. package/dist/cjs/sdk/types.js.map +1 -0
  39. package/dist/cjs/sdk/voltra-client.js +632 -0
  40. package/dist/cjs/sdk/voltra-client.js.map +1 -0
  41. package/dist/cjs/sdk/voltra-manager.js +419 -0
  42. package/dist/cjs/sdk/voltra-manager.js.map +1 -0
  43. package/dist/cjs/shared/index.js +12 -0
  44. package/dist/cjs/shared/index.js.map +1 -0
  45. package/dist/cjs/shared/utils.js +51 -0
  46. package/dist/cjs/shared/utils.js.map +1 -0
  47. package/dist/cjs/voltra/index.js +56 -0
  48. package/dist/cjs/voltra/index.js.map +1 -0
  49. package/dist/cjs/voltra/models/connection.js +68 -0
  50. package/dist/cjs/voltra/models/connection.js.map +1 -0
  51. package/dist/cjs/voltra/models/device-filter.js +28 -0
  52. package/dist/cjs/voltra/models/device-filter.js.map +1 -0
  53. package/dist/cjs/voltra/models/device.js +152 -0
  54. package/dist/cjs/voltra/models/device.js.map +1 -0
  55. package/dist/cjs/voltra/models/telemetry/frame.js +46 -0
  56. package/dist/cjs/voltra/models/telemetry/frame.js.map +1 -0
  57. package/dist/cjs/voltra/models/telemetry/index.js +14 -0
  58. package/dist/cjs/voltra/models/telemetry/index.js.map +1 -0
  59. package/dist/cjs/voltra/protocol/commands.js +230 -0
  60. package/dist/cjs/voltra/protocol/commands.js.map +1 -0
  61. package/dist/cjs/voltra/protocol/constants.js +136 -0
  62. package/dist/cjs/voltra/protocol/constants.js.map +1 -0
  63. package/dist/cjs/voltra/protocol/data/chains.json +830 -0
  64. package/dist/cjs/voltra/protocol/data/eccentric.json +1598 -0
  65. package/dist/cjs/voltra/protocol/data/protocol.json +54 -0
  66. package/dist/cjs/voltra/protocol/data/weights.json +62 -0
  67. package/dist/cjs/voltra/protocol/index.js +25 -0
  68. package/dist/cjs/voltra/protocol/index.js.map +1 -0
  69. package/dist/cjs/voltra/protocol/telemetry-decoder.js +146 -0
  70. package/dist/cjs/voltra/protocol/telemetry-decoder.js.map +1 -0
  71. package/dist/esm/bluetooth/adapters/base.js +112 -0
  72. package/dist/esm/bluetooth/adapters/base.js.map +1 -0
  73. package/dist/esm/bluetooth/adapters/index.js +51 -0
  74. package/dist/esm/bluetooth/adapters/index.js.map +1 -0
  75. package/dist/esm/bluetooth/adapters/native.js +469 -0
  76. package/dist/esm/bluetooth/adapters/native.js.map +1 -0
  77. package/dist/esm/bluetooth/adapters/node.js +191 -0
  78. package/dist/esm/bluetooth/adapters/node.js.map +1 -0
  79. package/dist/esm/bluetooth/adapters/types.js +10 -0
  80. package/dist/esm/bluetooth/adapters/types.js.map +1 -0
  81. package/dist/esm/bluetooth/adapters/web-bluetooth-base.js +183 -0
  82. package/dist/esm/bluetooth/adapters/web-bluetooth-base.js.map +1 -0
  83. package/dist/esm/bluetooth/adapters/web.js +108 -0
  84. package/dist/esm/bluetooth/adapters/web.js.map +1 -0
  85. package/dist/esm/bluetooth/controllers/scanner-controller.js +141 -0
  86. package/dist/esm/bluetooth/controllers/scanner-controller.js.map +1 -0
  87. package/dist/esm/bluetooth/index.js +17 -0
  88. package/dist/esm/bluetooth/index.js.map +1 -0
  89. package/dist/esm/bluetooth/models/connection.js +63 -0
  90. package/dist/esm/bluetooth/models/connection.js.map +1 -0
  91. package/dist/esm/bluetooth/models/device.js +22 -0
  92. package/dist/esm/bluetooth/models/device.js.map +1 -0
  93. package/dist/esm/bluetooth/models/environment.js +101 -0
  94. package/dist/esm/bluetooth/models/environment.js.map +1 -0
  95. package/dist/esm/errors.js +155 -0
  96. package/dist/esm/errors.js.map +1 -0
  97. package/dist/esm/index.js +72 -0
  98. package/dist/esm/index.js.map +1 -0
  99. package/dist/esm/react/hooks.js +257 -0
  100. package/dist/esm/react/hooks.js.map +1 -0
  101. package/dist/esm/react/index.js +12 -0
  102. package/dist/esm/react/index.js.map +1 -0
  103. package/dist/esm/sdk/index.js +9 -0
  104. package/dist/esm/sdk/index.js.map +1 -0
  105. package/dist/esm/sdk/types.js +7 -0
  106. package/dist/esm/sdk/types.js.map +1 -0
  107. package/dist/esm/sdk/voltra-client.js +628 -0
  108. package/dist/esm/sdk/voltra-client.js.map +1 -0
  109. package/dist/esm/sdk/voltra-manager.js +415 -0
  110. package/dist/esm/sdk/voltra-manager.js.map +1 -0
  111. package/dist/esm/shared/index.js +5 -0
  112. package/dist/esm/shared/index.js.map +1 -0
  113. package/dist/esm/shared/utils.js +45 -0
  114. package/dist/esm/shared/utils.js.map +1 -0
  115. package/dist/esm/voltra/index.js +26 -0
  116. package/dist/esm/voltra/index.js.map +1 -0
  117. package/dist/esm/voltra/models/connection.js +63 -0
  118. package/dist/esm/voltra/models/connection.js.map +1 -0
  119. package/dist/esm/voltra/models/device-filter.js +23 -0
  120. package/dist/esm/voltra/models/device-filter.js.map +1 -0
  121. package/dist/esm/voltra/models/device.js +148 -0
  122. package/dist/esm/voltra/models/device.js.map +1 -0
  123. package/dist/esm/voltra/models/telemetry/frame.js +40 -0
  124. package/dist/esm/voltra/models/telemetry/frame.js.map +1 -0
  125. package/dist/esm/voltra/models/telemetry/index.js +7 -0
  126. package/dist/esm/voltra/models/telemetry/index.js.map +1 -0
  127. package/dist/esm/voltra/protocol/commands.js +224 -0
  128. package/dist/esm/voltra/protocol/commands.js.map +1 -0
  129. package/dist/esm/voltra/protocol/constants.js +130 -0
  130. package/dist/esm/voltra/protocol/constants.js.map +1 -0
  131. package/dist/esm/voltra/protocol/data/chains.json +830 -0
  132. package/dist/esm/voltra/protocol/data/eccentric.json +1598 -0
  133. package/dist/esm/voltra/protocol/data/protocol.json +54 -0
  134. package/dist/esm/voltra/protocol/data/weights.json +62 -0
  135. package/dist/esm/voltra/protocol/index.js +9 -0
  136. package/dist/esm/voltra/protocol/index.js.map +1 -0
  137. package/dist/esm/voltra/protocol/telemetry-decoder.js +140 -0
  138. package/dist/esm/voltra/protocol/telemetry-decoder.js.map +1 -0
  139. package/dist/types/bluetooth/adapters/base.d.ts +85 -0
  140. package/dist/types/bluetooth/adapters/base.d.ts.map +1 -0
  141. package/dist/types/bluetooth/adapters/index.d.ts +35 -0
  142. package/dist/types/bluetooth/adapters/index.d.ts.map +1 -0
  143. package/dist/types/bluetooth/adapters/native.d.ts +109 -0
  144. package/dist/types/bluetooth/adapters/native.d.ts.map +1 -0
  145. package/dist/types/bluetooth/adapters/node.d.ts +91 -0
  146. package/dist/types/bluetooth/adapters/node.d.ts.map +1 -0
  147. package/dist/types/bluetooth/adapters/types.d.ts +102 -0
  148. package/dist/types/bluetooth/adapters/types.d.ts.map +1 -0
  149. package/dist/types/bluetooth/adapters/web-bluetooth-base.d.ts +90 -0
  150. package/dist/types/bluetooth/adapters/web-bluetooth-base.d.ts.map +1 -0
  151. package/dist/types/bluetooth/adapters/web.d.ts +57 -0
  152. package/dist/types/bluetooth/adapters/web.d.ts.map +1 -0
  153. package/dist/types/bluetooth/controllers/scanner-controller.d.ts +93 -0
  154. package/dist/types/bluetooth/controllers/scanner-controller.d.ts.map +1 -0
  155. package/dist/types/bluetooth/index.d.ts +14 -0
  156. package/dist/types/bluetooth/index.d.ts.map +1 -0
  157. package/dist/types/bluetooth/models/connection.d.ts +37 -0
  158. package/dist/types/bluetooth/models/connection.d.ts.map +1 -0
  159. package/dist/types/bluetooth/models/device.d.ts +25 -0
  160. package/dist/types/bluetooth/models/device.d.ts.map +1 -0
  161. package/dist/types/bluetooth/models/environment.d.ts +45 -0
  162. package/dist/types/bluetooth/models/environment.d.ts.map +1 -0
  163. package/dist/types/errors.d.ts +113 -0
  164. package/dist/types/errors.d.ts.map +1 -0
  165. package/dist/types/index.d.ts +55 -0
  166. package/dist/types/index.d.ts.map +1 -0
  167. package/dist/types/react/hooks.d.ts +130 -0
  168. package/dist/types/react/hooks.d.ts.map +1 -0
  169. package/dist/types/react/index.d.ts +12 -0
  170. package/dist/types/react/index.d.ts.map +1 -0
  171. package/dist/types/sdk/index.d.ts +11 -0
  172. package/dist/types/sdk/index.d.ts.map +1 -0
  173. package/dist/types/sdk/types.d.ts +104 -0
  174. package/dist/types/sdk/types.d.ts.map +1 -0
  175. package/dist/types/sdk/voltra-client.d.ts +221 -0
  176. package/dist/types/sdk/voltra-client.d.ts.map +1 -0
  177. package/dist/types/sdk/voltra-manager.d.ts +226 -0
  178. package/dist/types/sdk/voltra-manager.d.ts.map +1 -0
  179. package/dist/types/shared/index.d.ts +5 -0
  180. package/dist/types/shared/index.d.ts.map +1 -0
  181. package/dist/types/shared/utils.d.ts +25 -0
  182. package/dist/types/shared/utils.d.ts.map +1 -0
  183. package/dist/types/voltra/index.d.ts +13 -0
  184. package/dist/types/voltra/index.d.ts.map +1 -0
  185. package/dist/types/voltra/models/connection.d.ts +37 -0
  186. package/dist/types/voltra/models/connection.d.ts.map +1 -0
  187. package/dist/types/voltra/models/device-filter.d.ts +19 -0
  188. package/dist/types/voltra/models/device-filter.d.ts.map +1 -0
  189. package/dist/types/voltra/models/device.d.ts +105 -0
  190. package/dist/types/voltra/models/device.d.ts.map +1 -0
  191. package/dist/types/voltra/models/telemetry/frame.d.ts +41 -0
  192. package/dist/types/voltra/models/telemetry/frame.d.ts.map +1 -0
  193. package/dist/types/voltra/models/telemetry/index.d.ts +8 -0
  194. package/dist/types/voltra/models/telemetry/index.d.ts.map +1 -0
  195. package/dist/types/voltra/protocol/commands.d.ts +99 -0
  196. package/dist/types/voltra/protocol/commands.d.ts.map +1 -0
  197. package/dist/types/voltra/protocol/constants.d.ts +103 -0
  198. package/dist/types/voltra/protocol/constants.d.ts.map +1 -0
  199. package/dist/types/voltra/protocol/index.d.ts +9 -0
  200. package/dist/types/voltra/protocol/index.d.ts.map +1 -0
  201. package/dist/types/voltra/protocol/telemetry-decoder.d.ts +45 -0
  202. package/dist/types/voltra/protocol/telemetry-decoder.d.ts.map +1 -0
  203. package/package.json +111 -0
@@ -0,0 +1,191 @@
1
+ /**
2
+ * NodeBLEAdapter
3
+ *
4
+ * BLE adapter for Node.js using the webbluetooth npm package.
5
+ * Implements the same W3C Web Bluetooth API for server-side BLE.
6
+ *
7
+ * Requirements:
8
+ * - Node.js environment
9
+ * - webbluetooth npm package
10
+ * - Platform-specific Bluetooth support (macOS, Linux, Windows)
11
+ */
12
+ import { WebBluetoothBase } from './web-bluetooth-base';
13
+ // Dynamic import for webbluetooth to avoid bundling issues in non-Node environments
14
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
15
+ let BluetoothClass = null;
16
+ /**
17
+ * BLE adapter for Node.js environments using webbluetooth package.
18
+ *
19
+ * Device selection flow:
20
+ * 1. Call scan() - starts BLE scan
21
+ * 2. Devices are discovered via deviceFound callback
22
+ * 3. deviceChooser function selects a device (or first match by default)
23
+ * 4. Call connect() to establish GATT connection
24
+ */
25
+ export class NodeBLEAdapter extends WebBluetoothBase {
26
+ constructor(config) {
27
+ super(config);
28
+ /** Device selected during scan, stored for connect() */
29
+ this.selectedDevice = null;
30
+ /** List of discovered devices during current scan */
31
+ this.discoveredDevices = [];
32
+ this.deviceChooser = config.deviceChooser ?? null;
33
+ }
34
+ /**
35
+ * Set the device chooser function.
36
+ * Called during scan to programmatically select a device.
37
+ *
38
+ * @param chooser Function that receives discovered devices and returns the chosen one
39
+ */
40
+ setDeviceChooser(chooser) {
41
+ this.deviceChooser = chooser;
42
+ }
43
+ /**
44
+ * Ensure webbluetooth is loaded (lazy load to avoid bundler issues).
45
+ */
46
+ async ensureBluetoothLoaded() {
47
+ if (!BluetoothClass) {
48
+ try {
49
+ // Dynamic import for Node.js - get the Bluetooth class constructor
50
+ const webbluetooth = await import('webbluetooth');
51
+ BluetoothClass = webbluetooth.Bluetooth;
52
+ }
53
+ catch {
54
+ throw new Error('webbluetooth package not found. Install with: npm install webbluetooth');
55
+ }
56
+ }
57
+ }
58
+ /**
59
+ * Scan for devices programmatically.
60
+ *
61
+ * In Node.js, there's no browser picker. Devices are discovered via
62
+ * the deviceFound callback, and the deviceChooser function selects one.
63
+ *
64
+ * IMPORTANT: The webbluetooth package requires creating a new Bluetooth
65
+ * instance with deviceFound in the constructor for the callback to work
66
+ * reliably. Using the default singleton doesn't call the callback consistently.
67
+ *
68
+ * @param timeout Scan timeout in seconds
69
+ * @returns Array of discovered devices (selection happens via deviceChooser)
70
+ */
71
+ async scan(timeout) {
72
+ await this.ensureBluetoothLoaded();
73
+ this.discoveredDevices = [];
74
+ this.selectedDevice = null;
75
+ return new Promise((resolve, reject) => {
76
+ const timeoutId = setTimeout(() => {
77
+ // Timeout - return discovered devices
78
+ console.log(`[NodeBLE] Scan timeout. Found ${this.discoveredDevices.length} device(s)`);
79
+ resolve(this.discoveredDevices);
80
+ }, timeout * 1000);
81
+ // Create a new Bluetooth instance with deviceFound in constructor
82
+ // This is required for the callback to fire reliably in webbluetooth
83
+ const bluetooth = new BluetoothClass({
84
+ deviceFound: (device, selectFn) => {
85
+ // Apply name prefix filter manually (more reliable than built-in filter)
86
+ if (this.config.deviceNamePrefix &&
87
+ !device.name?.startsWith(this.config.deviceNamePrefix)) {
88
+ // Device doesn't match prefix, skip it
89
+ return false;
90
+ }
91
+ const discoveredDevice = {
92
+ id: device.id,
93
+ name: device.name ?? 'Unknown Device',
94
+ rssi: null,
95
+ };
96
+ // Add to discovered list
97
+ if (!this.discoveredDevices.find((d) => d.id === device.id)) {
98
+ this.discoveredDevices.push(discoveredDevice);
99
+ console.log(`[NodeBLE] Found device: ${device.name}`);
100
+ }
101
+ // Check if we should select this device
102
+ let shouldSelect = false;
103
+ if (this.deviceChooser) {
104
+ // Use custom chooser
105
+ const chosen = this.deviceChooser(this.discoveredDevices);
106
+ shouldSelect = chosen?.id === device.id;
107
+ }
108
+ else {
109
+ // Default: select first matching device
110
+ shouldSelect = true;
111
+ }
112
+ if (shouldSelect) {
113
+ console.log(`[NodeBLE] Selected device: ${device.name}`);
114
+ this.selectedDevice = device;
115
+ clearTimeout(timeoutId);
116
+ selectFn();
117
+ return true;
118
+ }
119
+ return false;
120
+ },
121
+ });
122
+ bluetooth
123
+ .requestDevice({
124
+ // Always use acceptAllDevices - we filter manually in deviceFound
125
+ // The built-in namePrefix filter doesn't work reliably
126
+ acceptAllDevices: true,
127
+ optionalServices: [this.config.serviceUUID],
128
+ })
129
+ .then((device) => {
130
+ // Device was selected
131
+ this.selectedDevice = device;
132
+ resolve(this.discoveredDevices);
133
+ })
134
+ .catch((error) => {
135
+ clearTimeout(timeoutId);
136
+ if (error.name === 'NotFoundError') {
137
+ // No device selected
138
+ console.log('[NodeBLE] No device selected');
139
+ resolve(this.discoveredDevices);
140
+ }
141
+ else {
142
+ reject(error);
143
+ }
144
+ });
145
+ });
146
+ }
147
+ /**
148
+ * Connect to the selected device.
149
+ *
150
+ * @param deviceId Device ID (should match the selected device)
151
+ * @param options Connection options
152
+ */
153
+ async connect(deviceId, options) {
154
+ // Verify we have the device from scan()
155
+ if (!this.selectedDevice) {
156
+ throw new Error('No device selected. Call scan() first.');
157
+ }
158
+ if (this.selectedDevice.id !== deviceId) {
159
+ console.warn(`[NodeBLE] Device ID mismatch: expected ${this.selectedDevice.id}, got ${deviceId}`);
160
+ }
161
+ // Connect to GATT server
162
+ await this.connectToDevice(this.selectedDevice);
163
+ // Handle immediate write if provided (for authentication)
164
+ if (options?.immediateWrite) {
165
+ console.log('[NodeBLE] Sending immediate auth write...');
166
+ await this.write(options.immediateWrite);
167
+ console.log('[NodeBLE] Immediate auth write sent');
168
+ }
169
+ }
170
+ /**
171
+ * Override disconnect to also clear selected device.
172
+ */
173
+ async disconnect() {
174
+ await super.disconnect();
175
+ this.selectedDevice = null;
176
+ this.discoveredDevices = [];
177
+ }
178
+ /**
179
+ * Get the list of devices discovered during the last scan.
180
+ */
181
+ getDiscoveredDevices() {
182
+ return [...this.discoveredDevices];
183
+ }
184
+ /**
185
+ * Check if running in a Node.js environment.
186
+ */
187
+ static isNodeEnvironment() {
188
+ return (typeof process !== 'undefined' && process.versions != null && process.versions.node != null);
189
+ }
190
+ }
191
+ //# sourceMappingURL=node.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node.js","sourceRoot":"","sources":["../../../../src/bluetooth/adapters/node.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * BLE Abstraction Layer Types
3
+ *
4
+ * Defines interfaces for BLE operations implemented by platform-specific adapters:
5
+ * - Native (iOS/Android): react-native-ble-plx
6
+ * - Browser: Web Bluetooth API
7
+ * - Node.js: webbluetooth npm package
8
+ */
9
+ export {};
10
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../../src/bluetooth/adapters/types.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG"}
@@ -0,0 +1,183 @@
1
+ /**
2
+ * WebBluetoothBase
3
+ *
4
+ * Abstract base class for W3C Web Bluetooth API adapters.
5
+ * Provides shared GATT operations for both browser and Node.js environments.
6
+ *
7
+ * Subclasses:
8
+ * - WebBLEAdapter: Browser using navigator.bluetooth
9
+ * - NodeBLEAdapter: Node.js using webbluetooth npm package
10
+ */
11
+ import { BaseBLEAdapter } from './base';
12
+ /**
13
+ * Abstract base class for Web Bluetooth adapters.
14
+ *
15
+ * Implements shared GATT operations:
16
+ * - Service/characteristic discovery
17
+ * - Write operations
18
+ * - Notification subscription
19
+ * - Disconnect cleanup
20
+ *
21
+ * Subclasses implement platform-specific device selection (scan/connect).
22
+ */
23
+ export class WebBluetoothBase extends BaseBLEAdapter {
24
+ constructor(config) {
25
+ super();
26
+ /** Connected Bluetooth device */
27
+ this.device = null;
28
+ /** GATT server connection */
29
+ this.server = null;
30
+ /** Write characteristic */
31
+ this.writeChar = null;
32
+ /** Notify characteristic */
33
+ this.notifyChar = null;
34
+ /** Bound notification handler for cleanup */
35
+ this.boundNotificationHandler = null;
36
+ // Web Bluetooth API requires lowercase UUIDs
37
+ this.config = {
38
+ ...config.ble,
39
+ serviceUUID: config.ble.serviceUUID.toLowerCase(),
40
+ writeCharUUID: config.ble.writeCharUUID.toLowerCase(),
41
+ notifyCharUUID: config.ble.notifyCharUUID.toLowerCase(),
42
+ };
43
+ }
44
+ // ===========================================================================
45
+ // Shared GATT operations
46
+ // ===========================================================================
47
+ /**
48
+ * Connect to a Bluetooth device's GATT server and set up characteristics.
49
+ * Called by subclasses after device selection.
50
+ */
51
+ async connectToDevice(device) {
52
+ this.device = device;
53
+ // Set up disconnect listener
54
+ device.addEventListener('gattserverdisconnected', () => {
55
+ console.log('[WebBluetooth] Device disconnected');
56
+ this.handleDisconnect();
57
+ });
58
+ // Connect to GATT server
59
+ if (!device.gatt) {
60
+ throw new Error('Device does not support GATT');
61
+ }
62
+ this.setConnectionState('connecting');
63
+ try {
64
+ this.server = await device.gatt.connect();
65
+ await this.setupCharacteristics();
66
+ this.setConnectionState('connected');
67
+ }
68
+ catch (error) {
69
+ console.error('[WebBluetooth] Connect error:', error);
70
+ this.setConnectionState('disconnected');
71
+ throw error;
72
+ }
73
+ }
74
+ /**
75
+ * Discover service and characteristics, set up notifications.
76
+ */
77
+ async setupCharacteristics() {
78
+ if (!this.server) {
79
+ throw new Error('Not connected to GATT server');
80
+ }
81
+ // Get the primary service
82
+ const service = await this.server.getPrimaryService(this.config.serviceUUID);
83
+ // Get write characteristic
84
+ this.writeChar = await service.getCharacteristic(this.config.writeCharUUID);
85
+ // Get notify characteristic and start notifications
86
+ this.notifyChar = await service.getCharacteristic(this.config.notifyCharUUID);
87
+ // Set up notification handler
88
+ this.boundNotificationHandler = this.handleNotification.bind(this);
89
+ this.notifyChar.addEventListener('characteristicvaluechanged', this.boundNotificationHandler);
90
+ await this.notifyChar.startNotifications();
91
+ // Also listen for notifications on write characteristic if it supports it
92
+ if (this.writeChar.properties.notify) {
93
+ this.writeChar.addEventListener('characteristicvaluechanged', this.boundNotificationHandler);
94
+ await this.writeChar.startNotifications();
95
+ }
96
+ console.log('[WebBluetooth] Characteristics set up');
97
+ }
98
+ /**
99
+ * Handle incoming notification data.
100
+ */
101
+ handleNotification(event) {
102
+ const characteristic = event.target;
103
+ if (characteristic.value) {
104
+ const data = new Uint8Array(characteristic.value.buffer);
105
+ this.emitNotification(data);
106
+ }
107
+ }
108
+ /**
109
+ * Handle device disconnection.
110
+ */
111
+ handleDisconnect() {
112
+ this.cleanup();
113
+ this.setConnectionState('disconnected');
114
+ }
115
+ /**
116
+ * Clean up resources.
117
+ */
118
+ cleanup() {
119
+ // Remove notification listeners
120
+ if (this.notifyChar && this.boundNotificationHandler) {
121
+ try {
122
+ this.notifyChar.removeEventListener('characteristicvaluechanged', this.boundNotificationHandler);
123
+ }
124
+ catch {
125
+ // Ignore errors during cleanup
126
+ }
127
+ }
128
+ if (this.writeChar && this.boundNotificationHandler) {
129
+ try {
130
+ this.writeChar.removeEventListener('characteristicvaluechanged', this.boundNotificationHandler);
131
+ }
132
+ catch {
133
+ // Ignore errors during cleanup
134
+ }
135
+ }
136
+ this.boundNotificationHandler = null;
137
+ this.writeChar = null;
138
+ this.notifyChar = null;
139
+ this.server = null;
140
+ this.device = null;
141
+ }
142
+ // ===========================================================================
143
+ // BLEAdapter interface - implemented
144
+ // ===========================================================================
145
+ /**
146
+ * Disconnect from the device.
147
+ */
148
+ async disconnect() {
149
+ this.setConnectionState('disconnecting');
150
+ if (this.device?.gatt?.connected) {
151
+ this.device.gatt.disconnect();
152
+ }
153
+ this.cleanup();
154
+ this.setConnectionState('disconnected');
155
+ }
156
+ /**
157
+ * Write data to the device.
158
+ */
159
+ async write(data) {
160
+ if (!this.writeChar) {
161
+ throw new Error('Not connected to device');
162
+ }
163
+ // Use ArrayBuffer.slice to ensure we have a proper ArrayBuffer
164
+ const buffer = data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength);
165
+ await this.writeChar.writeValueWithResponse(buffer);
166
+ }
167
+ // ===========================================================================
168
+ // Extended methods
169
+ // ===========================================================================
170
+ /**
171
+ * Get info about the currently connected device.
172
+ */
173
+ getConnectedDevice() {
174
+ if (!this.device)
175
+ return null;
176
+ return {
177
+ id: this.device.id,
178
+ name: this.device.name ?? 'Unknown',
179
+ rssi: null, // Not available via Web Bluetooth after connection
180
+ };
181
+ }
182
+ }
183
+ //# sourceMappingURL=web-bluetooth-base.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"web-bluetooth-base.js","sourceRoot":"","sources":["../../../../src/bluetooth/adapters/web-bluetooth-base.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,108 @@
1
+ /**
2
+ * WebBLEAdapter
3
+ *
4
+ * BLE adapter for web browsers using the native Web Bluetooth API.
5
+ * Uses the browser's built-in device picker for device selection.
6
+ *
7
+ * Requirements:
8
+ * - Chrome, Edge, or Opera browser (Safari/Firefox don't support Web Bluetooth)
9
+ * - HTTPS or localhost
10
+ * - User gesture required to trigger device picker
11
+ */
12
+ import { WebBluetoothBase } from './web-bluetooth-base';
13
+ /**
14
+ * BLE adapter using the browser's native Web Bluetooth API.
15
+ *
16
+ * Device selection flow:
17
+ * 1. Call scan() - triggers browser's device picker
18
+ * 2. User selects device from browser UI
19
+ * 3. scan() returns with the selected device
20
+ * 4. Call connect() to establish GATT connection
21
+ */
22
+ export class WebBLEAdapter extends WebBluetoothBase {
23
+ constructor(config) {
24
+ super(config);
25
+ /** Device selected from browser picker, stored for connect() */
26
+ this.selectedDevice = null;
27
+ }
28
+ /**
29
+ * Scan for devices using the browser's device picker.
30
+ *
31
+ * Note: This triggers the browser's native Bluetooth device picker UI.
32
+ * The user must select a device from the picker. Returns when user
33
+ * selects a device or cancels.
34
+ *
35
+ * @param _timeout Ignored - browser controls the picker timeout
36
+ * @returns Array with single selected device, or empty if cancelled
37
+ */
38
+ async scan(_timeout) {
39
+ try {
40
+ // Request device from browser - this shows the native picker
41
+ const device = await navigator.bluetooth.requestDevice({
42
+ filters: this.config.deviceNamePrefix
43
+ ? [{ namePrefix: this.config.deviceNamePrefix }]
44
+ : undefined,
45
+ acceptAllDevices: !this.config.deviceNamePrefix,
46
+ optionalServices: [this.config.serviceUUID],
47
+ });
48
+ // Store for connect()
49
+ this.selectedDevice = device;
50
+ console.log(`[WebBLE] Device selected: ${device.name}`);
51
+ return [
52
+ {
53
+ id: device.id,
54
+ name: device.name ?? 'Unknown Device',
55
+ rssi: null, // Not available from requestDevice
56
+ },
57
+ ];
58
+ }
59
+ catch (error) {
60
+ // User cancelled or error occurred
61
+ if (error.name === 'NotFoundError') {
62
+ console.log('[WebBLE] User cancelled device selection');
63
+ return [];
64
+ }
65
+ throw error;
66
+ }
67
+ }
68
+ /**
69
+ * Connect to the selected device.
70
+ *
71
+ * Note: In Web Bluetooth, the device is already "selected" during scan().
72
+ * This method establishes the GATT connection.
73
+ *
74
+ * @param deviceId Device ID (should match the selected device)
75
+ * @param options Connection options
76
+ */
77
+ async connect(deviceId, options) {
78
+ // Verify we have the device from scan()
79
+ if (!this.selectedDevice) {
80
+ throw new Error('No device selected. Call scan() first.');
81
+ }
82
+ if (this.selectedDevice.id !== deviceId) {
83
+ console.warn(`[WebBLE] Device ID mismatch: expected ${this.selectedDevice.id}, got ${deviceId}`);
84
+ }
85
+ // Connect to GATT server
86
+ await this.connectToDevice(this.selectedDevice);
87
+ // Handle immediate write if provided (for authentication)
88
+ if (options?.immediateWrite) {
89
+ console.log('[WebBLE] Sending immediate auth write...');
90
+ await this.write(options.immediateWrite);
91
+ console.log('[WebBLE] Immediate auth write sent');
92
+ }
93
+ }
94
+ /**
95
+ * Override disconnect to also clear selected device.
96
+ */
97
+ async disconnect() {
98
+ await super.disconnect();
99
+ this.selectedDevice = null;
100
+ }
101
+ /**
102
+ * Check if Web Bluetooth is supported in this browser.
103
+ */
104
+ static isSupported() {
105
+ return typeof navigator !== 'undefined' && 'bluetooth' in navigator;
106
+ }
107
+ }
108
+ //# sourceMappingURL=web.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"web.js","sourceRoot":"","sources":["../../../../src/bluetooth/adapters/web.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,gBAAgB,EAA2B,MAAM,sBAAsB,CAAC;AAGjF;;;;;;;;GAQG;AACH,MAAM,OAAO,aAAc,SAAQ,gBAAgB;IAIjD,YAAY,MAA0B;QACpC,KAAK,CAAC,MAAM,CAAC,CAAC;QAJhB,gEAAgE;QACxD,mBAAc,GAA2B,IAAI,CAAC;IAItD,CAAC;IAED;;;;;;;;;OASG;IACH,KAAK,CAAC,IAAI,CAAC,QAAgB;QACzB,IAAI,CAAC;YACH,6DAA6D;YAC7D,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,SAAS,CAAC,aAAa,CAAC;gBACrD,OAAO,EAAE,IAAI,CAAC,MAAM,CAAC,gBAAgB;oBACnC,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,IAAI,CAAC,MAAM,CAAC,gBAAgB,EAAE,CAAC;oBAChD,CAAC,CAAC,SAAS;gBACb,gBAAgB,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,gBAAgB;gBAC/C,gBAAgB,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC;aAC5C,CAAC,CAAC;YAEH,sBAAsB;YACtB,IAAI,CAAC,cAAc,GAAG,MAAM,CAAC;YAE7B,OAAO,CAAC,GAAG,CAAC,6BAA6B,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;YAExD,OAAO;gBACL;oBACE,EAAE,EAAE,MAAM,CAAC,EAAE;oBACb,IAAI,EAAE,MAAM,CAAC,IAAI,IAAI,gBAAgB;oBACrC,IAAI,EAAE,IAAI,EAAE,mCAAmC;iBAChD;aACF,CAAC;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,mCAAmC;YACnC,IAAK,KAAe,CAAC,IAAI,KAAK,eAAe,EAAE,CAAC;gBAC9C,OAAO,CAAC,GAAG,CAAC,0CAA0C,CAAC,CAAC;gBACxD,OAAO,EAAE,CAAC;YACZ,CAAC;YACD,MAAM,KAAK,CAAC;QACd,CAAC;IACH,CAAC;IAED;;;;;;;;OAQG;IACH,KAAK,CAAC,OAAO,CAAC,QAAgB,EAAE,OAAwB;QACtD,wCAAwC;QACxC,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE,CAAC;YACzB,MAAM,IAAI,KAAK,CAAC,wCAAwC,CAAC,CAAC;QAC5D,CAAC;QAED,IAAI,IAAI,CAAC,cAAc,CAAC,EAAE,KAAK,QAAQ,EAAE,CAAC;YACxC,OAAO,CAAC,IAAI,CACV,yCAAyC,IAAI,CAAC,cAAc,CAAC,EAAE,SAAS,QAAQ,EAAE,CACnF,CAAC;QACJ,CAAC;QAED,yBAAyB;QACzB,MAAM,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;QAEhD,0DAA0D;QAC1D,IAAI,OAAO,EAAE,cAAc,EAAE,CAAC;YAC5B,OAAO,CAAC,GAAG,CAAC,0CAA0C,CAAC,CAAC;YACxD,MAAM,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC;YACzC,OAAO,CAAC,GAAG,CAAC,oCAAoC,CAAC,CAAC;QACpD,CAAC;IACH,CAAC;IAED;;OAEG;IACM,KAAK,CAAC,UAAU;QACvB,MAAM,KAAK,CAAC,UAAU,EAAE,CAAC;QACzB,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC;IAC7B,CAAC;IAED;;OAEG;IACH,MAAM,CAAC,WAAW;QAChB,OAAO,OAAO,SAAS,KAAK,WAAW,IAAI,WAAW,IAAI,SAAS,CAAC;IACtE,CAAC;CACF"}
@@ -0,0 +1,141 @@
1
+ /**
2
+ * Scanner Controller
3
+ *
4
+ * Manages BLE device scanning with auto-scan functionality.
5
+ * This is a generic controller that can be configured with a device filter.
6
+ */
7
+ /**
8
+ * Controller for BLE device scanning.
9
+ */
10
+ export class ScannerController {
11
+ constructor(adapter, _environment, config, deviceFilter) {
12
+ this.adapter = adapter;
13
+ this._environment = _environment;
14
+ this.config = config;
15
+ this.deviceFilter = deviceFilter;
16
+ this._isScanning = false;
17
+ this._discoveredDevices = [];
18
+ this._lastScanTime = 0;
19
+ this._error = null;
20
+ this._autoScanInterval = null;
21
+ this._listeners = new Set();
22
+ }
23
+ /**
24
+ * Get current scanner state.
25
+ */
26
+ getState() {
27
+ return {
28
+ isScanning: this._isScanning,
29
+ discoveredDevices: this._discoveredDevices,
30
+ lastScanTime: this._lastScanTime,
31
+ error: this._error,
32
+ };
33
+ }
34
+ /**
35
+ * Subscribe to scanner events.
36
+ */
37
+ subscribe(listener) {
38
+ this._listeners.add(listener);
39
+ return () => this._listeners.delete(listener);
40
+ }
41
+ emit(event) {
42
+ this._listeners.forEach((listener) => listener(event));
43
+ }
44
+ /**
45
+ * Scan for devices.
46
+ */
47
+ async scan() {
48
+ // Don't scan if already scanning
49
+ if (this._isScanning) {
50
+ return this._discoveredDevices;
51
+ }
52
+ this._isScanning = true;
53
+ this._error = null;
54
+ this.emit({ type: 'scanStarted' });
55
+ try {
56
+ const devices = await this.adapter.scan(this.config.scanDurationMs / 1000);
57
+ // Apply device filter if provided, otherwise return all devices
58
+ const filteredDevices = this.deviceFilter
59
+ ? this.deviceFilter(devices)
60
+ : devices;
61
+ this._discoveredDevices = filteredDevices;
62
+ this._lastScanTime = Date.now();
63
+ this._isScanning = false;
64
+ this.emit({ type: 'scanCompleted', devices: filteredDevices });
65
+ return filteredDevices;
66
+ }
67
+ catch (e) {
68
+ this._isScanning = false;
69
+ const errorMsg = e instanceof Error ? e.message : String(e);
70
+ // User cancelled device picker (web) - not an error
71
+ if (errorMsg.includes('NotFoundError')) {
72
+ this._error = null;
73
+ this.emit({ type: 'scanCompleted', devices: [] });
74
+ return [];
75
+ }
76
+ // On web, "permission" errors are expected when scan is called without user gesture
77
+ // Don't show these as errors - user just needs to click the scan button
78
+ if (this._environment.requiresUserGesture && errorMsg.includes('permission')) {
79
+ this._error = null;
80
+ this.emit({ type: 'scanCompleted', devices: [] });
81
+ return [];
82
+ }
83
+ // Categorize errors
84
+ if (errorMsg.includes('permission') || errorMsg.includes('Unauthorized')) {
85
+ this._error = 'Bluetooth permission required. Please enable in Settings.';
86
+ }
87
+ else if (errorMsg.includes('Timeout') || errorMsg.includes('PoweredOff')) {
88
+ this._error = 'Please enable Bluetooth on your device.';
89
+ }
90
+ else {
91
+ this._error = `Scan failed: ${errorMsg}`;
92
+ }
93
+ this.emit({ type: 'scanFailed', error: this._error ?? 'Unknown error' });
94
+ return [];
95
+ }
96
+ }
97
+ /**
98
+ * Start auto-scanning.
99
+ * Returns cleanup function.
100
+ */
101
+ startAutoScan(isConnected) {
102
+ // Clear existing intervals
103
+ this.stopAutoScan();
104
+ // Initial scan after short delay
105
+ setTimeout(() => {
106
+ if (!isConnected()) {
107
+ this.scan();
108
+ }
109
+ }, 500);
110
+ // Periodic auto-scan when not connected
111
+ this._autoScanInterval = setInterval(() => {
112
+ if (!this._isScanning && !isConnected()) {
113
+ this.scan();
114
+ }
115
+ }, this.config.scanIntervalMs);
116
+ return () => this.stopAutoScan();
117
+ }
118
+ /**
119
+ * Stop auto-scanning.
120
+ */
121
+ stopAutoScan() {
122
+ if (this._autoScanInterval) {
123
+ clearInterval(this._autoScanInterval);
124
+ this._autoScanInterval = null;
125
+ }
126
+ }
127
+ /**
128
+ * Clear error state.
129
+ */
130
+ clearError() {
131
+ this._error = null;
132
+ }
133
+ /**
134
+ * Cleanup resources.
135
+ */
136
+ dispose() {
137
+ this.stopAutoScan();
138
+ this._listeners.clear();
139
+ }
140
+ }
141
+ //# sourceMappingURL=scanner-controller.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scanner-controller.js","sourceRoot":"","sources":["../../../../src/bluetooth/controllers/scanner-controller.ts"],"names":[],"mappings":"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"}