diff --git a/.changeset/spicy-dogs-sneeze.md b/.changeset/spicy-dogs-sneeze.md new file mode 100644 index 000000000..b85bf8049 --- /dev/null +++ b/.changeset/spicy-dogs-sneeze.md @@ -0,0 +1,5 @@ +--- +"@ledgerhq/device-sdk-core": patch +--- + +Add TSDoc comments for things exposed through API diff --git a/packages/core/src/api/DeviceSdk.ts b/packages/core/src/api/DeviceSdk.ts index 60f0a53c9..d2d585df7 100644 --- a/packages/core/src/api/DeviceSdk.ts +++ b/packages/core/src/api/DeviceSdk.ts @@ -38,6 +38,11 @@ import { } from "@internal/usb/use-case/GetConnectedDeviceUseCase"; import { makeContainer, MakeContainerProps } from "@root/src/di"; +/** + * The main class to interact with the SDK. + * + * NB: do not instantiate this class directly, instead, use `DeviceSdkBuilder`. + */ export class DeviceSdk { container: Container; /** @internal */ @@ -48,54 +53,107 @@ export class DeviceSdk { this.container = makeContainer({ stub, loggers }); } + /** + * Returns a promise resolving to the version of the SDK. + */ getVersion(): Promise { return this.container .get(configTypes.GetSdkVersionUseCase) .getSdkVersion(); } + /** + * Starts discovering devices connected via USB HID (BLE not implemented yet). + * + * For the WeHID implementation, this use-case needs to be called as a result + * of an user interaction (button "click" event for ex). + * + * @returns {Observable} An observable of discovered devices. + */ startDiscovering(): Observable { return this.container .get(discoveryTypes.StartDiscoveringUseCase) .execute(); } + /** + * Stops discovering devices connected via USB HID (and later BLE). + */ stopDiscovering() { return this.container .get(discoveryTypes.StopDiscoveringUseCase) .execute(); } + /** + * Connects to a device previously discovered with `DeviceSdk.startDiscovering`. + * Creates a new device session which: + * - Represents the connection to the device. + * - Is terminated upon disconnection of the device. + * - Exposes the device state through an observable (see `DeviceSdk.getDeviceSessionState`) + * - Should be used for all subsequent communication with the device. + * + * @param {ConnectUseCaseArgs} args - The device ID (obtained in discovery) to connect to. + * @returns The session ID to use for further communication with the device. + */ connect(args: ConnectUseCaseArgs): Promise { return this.container .get(discoveryTypes.ConnectUseCase) .execute(args); } + /** + * Disconnects to a discovered device via USB HID (and later BLE). + * + * @param {DisconnectUseCaseArgs} args - The session ID to disconnect. + */ disconnect(args: DisconnectUseCaseArgs): Promise { return this.container .get(discoveryTypes.DisconnectUseCase) .execute(args); } + /** + * Sends an APDU command to a device through a device session. + * + * @param {SendApduUseCaseArgs} args - The device session ID and APDU command to send. + */ sendApdu(args: SendApduUseCaseArgs): Promise { return this.container .get(sendTypes.SendApduUseCase) .execute(args); } + /** + * Sends a command to a device through a device session. + * + * @param {SendCommandUseCaseArgs} - The device session ID, command and command parameters to send. + * @returns A promise resolving with the response from the command. + */ sendCommand(args: SendCommandUseCaseArgs): Promise { return this.container .get(commandTypes.SendCommandUseCase) .execute(args); } + /** + * Gets the connected from its device session ID. + * + * @param {GetConnectedDeviceUseCaseArgs} args - The device session ID. + * @returns {ConnectedDevice} The connected device. + */ getConnectedDevice(args: GetConnectedDeviceUseCaseArgs): ConnectedDevice { return this.container .get(usbDiTypes.GetConnectedDeviceUseCase) .execute(args); } + /** + * Gets the device state of a session. + * + * @param {{DeviceSessionId}} args - The device session ID. + * @returns {Observable} An observable of the session device state. + */ getDeviceSessionState(args: { sessionId: DeviceSessionId; }): Observable { diff --git a/packages/core/src/api/DeviceSdkBuilder.ts b/packages/core/src/api/DeviceSdkBuilder.ts index f308b3e68..7bc59000c 100644 --- a/packages/core/src/api/DeviceSdkBuilder.ts +++ b/packages/core/src/api/DeviceSdkBuilder.ts @@ -1,6 +1,17 @@ import { LoggerSubscriberService } from "./logger-subscriber/service/LoggerSubscriberService"; import { DeviceSdk } from "./DeviceSdk"; +/** + * Builder for the `DeviceSdk` class. + * + * @example + * ``` + * const sdk = new LedgerDeviceSdkBuilder() + * .setStub(false) + * .addLogger(myLogger) + * .build(); + * ``` + */ export class LedgerDeviceSdkBuilder { stub = false; loggers: LoggerSubscriberService[] = []; @@ -14,6 +25,9 @@ export class LedgerDeviceSdkBuilder { return this; } + /** + * Add a logger to the SDK that will receive its logs + */ addLogger(logger: LoggerSubscriberService): LedgerDeviceSdkBuilder { this.loggers.push(logger); return this; diff --git a/packages/core/src/api/apdu/model/Apdu.ts b/packages/core/src/api/apdu/model/Apdu.ts index 2ecabdcdc..4482cb23c 100644 --- a/packages/core/src/api/apdu/model/Apdu.ts +++ b/packages/core/src/api/apdu/model/Apdu.ts @@ -1,8 +1,31 @@ +/** + * Represents an APDU command that can be sent to a device. + * DO NOT USE THIS CLASS DIRECTLY, use ApduBuilder instead. + */ export class Apdu { + /** + * Instruction class (1 byte) + */ readonly cla: number; + + /** + * Instruction code (1 byte) + */ readonly ins: number; + + /** + * Instruction parameter 1 (2 bytes) + */ readonly p1: number; + + /** + * Instruction parameter 2 (2 bytes) + */ readonly p2: number; + + /** + * Bytes of data + */ data?: Uint8Array; constructor( @@ -19,6 +42,10 @@ export class Apdu { this.data = data; } + /** + * Get the raw binary data of the APDU command + * @returns {Uint8Array} - The raw APDU command + */ getRawApdu(): Uint8Array { const header = Uint8Array.from([ this.cla, diff --git a/packages/core/src/api/apdu/utils/ApduBuilder.ts b/packages/core/src/api/apdu/utils/ApduBuilder.ts index 7c2a893bf..ffa4cd410 100644 --- a/packages/core/src/api/apdu/utils/ApduBuilder.ts +++ b/packages/core/src/api/apdu/utils/ApduBuilder.ts @@ -21,9 +21,21 @@ export type ApduBuilderArgs = { }; /** - * ApduBuilder is a utility class to help build APDU commands + * ApduBuilder is a utility class to help build APDU commands. * It allows to easily add data to the data field of the APDU command - * and to encode data in different formats + * and to encode this data in different formats. + * + * @example + * ``` + * const apduBuilder = new ApduBuilder({ ins: 0x01, cla: 0x02, p1: 0x03, p2: 0x04 }) + * .add8BitUintToData(0x05) + * .add16BitUintToData(0x0607) + * .addHexaStringToData("0x0809") + * .addAsciiStringToData("hello") + * + * const apdu = apduBuilder.build(); + * const builderErrors = apduBuilder.getErrors(); + * ``` */ export class ApduBuilder { private _ins: number; diff --git a/packages/core/src/api/apdu/utils/ApduParser.ts b/packages/core/src/api/apdu/utils/ApduParser.ts index 9e05c0146..783ebd633 100644 --- a/packages/core/src/api/apdu/utils/ApduParser.ts +++ b/packages/core/src/api/apdu/utils/ApduParser.ts @@ -5,6 +5,18 @@ export type TaggedField = { value: Uint8Array; }; +/** + * ApduParser is a utility class to help parse APDU responses. + * + * It provides methods to extract fields of different types from the response. + * + * @example + * ``` + * const parser = new ApduParser(apduResponse); + * const targetId = parser.encodeToHexaString(parser.extractFieldByLength(4)); + * const seVersion = parser.encodeToString(parser.extractFieldLVEncoded()); + * ``` + */ export class ApduParser { private _index: number; private _response: Uint8Array; diff --git a/packages/core/src/api/apdu/utils/AppBuilderError.ts b/packages/core/src/api/apdu/utils/AppBuilderError.ts index c0c725250..2566422bc 100644 --- a/packages/core/src/api/apdu/utils/AppBuilderError.ts +++ b/packages/core/src/api/apdu/utils/AppBuilderError.ts @@ -45,6 +45,9 @@ export class HexaStringEncodeError implements SdkAppBuilderError { } } +/** + * Type for all possible errors that can be thrown by the AppBuilder. + */ export type AppBuilderError = | InvalidValueError | ValueOverflowError diff --git a/packages/core/src/api/command/Command.ts b/packages/core/src/api/command/Command.ts index 2f7f6f479..6cbd474f0 100644 --- a/packages/core/src/api/command/Command.ts +++ b/packages/core/src/api/command/Command.ts @@ -2,8 +2,17 @@ import { Apdu } from "@api/apdu/model/Apdu"; import { DeviceModelId } from "@api/device/DeviceModel"; import { ApduResponse } from "@api/device-session/ApduResponse"; +/** + * Represents a command that can be sent to a device. + */ export interface Command { + /** + * Returns the APDU to be sent to the device. + */ getApdu(args?: U): Apdu; + /** + * Parses the APDU response from the device. + */ parseResponse( apduResponse: ApduResponse, deviceModelId: DeviceModelId | void, diff --git a/packages/core/src/api/command/os/GetAppAndVersionCommand.ts b/packages/core/src/api/command/os/GetAppAndVersionCommand.ts index b91178a0e..9990a7799 100644 --- a/packages/core/src/api/command/os/GetAppAndVersionCommand.ts +++ b/packages/core/src/api/command/os/GetAppAndVersionCommand.ts @@ -10,11 +10,21 @@ import { CommandUtils } from "@api/command/utils/CommandUtils"; import { ApduResponse } from "@api/device-session/ApduResponse"; export type GetAppAndVersionResponse = { + /** + * The name of the application currently running on the device. + */ name: string; + /** + * The version of the application currently running on the device. + */ version: string; flags?: number | Uint8Array; }; +/** + * Command to get information about the application currently running on the + * device. + */ export class GetAppAndVersionCommand implements Command { diff --git a/packages/core/src/api/command/os/GetBatteryStatusCommand.test.ts b/packages/core/src/api/command/os/GetBatteryStatusCommand.test.ts index 655328605..f29e7e288 100644 --- a/packages/core/src/api/command/os/GetBatteryStatusCommand.test.ts +++ b/packages/core/src/api/command/os/GetBatteryStatusCommand.test.ts @@ -44,7 +44,7 @@ describe("GetBatteryStatus", () => { }); describe("getApdu", () => { - it("should return the GetBatteryStatus APUD", () => { + it("should return the GetBatteryStatus APDU", () => { expect( command.getApdu(BatteryStatusType.BATTERY_PERCENTAGE).getRawApdu(), ).toStrictEqual(GET_BATTERY_STATUS_APDU_PERCENTAGE); diff --git a/packages/core/src/api/command/os/GetBatteryStatusCommand.ts b/packages/core/src/api/command/os/GetBatteryStatusCommand.ts index c692a78da..71e8b3e17 100644 --- a/packages/core/src/api/command/os/GetBatteryStatusCommand.ts +++ b/packages/core/src/api/command/os/GetBatteryStatusCommand.ts @@ -11,11 +11,29 @@ import { import { CommandUtils } from "@api/command/utils/CommandUtils"; import { ApduResponse } from "@api/device-session/ApduResponse"; +/** + * The type of battery information to retrieve. + */ export enum BatteryStatusType { + /** + * The command response will be the battery percentage. + */ BATTERY_PERCENTAGE = 0x00, + /** + * The command response will be the battery voltage in mV. + */ BATTERY_VOLTAGE = 0x01, + /** + * The command response will be the battery temperature in degree celsius + */ BATTERY_TEMPERATURE = 0x02, + /** + * The command response will be the battery current in mA. + */ BATTERY_CURRENT = 0x03, + /** + * The command response will be the battery status (cf. `BatteryStatusFlags`) + */ BATTERY_FLAGS = 0x04, } @@ -42,8 +60,20 @@ type BatteryStatusFlags = { issueBattery: boolean; }; +/** + * The response type depends on the `statusType` parameter sent with the command, + * cf. `BatteryStatusType`. + */ export type GetBatteryStatusResponse = number | BatteryStatusFlags; +/** + * Command to get the battery status of the device. + * The parameter statusType defines the type of information to retrieve, cf. + * `BatteryStatusType`. + * + * WARNING: this command should not be sent within a logic of polling as it is + * going to decrease the overall performance of the communication with the device. + */ export class GetBatteryStatusCommand implements Command { diff --git a/packages/core/src/api/command/os/GetOsVersionCommand.ts b/packages/core/src/api/command/os/GetOsVersionCommand.ts index 3eefb3571..05cdc3d54 100644 --- a/packages/core/src/api/command/os/GetOsVersionCommand.ts +++ b/packages/core/src/api/command/os/GetOsVersionCommand.ts @@ -7,17 +7,62 @@ import { CommandUtils } from "@api/command/utils/CommandUtils"; import { DeviceModelId } from "@api/device/DeviceModel"; import { ApduResponse } from "@api/device-session/ApduResponse"; +/** + * Response of the GetOsVersionCommand. + */ export type GetOsVersionResponse = { + /** + * Target identifier. + */ targetId: string; + + /** + * Version of BOLOS on the secure element (SE). + * {@link https://developers.ledger.com/docs/device-app/architecture/bolos/hardware-architecture | Hardware Architecture} + */ seVersion: string; + + /** + * Secure element flags. + * Used to represent the current state of the secure element. + */ seFlags: number; + + /** + * Version of the microcontroller unit (MCU) SEPH, which is the SE-MCU link protocol. + * {@link https://developers.ledger.com/docs/device-app/architecture/bolos/hardware-architecture | Hardware Architecture} + */ mcuSephVersion: string; + + /** + * Version of the MCU bootloader. + */ mcuBootloaderVersion: string; + + /** + * Hardware revision version. + * Only available for Ledger Nano X in which case it's "00" or "01". + */ hwVersion: string; - langId: string; + + /** + * Identifier of the installed language pack. + * Can be one of: + * - "00": English + * - "01": French + * - "02": Spanish + */ + langId: string; // [SHOULD] be an enum + + /** + * State for Ledger Recover. // [SHOULD] Add more information about this field + */ recoverState: string; }; +/** + * Command to get information about the device firmware. + */ export class GetOsVersionCommand implements Command { getApdu = (): Apdu => new ApduBuilder({ diff --git a/packages/core/src/api/command/use-case/SendCommandUseCase.ts b/packages/core/src/api/command/use-case/SendCommandUseCase.ts index 0e2b4c8fd..19ce38102 100644 --- a/packages/core/src/api/command/use-case/SendCommandUseCase.ts +++ b/packages/core/src/api/command/use-case/SendCommandUseCase.ts @@ -7,8 +7,17 @@ import { loggerTypes } from "@internal/logger-publisher/di/loggerTypes"; import { LoggerPublisherService } from "@internal/logger-publisher/service/LoggerPublisherService"; export type SendCommandUseCaseArgs = { + /** + * The device session id. + */ sessionId: string; + /** + * The command to send. + */ command: Command; + /** + * The parameters of the command. + */ params: U; }; @@ -29,6 +38,14 @@ export class SendCommandUseCase { this._logger = loggerFactory("SendCommandUseCase"); } + /** + * Sends a command to a device through a device session. + * + * @param sessionId - The device session id. + * @param command - The command to send. + * @param params - The parameters of the command. + * @returns The response from the command. + */ async execute({ sessionId, command, diff --git a/packages/core/src/api/device-session/types.ts b/packages/core/src/api/device-session/types.ts index 4edcad22d..e838f624b 100644 --- a/packages/core/src/api/device-session/types.ts +++ b/packages/core/src/api/device-session/types.ts @@ -1 +1,4 @@ +/** + * Unique identifier for a device session. + */ export type DeviceSessionId = string; diff --git a/packages/core/src/api/device/DeviceModel.ts b/packages/core/src/api/device/DeviceModel.ts index 0f9367de2..b4f60e01c 100644 --- a/packages/core/src/api/device/DeviceModel.ts +++ b/packages/core/src/api/device/DeviceModel.ts @@ -5,6 +5,14 @@ export enum DeviceModelId { STAX = "stax", } +/** + * Unique identifier for a device. + * + * NB: This identifier is generated at runtime and is not persisted. It cannot + * be used to identify a device across sessions. + * There is in fact no way to identify a device across sessions, which is a + * privacy feature of Ledger devices. + */ export type DeviceId = string; export class DeviceModel { diff --git a/packages/core/src/api/logger-subscriber/service/LoggerSubscriberService.ts b/packages/core/src/api/logger-subscriber/service/LoggerSubscriberService.ts index 829032555..a4a9000eb 100644 --- a/packages/core/src/api/logger-subscriber/service/LoggerSubscriberService.ts +++ b/packages/core/src/api/logger-subscriber/service/LoggerSubscriberService.ts @@ -1,6 +1,12 @@ import { LogLevel } from "@api/logger-subscriber/model/LogLevel"; import { LogSubscriberOptions } from "@api/logger-subscriber/model/LogSubscriberOptions"; +/** + * Logger subscriber service. + * + * Implement this interface and use `LedgerDeviceSdkBuilder.addLogger` to + * receive logs from the SDK. + */ export interface LoggerSubscriberService { log(level: LogLevel, message: string, options: LogSubscriberOptions): void; } diff --git a/packages/core/src/internal/discovery/use-case/ConnectUseCase.ts b/packages/core/src/internal/discovery/use-case/ConnectUseCase.ts index 13b6b3724..646fb37b6 100644 --- a/packages/core/src/internal/discovery/use-case/ConnectUseCase.ts +++ b/packages/core/src/internal/discovery/use-case/ConnectUseCase.ts @@ -11,6 +11,9 @@ import { usbDiTypes } from "@internal/usb/di/usbDiTypes"; import type { UsbHidTransport } from "@internal/usb/transport/UsbHidTransport"; export type ConnectUseCaseArgs = { + /** + * UUID of the device obtained through device discovery `StartDiscoveringUseCase` + */ deviceId: DeviceId; }; diff --git a/packages/core/src/internal/discovery/use-case/StartDiscoveringUseCase.ts b/packages/core/src/internal/discovery/use-case/StartDiscoveringUseCase.ts index 85c2e9bd8..b0a133642 100644 --- a/packages/core/src/internal/discovery/use-case/StartDiscoveringUseCase.ts +++ b/packages/core/src/internal/discovery/use-case/StartDiscoveringUseCase.ts @@ -6,7 +6,7 @@ import { DiscoveredDevice } from "@internal/usb/model/DiscoveredDevice"; import type { UsbHidTransport } from "@internal/usb/transport/UsbHidTransport"; /** - * Starts discovering devices connected via USB HID (and later BLE). + * Starts discovering devices connected via USB HID (BLE not implemented yet). * * For the WebHID implementation, this use-case needs to be called as a result of an user interaction (button "click" event for ex). */ diff --git a/packages/core/src/internal/send/use-case/SendApduUseCase.ts b/packages/core/src/internal/send/use-case/SendApduUseCase.ts index 248785aa3..ce0905387 100644 --- a/packages/core/src/internal/send/use-case/SendApduUseCase.ts +++ b/packages/core/src/internal/send/use-case/SendApduUseCase.ts @@ -8,7 +8,13 @@ import { loggerTypes } from "@internal/logger-publisher/di/loggerTypes"; import { LoggerPublisherService } from "@internal/logger-publisher/service/LoggerPublisherService"; export type SendApduUseCaseArgs = { + /** + * Device session identifier obtained through `DeviceSdk.connect` + */ sessionId: DeviceSessionId; + /** + * APDU to send to the device + */ apdu: Uint8Array; }; diff --git a/packages/core/src/internal/usb/model/DiscoveredDevice.ts b/packages/core/src/internal/usb/model/DiscoveredDevice.ts index 7f908b7c0..5dc6a39a1 100644 --- a/packages/core/src/internal/usb/model/DiscoveredDevice.ts +++ b/packages/core/src/internal/usb/model/DiscoveredDevice.ts @@ -6,6 +6,14 @@ import { InternalDeviceModel } from "@internal/device-model/model/DeviceModel"; */ export type DiscoveredDevice = { // type: "web-hid", // "node-hid" in the future -> no need as we will only have 1 USB transport implementation running - id: DeviceId; // UUID to map with the associated transport device + + /** + * Unique identifier for the device. + * NB: This identifier is generated at runtime and is not persisted. + * It cannot be used to identify a device across sessions. + * There is in fact no way to identify a device across sessions, which is a + * privacy feature of Ledger devices. + */ + id: DeviceId; deviceModel: InternalDeviceModel; };