@@ -43,9 +43,14 @@ class WebBluetooth extends EventTarget {
43
43
44
44
this . bluetooth . addEventListener ( "connect" , ( e ) => this . handleNewDevice ( e . target ) ) ;
45
45
this . bluetooth . addEventListener ( "disconnect" , ( e ) => this . handleRemovedDevice ( e . target ) ) ;
46
- this . bluetooth . addEventListener ( "gatserverdisconnected " , ( e ) => this . handleRemovedDevice ( e . target ) ) ;
46
+ this . bluetooth . addEventListener ( "gattserverdisconnected " , ( e ) => this . handleRemovedDevice ( e . target ) ) ;
47
47
48
48
this . loadDevices ( ) ;
49
+
50
+ // Properly bind all event handlers ONCE
51
+ this . boundHandleDisconnect = this . handleDisconnect . bind ( this ) ;
52
+ this . boundHandleNotification = this . handleNotification . bind ( this ) ;
53
+ this . boundHandleReceiveBytes = this . handleReceiveBytes . bind ( this ) ;
49
54
}
50
55
51
56
handleNewDevice ( device ) {
@@ -135,26 +140,26 @@ class WebBluetooth extends EventTarget {
135
140
136
141
console . log ( `${ this . logHead } Opening connection with ID: ${ path } , Baud: ${ options . baudRate } ` ) ;
137
142
138
- this . device . addEventListener ( "gattserverdisconnected" , this . handleDisconnect . bind ( this ) ) ;
143
+ // Use bound method references
144
+ this . device . addEventListener ( "gattserverdisconnected" , this . boundHandleDisconnect ) ;
139
145
140
146
try {
141
147
console . log ( `${ this . logHead } Connecting to GATT Server` ) ;
142
148
143
149
await this . gattConnect ( ) ;
144
150
151
+ // Check if the GATT connection was successful before proceeding
152
+ if ( ! this . device . gatt ?. connected ) {
153
+ throw new Error ( "GATT server connection failed" ) ;
154
+ }
155
+
145
156
gui_log ( i18n . getMessage ( "bluetoothConnected" , [ this . device . name ] ) ) ;
146
157
147
158
await this . getServices ( ) ;
148
159
await this . getCharacteristics ( ) ;
149
160
await this . startNotifications ( ) ;
150
- } catch ( error ) {
151
- gui_log ( i18n . getMessage ( "bluetoothConnectionError" , [ error ] ) ) ;
152
- }
153
-
154
- // Bluetooth API doesn't provide a way for getInfo() or similar to get the connection info
155
- const connectionInfo = this . device . gatt . connected ;
156
161
157
- if ( connectionInfo && ! this . openCanceled ) {
162
+ // Connection is fully established only after all setup completes successfully
158
163
this . connected = true ;
159
164
this . connectionId = this . device . port ;
160
165
this . bitrate = options . baudRate ;
@@ -163,35 +168,25 @@ class WebBluetooth extends EventTarget {
163
168
this . failed = 0 ;
164
169
this . openRequested = false ;
165
170
166
- this . device . addEventListener ( "disconnect" , this . handleDisconnect . bind ( this ) ) ;
167
- this . addEventListener ( "receive" , this . handleReceiveBytes ) ;
171
+ // Use bound references here too
172
+ this . device . addEventListener ( "disconnect" , this . boundHandleDisconnect ) ;
173
+ this . addEventListener ( "receive" , this . boundHandleReceiveBytes ) ;
168
174
169
175
console . log ( `${ this . logHead } Connection opened with ID: ${ this . connectionId } , Baud: ${ options . baudRate } ` ) ;
170
176
171
- this . dispatchEvent ( new CustomEvent ( "connect" , { detail : connectionInfo } ) ) ;
172
- } else if ( connectionInfo && this . openCanceled ) {
173
- this . connectionId = this . device . port ;
177
+ this . dispatchEvent ( new CustomEvent ( "connect" , { detail : true } ) ) ;
178
+ return true ;
179
+ } catch ( error ) {
180
+ console . error ( `${ this . logHead } Connection error:` , error ) ;
181
+ gui_log ( i18n . getMessage ( "bluetoothConnectionError" , [ error ] ) ) ;
174
182
175
- console . log (
176
- `${ this . logHead } Connection opened with ID: ${ connectionInfo . connectionId } , but request was canceled, disconnecting` ,
177
- ) ;
178
- // some bluetooth dongles/dongle drivers really doesn't like to be closed instantly, adding a small delay
179
- setTimeout ( ( ) => {
180
- this . openRequested = false ;
181
- this . openCanceled = false ;
182
- this . disconnect ( ( ) => {
183
- this . dispatchEvent ( new CustomEvent ( "connect" , { detail : false } ) ) ;
184
- } ) ;
185
- } , 150 ) ;
186
- } else if ( this . openCanceled ) {
187
- console . log ( `${ this . logHead } Connection didn't open and request was canceled` ) ;
183
+ // Clean up any partial connection state
188
184
this . openRequested = false ;
189
185
this . openCanceled = false ;
186
+
187
+ // Signal connection failure
190
188
this . dispatchEvent ( new CustomEvent ( "connect" , { detail : false } ) ) ;
191
- } else {
192
- this . openRequested = false ;
193
- console . log ( `${ this . logHead } Failed to open bluetooth port` ) ;
194
- this . dispatchEvent ( new CustomEvent ( "connect" , { detail : false } ) ) ;
189
+ return false ;
195
190
}
196
191
}
197
192
@@ -221,47 +216,84 @@ class WebBluetooth extends EventTarget {
221
216
}
222
217
223
218
async getCharacteristics ( ) {
224
- const characteristics = await this . service . getCharacteristics ( ) ;
219
+ try {
220
+ const characteristics = await this . service . getCharacteristics ( ) ;
225
221
226
- characteristics . forEach ( ( characteristic ) => {
227
- // console.log("Characteristic: ", characteristic);
228
- if ( characteristic . uuid == this . deviceDescription . writeCharacteristic ) {
229
- this . writeCharacteristic = characteristic ;
222
+ if ( ! characteristics || characteristics . length === 0 ) {
223
+ throw new Error ( "No characteristics found" ) ;
230
224
}
231
225
232
- if ( characteristic . uuid == this . deviceDescription . readCharacteristic ) {
233
- this . readCharacteristic = characteristic ;
226
+ // Reset characteristics
227
+ this . writeCharacteristic = null ;
228
+ this . readCharacteristic = null ;
229
+
230
+ // Collect all matching characteristics first without breaking early
231
+ const writeMatches = [ ] ;
232
+ const readMatches = [ ] ;
233
+
234
+ for ( const characteristic of characteristics ) {
235
+ if ( characteristic . uuid === this . deviceDescription . writeCharacteristic ) {
236
+ writeMatches . push ( characteristic ) ;
237
+ }
238
+
239
+ if ( characteristic . uuid === this . deviceDescription . readCharacteristic ) {
240
+ readMatches . push ( characteristic ) ;
241
+ }
234
242
}
235
- return this . writeCharacteristic && this . readCharacteristic ;
236
- } ) ;
237
243
238
- if ( ! this . writeCharacteristic ) {
239
- throw new Error (
240
- "Unexpected write characteristic found - should be" ,
241
- this . deviceDescription . writeCharacteristic ,
242
- ) ;
243
- }
244
+ // Select the first match of each type
245
+ if ( writeMatches . length > 0 ) {
246
+ this . writeCharacteristic = writeMatches [ 0 ] ;
247
+ if ( writeMatches . length > 1 ) {
248
+ console . warn ( `${ this . logHead } Multiple write characteristics found, using first match` ) ;
249
+ }
250
+ }
244
251
245
- if ( ! this . readCharacteristic ) {
246
- throw new Error (
247
- "Unexpected read characteristic found - should be" ,
248
- this . deviceDescription . readCharacteristic ,
249
- ) ;
250
- }
252
+ if ( readMatches . length > 0 ) {
253
+ this . readCharacteristic = readMatches [ 0 ] ;
254
+ if ( readMatches . length > 1 ) {
255
+ console . warn ( ` ${ this . logHead } Multiple read characteristics found, using first match` ) ;
256
+ }
257
+ }
251
258
252
- this . readCharacteristic . addEventListener ( "characteristicvaluechanged" , this . handleNotification . bind ( this ) ) ;
259
+ if ( ! this . writeCharacteristic ) {
260
+ throw new Error ( `Write characteristic not found: ${ this . deviceDescription . writeCharacteristic } ` ) ;
261
+ }
253
262
254
- return await this . readCharacteristic . readValue ( ) ;
263
+ if ( ! this . readCharacteristic ) {
264
+ throw new Error ( `Read characteristic not found: ${ this . deviceDescription . readCharacteristic } ` ) ;
265
+ }
266
+
267
+ // Use the bound method for the event listener
268
+ this . readCharacteristic . addEventListener ( "characteristicvaluechanged" , this . boundHandleNotification ) ;
269
+
270
+ return await this . readCharacteristic . readValue ( ) ;
271
+ } catch ( error ) {
272
+ console . error ( `${ this . logHead } Error getting characteristics:` , error ) ;
273
+ throw error ;
274
+ }
255
275
}
256
276
257
277
handleNotification ( event ) {
258
- const buffer = new Uint8Array ( event . target . value . byteLength ) ;
278
+ try {
279
+ if ( ! event . target . value ) {
280
+ console . warn ( `${ this . logHead } Empty notification received` ) ;
281
+ return ;
282
+ }
259
283
260
- for ( let i = 0 ; i < event . target . value . byteLength ; i ++ ) {
261
- buffer [ i ] = event . target . value . getUint8 ( i ) ;
262
- }
284
+ const buffer = new Uint8Array ( event . target . value . byteLength ) ;
285
+
286
+ // Copy data with validation
287
+ for ( let i = 0 ; i < event . target . value . byteLength ; i ++ ) {
288
+ buffer [ i ] = event . target . value . getUint8 ( i ) ;
289
+ }
263
290
264
- this . dispatchEvent ( new CustomEvent ( "receive" , { detail : buffer } ) ) ;
291
+ if ( buffer . length ) {
292
+ this . dispatchEvent ( new CustomEvent ( "receive" , { detail : buffer } ) ) ;
293
+ }
294
+ } catch ( error ) {
295
+ console . error ( `${ this . logHead } Error handling notification:` , error ) ;
296
+ }
265
297
}
266
298
267
299
startNotifications ( ) {
@@ -287,48 +319,51 @@ class WebBluetooth extends EventTarget {
287
319
return ;
288
320
}
289
321
290
- const doCleanup = async ( ) => {
291
- this . removeEventListener ( "receive" , this . handleReceiveBytes ) ;
322
+ this . closeRequested = true ; // Set this to prevent reentry
323
+
324
+ try {
325
+ this . removeEventListener ( "receive" , this . boundHandleReceiveBytes ) ;
292
326
293
327
if ( this . device ) {
294
- this . device . removeEventListener ( "disconnect" , this . handleDisconnect . bind ( this ) ) ;
295
- this . device . removeEventListener ( "gattserverdisconnected" , this . handleDisconnect ) ;
296
- this . readCharacteristic . removeEventListener (
297
- "characteristicvaluechanged" ,
298
- this . handleNotification . bind ( this ) ,
299
- ) ;
300
-
301
- if ( this . device . gatt . connected ) {
302
- this . device . gatt . disconnect ( ) ;
328
+ // Use the properly bound references
329
+ this . device . removeEventListener ( "disconnect" , this . boundHandleDisconnect ) ;
330
+ this . device . removeEventListener ( "gattserverdisconnected" , this . boundHandleDisconnect ) ;
331
+
332
+ if ( this . readCharacteristic ) {
333
+ try {
334
+ // Stop notifications first to avoid errors
335
+ await this . readCharacteristic . stopNotifications ( ) ;
336
+ this . readCharacteristic . removeEventListener (
337
+ "characteristicvaluechanged" ,
338
+ this . boundHandleNotification ,
339
+ ) ;
340
+ } catch ( err ) {
341
+ console . warn ( `${ this . logHead } Error stopping notifications:` , err ) ;
342
+ }
303
343
}
304
344
305
- this . writeCharacteristic = false ;
306
- this . readCharacteristic = false ;
307
- this . deviceDescription = false ;
308
- this . device = null ;
309
- }
310
- } ;
311
-
312
- try {
313
- await doCleanup ( ) ;
345
+ // Safely disconnect GATT
346
+ if ( this . device . gatt ?. connected ) {
347
+ await this . device . gatt . disconnect ( ) ;
348
+ }
314
349
315
- console . log (
316
- `${ this . logHead } Connection with ID: ${ this . connectionId } closed, Sent: ${ this . bytesSent } bytes, Received: ${ this . bytesReceived } bytes` ,
317
- ) ;
350
+ // Clear references
351
+ this . writeCharacteristic = null ;
352
+ this . readCharacteristic = null ;
353
+ this . deviceDescription = null ;
354
+ }
318
355
356
+ console . log ( `${ this . logHead } Connection closed successfully` ) ;
319
357
this . connectionId = false ;
320
358
this . bitrate = 0 ;
359
+ this . device = null ;
321
360
this . dispatchEvent ( new CustomEvent ( "disconnect" , { detail : true } ) ) ;
322
361
} catch ( error ) {
323
- console . error ( error ) ;
324
- console . error (
325
- `${ this . logHead } Failed to close connection with ID: ${ this . connectionId } closed, Sent: ${ this . bytesSent } bytes, Received: ${ this . bytesReceived } bytes` ,
326
- ) ;
362
+ console . error ( `${ this . logHead } Error during disconnect:` , error ) ;
327
363
this . dispatchEvent ( new CustomEvent ( "disconnect" , { detail : false } ) ) ;
328
364
} finally {
329
- if ( this . openCanceled ) {
330
- this . openCanceled = false ;
331
- }
365
+ this . closeRequested = false ;
366
+ this . openCanceled = false ;
332
367
}
333
368
}
334
369
0 commit comments