Skip to content

Commit 32affcd

Browse files
Apollon77claude
andauthored
fix(ble): silence noble disconnect-race rejections during unsubscribe (#3670)
When the BLE peripheral disconnects while a C2 unsubscribe (or final disconnect) call is in flight, noble rejects the pending operation with "Disconnected <N>". The previous code logged this as a real error even though it is the expected outcome of a normal disconnect race and the unsubscribe is moot once the link is gone. - Handshake-timeout path now skips the unsubscribe entirely when the peripheral is no longer connected, and treats noble disconnect errors as silent. - BTP-close-callback unsubscribe and the close() disconnect catch use the same isNobleDisconnectError filter so genuine errors still surface while race-rejections do not. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 0187601 commit 32affcd

1 file changed

Lines changed: 17 additions & 14 deletions

File tree

packages/nodejs-ble/src/NobleBleChannel.ts

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -502,9 +502,13 @@ export class NobleBleChannel extends BleChannel<Bytes> {
502502
const btpHandshakeTimeout = Time.getTimer("BLE handshake timeout", MatterBle.BTP_CONN_RSP_TIMEOUT, async () => {
503503
characteristicC2ForSubscribe.removeListener("data", handshakeHandler);
504504

505-
await characteristicC2ForSubscribe
506-
.unsubscribeAsync()
507-
.catch(error => logger.error(`Peripheral ${peripheralAddress}: Error while unsubscribing`, error));
505+
if (peripheral.state === "connected") {
506+
await characteristicC2ForSubscribe.unsubscribeAsync().catch(error => {
507+
if (!isNobleDisconnectError(error)) {
508+
logger.error(`Peripheral ${peripheralAddress}: Error while unsubscribing`, error);
509+
}
510+
});
511+
}
508512

509513
logger.debug(
510514
`Peripheral ${peripheralAddress}: Handshake Response not received. Disconnect from peripheral`,
@@ -560,14 +564,13 @@ export class NobleBleChannel extends BleChannel<Bytes> {
560564
async () => {
561565
if (peripheral.state !== "connected" || !nobleChannel.connected) return;
562566
logger.debug(`Peripheral ${peripheralAddress}: Disconnect from peripheral because btp session closed`);
563-
// Unsubscribe from C2 notifications, then disconnect. If unsubscribe fails (e.g. the
564-
// peripheral already started disconnecting and Noble's _withDisconnectHandler rejected the
565-
// pending operation with "Disconnected unknown"), proceed to disconnectAsync anyway.
566567
characteristicC2ForSubscribe
567568
.unsubscribeAsync()
568-
.catch(error =>
569-
logger.debug(`Peripheral ${peripheralAddress}: Error while unsubscribing from C2`, error),
570-
)
569+
.catch(error => {
570+
if (!isNobleDisconnectError(error)) {
571+
logger.debug(`Peripheral ${peripheralAddress}: Error while unsubscribing from C2`, error);
572+
}
573+
})
571574
.then(() => {
572575
if (peripheral.state !== "connected") {
573576
return;
@@ -663,11 +666,11 @@ export class NobleBleChannel extends BleChannel<Bytes> {
663666
this.#cleanupDataListener();
664667
await this.btpSession.close();
665668
if (this.connected) {
666-
this.peripheral
667-
.disconnectAsync()
668-
.catch(error =>
669-
logger.error(`Peripheral ${this.peripheral.address}: Error while disconnecting`, error),
670-
);
669+
this.peripheral.disconnectAsync().catch(error => {
670+
if (!isNobleDisconnectError(error)) {
671+
logger.error(`Peripheral ${this.peripheral.address}: Error while disconnecting`, error);
672+
}
673+
});
671674
}
672675
this.emitClosed();
673676
}

0 commit comments

Comments
 (0)