@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.
- package/LICENSE +21 -0
- package/README.md +272 -0
- package/dist/cjs/bluetooth/adapters/base.js +116 -0
- package/dist/cjs/bluetooth/adapters/base.js.map +1 -0
- package/dist/cjs/bluetooth/adapters/index.js +58 -0
- package/dist/cjs/bluetooth/adapters/index.js.map +1 -0
- package/dist/cjs/bluetooth/adapters/native.js +473 -0
- package/dist/cjs/bluetooth/adapters/native.js.map +1 -0
- package/dist/cjs/bluetooth/adapters/node.js +228 -0
- package/dist/cjs/bluetooth/adapters/node.js.map +1 -0
- package/dist/cjs/bluetooth/adapters/types.js +11 -0
- package/dist/cjs/bluetooth/adapters/types.js.map +1 -0
- package/dist/cjs/bluetooth/adapters/web-bluetooth-base.js +187 -0
- package/dist/cjs/bluetooth/adapters/web-bluetooth-base.js.map +1 -0
- package/dist/cjs/bluetooth/adapters/web.js +112 -0
- package/dist/cjs/bluetooth/adapters/web.js.map +1 -0
- package/dist/cjs/bluetooth/controllers/scanner-controller.js +145 -0
- package/dist/cjs/bluetooth/controllers/scanner-controller.js.map +1 -0
- package/dist/cjs/bluetooth/index.js +27 -0
- package/dist/cjs/bluetooth/index.js.map +1 -0
- package/dist/cjs/bluetooth/models/connection.js +68 -0
- package/dist/cjs/bluetooth/models/connection.js.map +1 -0
- package/dist/cjs/bluetooth/models/device.js +26 -0
- package/dist/cjs/bluetooth/models/device.js.map +1 -0
- package/dist/cjs/bluetooth/models/environment.js +106 -0
- package/dist/cjs/bluetooth/models/environment.js.map +1 -0
- package/dist/cjs/errors.js +167 -0
- package/dist/cjs/errors.js.map +1 -0
- package/dist/cjs/index.js +116 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/react/hooks.js +262 -0
- package/dist/cjs/react/hooks.js.map +1 -0
- package/dist/cjs/react/index.js +18 -0
- package/dist/cjs/react/index.js.map +1 -0
- package/dist/cjs/sdk/index.js +14 -0
- package/dist/cjs/sdk/index.js.map +1 -0
- package/dist/cjs/sdk/types.js +8 -0
- package/dist/cjs/sdk/types.js.map +1 -0
- package/dist/cjs/sdk/voltra-client.js +632 -0
- package/dist/cjs/sdk/voltra-client.js.map +1 -0
- package/dist/cjs/sdk/voltra-manager.js +419 -0
- package/dist/cjs/sdk/voltra-manager.js.map +1 -0
- package/dist/cjs/shared/index.js +12 -0
- package/dist/cjs/shared/index.js.map +1 -0
- package/dist/cjs/shared/utils.js +51 -0
- package/dist/cjs/shared/utils.js.map +1 -0
- package/dist/cjs/voltra/index.js +56 -0
- package/dist/cjs/voltra/index.js.map +1 -0
- package/dist/cjs/voltra/models/connection.js +68 -0
- package/dist/cjs/voltra/models/connection.js.map +1 -0
- package/dist/cjs/voltra/models/device-filter.js +28 -0
- package/dist/cjs/voltra/models/device-filter.js.map +1 -0
- package/dist/cjs/voltra/models/device.js +152 -0
- package/dist/cjs/voltra/models/device.js.map +1 -0
- package/dist/cjs/voltra/models/telemetry/frame.js +46 -0
- package/dist/cjs/voltra/models/telemetry/frame.js.map +1 -0
- package/dist/cjs/voltra/models/telemetry/index.js +14 -0
- package/dist/cjs/voltra/models/telemetry/index.js.map +1 -0
- package/dist/cjs/voltra/protocol/commands.js +230 -0
- package/dist/cjs/voltra/protocol/commands.js.map +1 -0
- package/dist/cjs/voltra/protocol/constants.js +136 -0
- package/dist/cjs/voltra/protocol/constants.js.map +1 -0
- package/dist/cjs/voltra/protocol/data/chains.json +830 -0
- package/dist/cjs/voltra/protocol/data/eccentric.json +1598 -0
- package/dist/cjs/voltra/protocol/data/protocol.json +54 -0
- package/dist/cjs/voltra/protocol/data/weights.json +62 -0
- package/dist/cjs/voltra/protocol/index.js +25 -0
- package/dist/cjs/voltra/protocol/index.js.map +1 -0
- package/dist/cjs/voltra/protocol/telemetry-decoder.js +146 -0
- package/dist/cjs/voltra/protocol/telemetry-decoder.js.map +1 -0
- package/dist/esm/bluetooth/adapters/base.js +112 -0
- package/dist/esm/bluetooth/adapters/base.js.map +1 -0
- package/dist/esm/bluetooth/adapters/index.js +51 -0
- package/dist/esm/bluetooth/adapters/index.js.map +1 -0
- package/dist/esm/bluetooth/adapters/native.js +469 -0
- package/dist/esm/bluetooth/adapters/native.js.map +1 -0
- package/dist/esm/bluetooth/adapters/node.js +191 -0
- package/dist/esm/bluetooth/adapters/node.js.map +1 -0
- package/dist/esm/bluetooth/adapters/types.js +10 -0
- package/dist/esm/bluetooth/adapters/types.js.map +1 -0
- package/dist/esm/bluetooth/adapters/web-bluetooth-base.js +183 -0
- package/dist/esm/bluetooth/adapters/web-bluetooth-base.js.map +1 -0
- package/dist/esm/bluetooth/adapters/web.js +108 -0
- package/dist/esm/bluetooth/adapters/web.js.map +1 -0
- package/dist/esm/bluetooth/controllers/scanner-controller.js +141 -0
- package/dist/esm/bluetooth/controllers/scanner-controller.js.map +1 -0
- package/dist/esm/bluetooth/index.js +17 -0
- package/dist/esm/bluetooth/index.js.map +1 -0
- package/dist/esm/bluetooth/models/connection.js +63 -0
- package/dist/esm/bluetooth/models/connection.js.map +1 -0
- package/dist/esm/bluetooth/models/device.js +22 -0
- package/dist/esm/bluetooth/models/device.js.map +1 -0
- package/dist/esm/bluetooth/models/environment.js +101 -0
- package/dist/esm/bluetooth/models/environment.js.map +1 -0
- package/dist/esm/errors.js +155 -0
- package/dist/esm/errors.js.map +1 -0
- package/dist/esm/index.js +72 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/react/hooks.js +257 -0
- package/dist/esm/react/hooks.js.map +1 -0
- package/dist/esm/react/index.js +12 -0
- package/dist/esm/react/index.js.map +1 -0
- package/dist/esm/sdk/index.js +9 -0
- package/dist/esm/sdk/index.js.map +1 -0
- package/dist/esm/sdk/types.js +7 -0
- package/dist/esm/sdk/types.js.map +1 -0
- package/dist/esm/sdk/voltra-client.js +628 -0
- package/dist/esm/sdk/voltra-client.js.map +1 -0
- package/dist/esm/sdk/voltra-manager.js +415 -0
- package/dist/esm/sdk/voltra-manager.js.map +1 -0
- package/dist/esm/shared/index.js +5 -0
- package/dist/esm/shared/index.js.map +1 -0
- package/dist/esm/shared/utils.js +45 -0
- package/dist/esm/shared/utils.js.map +1 -0
- package/dist/esm/voltra/index.js +26 -0
- package/dist/esm/voltra/index.js.map +1 -0
- package/dist/esm/voltra/models/connection.js +63 -0
- package/dist/esm/voltra/models/connection.js.map +1 -0
- package/dist/esm/voltra/models/device-filter.js +23 -0
- package/dist/esm/voltra/models/device-filter.js.map +1 -0
- package/dist/esm/voltra/models/device.js +148 -0
- package/dist/esm/voltra/models/device.js.map +1 -0
- package/dist/esm/voltra/models/telemetry/frame.js +40 -0
- package/dist/esm/voltra/models/telemetry/frame.js.map +1 -0
- package/dist/esm/voltra/models/telemetry/index.js +7 -0
- package/dist/esm/voltra/models/telemetry/index.js.map +1 -0
- package/dist/esm/voltra/protocol/commands.js +224 -0
- package/dist/esm/voltra/protocol/commands.js.map +1 -0
- package/dist/esm/voltra/protocol/constants.js +130 -0
- package/dist/esm/voltra/protocol/constants.js.map +1 -0
- package/dist/esm/voltra/protocol/data/chains.json +830 -0
- package/dist/esm/voltra/protocol/data/eccentric.json +1598 -0
- package/dist/esm/voltra/protocol/data/protocol.json +54 -0
- package/dist/esm/voltra/protocol/data/weights.json +62 -0
- package/dist/esm/voltra/protocol/index.js +9 -0
- package/dist/esm/voltra/protocol/index.js.map +1 -0
- package/dist/esm/voltra/protocol/telemetry-decoder.js +140 -0
- package/dist/esm/voltra/protocol/telemetry-decoder.js.map +1 -0
- package/dist/types/bluetooth/adapters/base.d.ts +85 -0
- package/dist/types/bluetooth/adapters/base.d.ts.map +1 -0
- package/dist/types/bluetooth/adapters/index.d.ts +35 -0
- package/dist/types/bluetooth/adapters/index.d.ts.map +1 -0
- package/dist/types/bluetooth/adapters/native.d.ts +109 -0
- package/dist/types/bluetooth/adapters/native.d.ts.map +1 -0
- package/dist/types/bluetooth/adapters/node.d.ts +91 -0
- package/dist/types/bluetooth/adapters/node.d.ts.map +1 -0
- package/dist/types/bluetooth/adapters/types.d.ts +102 -0
- package/dist/types/bluetooth/adapters/types.d.ts.map +1 -0
- package/dist/types/bluetooth/adapters/web-bluetooth-base.d.ts +90 -0
- package/dist/types/bluetooth/adapters/web-bluetooth-base.d.ts.map +1 -0
- package/dist/types/bluetooth/adapters/web.d.ts +57 -0
- package/dist/types/bluetooth/adapters/web.d.ts.map +1 -0
- package/dist/types/bluetooth/controllers/scanner-controller.d.ts +93 -0
- package/dist/types/bluetooth/controllers/scanner-controller.d.ts.map +1 -0
- package/dist/types/bluetooth/index.d.ts +14 -0
- package/dist/types/bluetooth/index.d.ts.map +1 -0
- package/dist/types/bluetooth/models/connection.d.ts +37 -0
- package/dist/types/bluetooth/models/connection.d.ts.map +1 -0
- package/dist/types/bluetooth/models/device.d.ts +25 -0
- package/dist/types/bluetooth/models/device.d.ts.map +1 -0
- package/dist/types/bluetooth/models/environment.d.ts +45 -0
- package/dist/types/bluetooth/models/environment.d.ts.map +1 -0
- package/dist/types/errors.d.ts +113 -0
- package/dist/types/errors.d.ts.map +1 -0
- package/dist/types/index.d.ts +55 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/react/hooks.d.ts +130 -0
- package/dist/types/react/hooks.d.ts.map +1 -0
- package/dist/types/react/index.d.ts +12 -0
- package/dist/types/react/index.d.ts.map +1 -0
- package/dist/types/sdk/index.d.ts +11 -0
- package/dist/types/sdk/index.d.ts.map +1 -0
- package/dist/types/sdk/types.d.ts +104 -0
- package/dist/types/sdk/types.d.ts.map +1 -0
- package/dist/types/sdk/voltra-client.d.ts +221 -0
- package/dist/types/sdk/voltra-client.d.ts.map +1 -0
- package/dist/types/sdk/voltra-manager.d.ts +226 -0
- package/dist/types/sdk/voltra-manager.d.ts.map +1 -0
- package/dist/types/shared/index.d.ts +5 -0
- package/dist/types/shared/index.d.ts.map +1 -0
- package/dist/types/shared/utils.d.ts +25 -0
- package/dist/types/shared/utils.d.ts.map +1 -0
- package/dist/types/voltra/index.d.ts +13 -0
- package/dist/types/voltra/index.d.ts.map +1 -0
- package/dist/types/voltra/models/connection.d.ts +37 -0
- package/dist/types/voltra/models/connection.d.ts.map +1 -0
- package/dist/types/voltra/models/device-filter.d.ts +19 -0
- package/dist/types/voltra/models/device-filter.d.ts.map +1 -0
- package/dist/types/voltra/models/device.d.ts +105 -0
- package/dist/types/voltra/models/device.d.ts.map +1 -0
- package/dist/types/voltra/models/telemetry/frame.d.ts +41 -0
- package/dist/types/voltra/models/telemetry/frame.d.ts.map +1 -0
- package/dist/types/voltra/models/telemetry/index.d.ts +8 -0
- package/dist/types/voltra/models/telemetry/index.d.ts.map +1 -0
- package/dist/types/voltra/protocol/commands.d.ts +99 -0
- package/dist/types/voltra/protocol/commands.d.ts.map +1 -0
- package/dist/types/voltra/protocol/constants.d.ts +103 -0
- package/dist/types/voltra/protocol/constants.d.ts.map +1 -0
- package/dist/types/voltra/protocol/index.d.ts +9 -0
- package/dist/types/voltra/protocol/index.d.ts.map +1 -0
- package/dist/types/voltra/protocol/telemetry-decoder.d.ts +45 -0
- package/dist/types/voltra/protocol/telemetry-decoder.d.ts.map +1 -0
- 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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"}
|