Skip to content

Commit

Permalink
πŸ‘” (dmk): DeviceSession fix + WebHidTransport reconnection update
Browse files Browse the repository at this point in the history
  • Loading branch information
valpinkman committed Jan 6, 2025
1 parent ff65f9e commit b743f51
Show file tree
Hide file tree
Showing 5 changed files with 50 additions and 17 deletions.
5 changes: 5 additions & 0 deletions .changeset/grumpy-tables-taste.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@ledgerhq/device-transport-kit-web-hid": patch
---

Update WebHidDeviceConnection to throw an error in case of a reconnect
5 changes: 5 additions & 0 deletions .changeset/ten-lions-report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@ledgerhq/device-management-kit": patch
---

Update DeviceSession to change the state of the device in case of an error
Original file line number Diff line number Diff line change
Expand Up @@ -120,13 +120,17 @@ export class DeviceSession {
options.triggersDisconnection,
);

return errorOrResponse.ifRight((response: ApduResponse) => {
if (CommandUtils.isLockedDeviceResponse(response)) {
this.updateDeviceStatus(DeviceStatus.LOCKED);
} else {
return errorOrResponse
.ifRight((response: ApduResponse) => {
if (CommandUtils.isLockedDeviceResponse(response)) {
this.updateDeviceStatus(DeviceStatus.LOCKED);
} else {
this.updateDeviceStatus(DeviceStatus.CONNECTED);
}
})
.ifLeft(() => {
this.updateDeviceStatus(DeviceStatus.CONNECTED);
}
});
});
}

async sendCommand<Response, Args, ErrorStatusCodes>(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
import { Left, Right } from "purify-ts";

import { RECONNECT_DEVICE_TIMEOUT } from "@api/data/WebHidConfig";
import { WebHidSendReportError } from "@api/model/Errors";
import { hidDeviceStubBuilder } from "@api/model/HIDDevice.stub";

import { WebHidDeviceConnection } from "./WebHidDeviceConnection";
Expand Down Expand Up @@ -282,12 +283,7 @@ describe("WebHidDeviceConnection", () => {

const response = await responsePromise;

expect(response).toEqual(
Right({
statusCode: new Uint8Array([0x90, 0x00]),
data: new Uint8Array([]),
}),
);
expect(response).toEqual(Left(new WebHidSendReportError()));
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
type LoggerPublisherService,
ReconnectionFailedError,
} from "@ledgerhq/device-management-kit";
import { type Either, Left, Right } from "purify-ts";
import { type Either, Left, Maybe, Nothing, Right } from "purify-ts";
import { Subject } from "rxjs";

import { RECONNECT_DEVICE_TIMEOUT } from "@api/data/WebHidConfig";
Expand All @@ -35,6 +35,7 @@ export class WebHidDeviceConnection implements DeviceConnection {
private readonly _apduReceiver: ApduReceiverService;
private _sendApduSubject: Subject<ApduResponse> = new Subject();
private readonly _logger: LoggerPublisherService;
private _pendingApdu: Maybe<Uint8Array> = Nothing;

/** Callback to notify the connection termination */
private _onConnectionTerminated: () => void;
Expand Down Expand Up @@ -80,7 +81,7 @@ export class WebHidDeviceConnection implements DeviceConnection {
triggersDisconnection?: boolean,
): Promise<Either<DmkError, ApduResponse>> {
this._sendApduSubject = new Subject();

this._pendingApdu = Maybe.of(apdu);
this._logger.debug("Sending APDU", {
data: { apdu },
tag: "apdu-sender",
Expand All @@ -90,6 +91,7 @@ export class WebHidDeviceConnection implements DeviceConnection {
(resolve) => {
this._sendApduSubject.subscribe({
next: async (r) => {
this._pendingApdu = Nothing;
if (triggersDisconnection && CommandUtils.isSuccessResponse(r)) {
// Anticipate the disconnection and wait for the reconnection before resolving
const reconnectionRes = await this.waitForReconnection();
Expand All @@ -102,14 +104,21 @@ export class WebHidDeviceConnection implements DeviceConnection {
}
},
error: (err) => {
this._pendingApdu = Nothing;
resolve(Left(err));
},
});
},
);

if (this.waitingForReconnection || !this.device.opened) {
const reconnectionRes = await this.waitForReconnection();
const waitingForDeviceResponse =
this.device.opened && this._pendingApdu.isJust();

const reconnectionRes = await this.waitForReconnection(
waitingForDeviceResponse,
);

if (reconnectionRes.isLeft()) {
return reconnectionRes;
}
Expand Down Expand Up @@ -154,12 +163,20 @@ export class WebHidDeviceConnection implements DeviceConnection {
});
}

private waitForReconnection(): Promise<Either<DmkError, void>> {
if (this.terminated)
private waitForReconnection(
waitingForDeviceResponse: boolean = false,
): Promise<Either<DmkError, void>> {
if (this.terminated) {
return Promise.resolve(Left(new ReconnectionFailedError()));
}

return new Promise<Either<DmkError, void>>((resolve) => {
const sub = this.reconnectionSubject.subscribe({
next: (res) => {
if (waitingForDeviceResponse) {
this._sendApduSubject.error(new WebHidSendReportError());
}

if (res === "success") {
resolve(Right(undefined));
} else {
Expand Down Expand Up @@ -193,7 +210,13 @@ export class WebHidDeviceConnection implements DeviceConnection {
this._logger.info("β±οΈπŸ”Œ Device reconnected");
clearTimeout(this.lostConnectionTimeout);
}

await device.open();

if (this._pendingApdu.isJust()) {
this._sendApduSubject.error(new WebHidSendReportError());
}

this.waitingForReconnection = false;
this.reconnectionSubject.next("success");
}
Expand Down

0 comments on commit b743f51

Please sign in to comment.