Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Docs/dmk improvements #332

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 66 additions & 27 deletions pages/docs/device-interaction/beginner/discover_and_connect.mdx
Original file line number Diff line number Diff line change
@@ -1,40 +1,79 @@
import { Callout } from "nextra/components";

# Connecting to a Device

There are two steps to connecting to a device:
## Overview

In this tutorial, you'll learn how to connect to a Ledger device using the Device Management Kit (DMK). We will cover the device discovery and connection process, and how to manage your device session effectively.

## Prerequisites

Ensure you have:

- A device running Ledger firmware.
- The DMK properly set up, as described in the [Setup guide](./init_dmk).

## Step-by-Step Guide

### Step 1: Discover Devices

- **Discovery**: `sdk.startDiscovering()`
- Returns an observable which will emit a new `DiscoveredDevice` for every scanned device.
- The `DiscoveredDevice` objects contain information about the device model.
- Use one of these values to connect to a given discovered device.
- **Connection**: `sdk.connect({ deviceId: device.id })`
- Returns a Promise resolving in a device session identifier `DeviceSessionId`.
- **Keep this device session identifier to further interact with the device.**
- Then, `sdk.getConnectedDevice({ sessionId })` returns the `ConnectedDevice`, which contains information about the device model and its name.
Begin by discovering available Ledger devices. Use the `sdk.startDiscovering()` method, which returns an observable. This observable emits a new `DiscoveredDevice` object for each device found.

```ts
sdk.startDiscovering().subscribe({
next: (device) => {
sdk.connect({ deviceId: device.id }).then((sessionId) => {
const connectedDevice = sdk.getConnectedDevice({ sessionId });
});
// Process each discovered device
console.log(`Discovered device: ${device.model}`);
},
error: (error) => {
console.error(error);
console.error("Error during device discovery:", error);
},
});
```

Then once a device is connected:

- **Disconnection**: `sdk.disconnect({ sessionId })`
- **Observe the device session state**: `sdk.getDeviceSessionState({ sessionId })`
- This will return an `Observable<DeviceSessionState>` to listen to the known information about the device:
- device status:
- ready to process a command
- busy
- locked
- disconnected
- device name
- information on the OS
- battery status
- currently opened app
<Callout>
- Each `DiscoveredDevice` contains details about the device model and other identification details.
- Use these details to select which device to connect to.
</Callout>

### Step 2: Connect to a Device

After identifying the desired device, initiate a connection using `sdk.connect()`. This method requires the `deviceId` and returns a Promise that resolves with a `DeviceSessionId`.

```ts
sdk.connect({ deviceId: device.id }).then((sessionId) => {
const connectedDevice = sdk.getConnectedDevice({ sessionId });
console.log(`Connected to device: ${connectedDevice.name}`);
});
```

<Callout type="warning" emoji="⚠️">
Device Session Identifier**: Store the `DeviceSessionId` securely, as it is needed for further interactions with the device.
</Callout>

### Step 3: Interact with the Connected Device

Once connected, you may wish to observe the device session state or eventually disconnect:

- **Observe Session State**: Use `sdk.getDeviceSessionState({ sessionId })` to monitor and react to device state changes. This returns an `Observable<DeviceSessionState>` that informs about the device's name, status (ready to process command, busy, locked or disconnected), information on the OS, battery status, and currently opened app.

```ts
sdk.getDeviceSessionState({ sessionId }).subscribe({
next: (state) => {
console.log("Device status:", state.status);
console.log("Battery level:", state.battery);
}
});
```

- **Disconnect From Device**: When you are done, ensure to disconnect using `sdk.disconnect({ sessionId })`.

```ts
sdk.disconnect({ sessionId }).then(() => {
console.log("Device disconnected");
});
```
## Best Practices

- **Session Management**: Maintain the session state to handle connection disruptions or status changes effectively.
- **Error Handling**: Implement robust error-handling mechanisms at each step to ensure smooth operation.
98 changes: 52 additions & 46 deletions pages/docs/device-interaction/beginner/exchange_data.mdx
Original file line number Diff line number Diff line change
@@ -1,114 +1,120 @@
import { Callout } from "nextra/components";

# Exchange data with the device

## Overview

This tutorial demonstrates how to send and receive data from a Ledger device using Application Protocol Data Units (APDUs). We'll explore both direct APDU handling and using pre-defined commands for ease of use.

## Prerequisites

Make sure you have a connected device session ID available from the previous connection tutorial.

## Sending an APDU

Once you have a connected device, you can send it APDU commands.
### Recommended Practices

> ℹ️ It is recommended to use the [pre-defined commands](#sending-a-pre-defined-command) when possible, or [build your own command](#building-a-new-command), to avoid dealing with the APDU directly. It will make your code more reusable.
<Callout>
Whenever possible, use [pre-defined commands](#sending-a-pre-defined-command) or [custom build commands](#building-a-new-command) rather than dealing with APDUs directly. This approach enhances code reusability and maintainability.
</Callout>

```ts
import {
ApduBuilder,
ApduParser,
CommandUtils,
} from "@ledgerhq/device-management-kit";
### Step 1: Building the APDU

To construct an APDU, use `ApduBuilder`. Here's an example for opening the Bitcoin app:

// ### 1. Building the APDU
// Use `ApduBuilder` to easily build the APDU and add data to its data field.
```ts
import { ApduBuilder } from "@ledgerhq/device-management-kit";

// Build the APDU to open the Bitcoin app
const openAppApduArgs = {
cla: 0xe0,
ins: 0xd8,
p1: 0x00,
p2: 0x00,
};
const openAppApduArgs = { cla: 0xe0, ins: 0xd8, p1: 0x00, p2: 0x00 };
const apdu = new ApduBuilder(openAppApduArgs)
.addAsciiStringToData("Bitcoin")
.build();
```
### Step 2: Sending the APDU

// ### 2. Sending the APDU
Send the constructed APDU using the `sdk.sendApdu` method:

```ts
const apduResponse = await sdk.sendApdu({ sessionId, apdu });
```
### Step 3: Parsing the Response

Parse the response using `ApduParser` and check for success:

// ### 3. Parsing the result
```ts
import { ApduParser, CommandUtils } from "@ledgerhq/device-management-kit";

const parser = new ApduParser(apduResponse);

if (!CommandUtils.isSuccessResponse(apduResponse)) {
throw new Error(
`Unexpected status word: ${parser.encodeToHexaString(
apduResponse.statusCode,
)}`,
`Unexpected status word: ${parser.encodeToHexaString(apduResponse.statusCode)}`,
);
}
```

## Sending a Pre-defined Command

There are some pre-defined commands that you can send to a connected device.
For convenience, pre-defined commands simplify APDU building and response parsing.

The `sendCommand` method will take care of building the APDU, sending it to the device and returning the parsed response.
<Callout type="warning" emoji="⚠️">
Most of the commands will reject with an error if the device is locked. Ensure that the device is unlocked before sending commands. You can check the device session state <code>(sdk.getDeviceSessionState)</code> to know if the device is locked.

> ## ❗️ Error Responses
>
> Most of the commands will reject with an error if the device is locked.
> Ensure that the device is unlocked before sending commands. You can check the device session state (`sdk.getDeviceSessionState`) to know if the device is locked.
>
> Most of the commands will reject with an error if the response status word is not `0x9000` (success response from the device).
Most of the commands will reject with an error if the response status word is not <code>0x9000</code> (success response from the device).
</Callout>

### Open App

This command will open the app with the given name. If the device is unlocked, it will not resolve/reject until the user has confirmed or denied the app opening on the device.
Opens the app with the given name:

```ts
import { OpenAppCommand } from "@ledgerhq/device-management-kit";

const command = new OpenAppCommand("Bitcoin"); // Open the Bitcoin app

const command = new OpenAppCommand("Bitcoin");
await sdk.sendCommand({ sessionId, command });
```

If the device is unlocked, it does not resolve/reject until the user has confirmed or denied the app opening on the device.

### Close App

This command will close the currently opened app.
Closes the currently open app:

```ts
import { CloseAppCommand } from "@ledgerhq/device-management-kit";

const command = new CloseAppCommand();

await sdk.sendCommand({ sessionId, command });
```

### Get OS Version

This command will return information about the currently installed OS on the device.

> ℹ️ If you want this information you can simply get it from the device session state by observing it with `sdk.getDeviceSessionState({ sessionId })`.
Fetches the OS version information:

```ts
import { GetOsVersionCommand } from "@ledgerhq/device-management-kit";

const command = new GetOsVersionCommand();

const { seVersion, mcuSephVersion, mcuBootloaderVersion } =
await sdk.sendCommand({ sessionId, command });
const { seVersion, mcuSephVersion, mcuBootloaderVersion } = await sdk.sendCommand({ sessionId, command });
```

### Get App and Version
<Callout>
If you want this information you can simply get it from the device session state by observing it with `sdk.getDeviceSessionState({ sessionId })`.
</Callout>

This command will return the name and version of the currently running app on the device.
### Get App and Version

> ℹ️ If you want this information you can simply get it from the device session state by observing it with `sdk.getDeviceSessionState({ sessionId })`.
Retrieves the current app name and version:

```ts
import { GetAppAndVersionCommand } from "@ledgerhq/device-management-kit";

const command = new GetAppAndVersionCommand();

const { name, version } = await sdk.sendCommand({ sessionId, command });
```
<Callout>
If you want this information you can simply get it from the device session state by observing it with `sdk.getDeviceSessionState({ sessionId })`.
</Callout>

## Sending a Pre-defined flow - Device Actions

Expand Down Expand Up @@ -184,4 +190,4 @@ observable.subscribe({

### Example in React

Check [the sample app](https://github.com/LedgerHQ/device-sdk-ts/tree/develop/apps/sample) for an advanced example showcasing all possible usages of the Device Management Kit in a React app.
See the [sample app](https://github.com/LedgerHQ/device-sdk-ts/tree/develop/apps/sample) for comprehensive examples showcasing the Device Management Kit's usage in React applications.
37 changes: 32 additions & 5 deletions pages/docs/device-interaction/beginner/init_dmk.mdx
Original file line number Diff line number Diff line change
@@ -1,23 +1,50 @@
# Setting up the SDK

The core package exposes an SDK builder `DeviceSdkBuilder` which will be used to initialise the SDK with your configuration.
## Overview

For now it allows you to add one or more custom loggers.
In this tutorial, you'll learn how to set up the Device Management Kit (DMK) SDK in your application. We'll guide you through initializing the SDK using the `DeviceSdkBuilder` and setting up logging. By the end of this tutorial, you will have a singleton instance of the SDK that will serve as the entry point for all interactions.

In the following example, we add a console logger (`.addLogger(new ConsoleLogger())`). Then we build the SDK with `.build()`.
## Prerequisites

**The returned object will be the entrypoint for all your interactions with the SDK. You should keep it as a <u>SINGLETON</u>.**
Before you begin, ensure you have the following:

The SDK should be built only once in your application runtime so keep a reference of this object somewhere.
- A working development environment with Node.js installed.
- Access to the DMK package (@ledgerhq/device-management-kit) in your project.

## Step-by-Step Guide

### Step 1: Import Required Components

Start by importing the necessary components from the DMK package. You'll need `ConsoleLogger`, `DeviceSdk`, and `DeviceSdkBuilder`.

```ts
import {
ConsoleLogger,
DeviceSdk,
DeviceSdkBuilder,
} from "@ledgerhq/device-management-kit";
```

### Step 2: Initialize the SDK with Configuration

Use the `DeviceSdkBuilder` to initialize the SDK. You can configure it by adding one or more custom loggers. In this example, we'll add a `ConsoleLogger`.

```ts
const sdk = new DeviceSdkBuilder()
.addLogger(new ConsoleLogger())
.build();
```

### Step 3: Maintain a Singleton Instance

The SDK should be instantiated only once during your application's runtime. It's important to keep the returned `DeviceSdk` object as a singleton. Store the reference globally if possible, so it can be reused across your application.

```ts
export const sdk = new DeviceSdkBuilder()
.addLogger(new ConsoleLogger())
.build();
```

## Best Practices

- **Singleton Pattern:** Ensure the SDK instance is maintained as a singleton. This improves efficiency and prevents repeated initializations.
2 changes: 1 addition & 1 deletion pages/docs/device-interaction/explanation/_meta.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export default {
introduction: "Why the Device Management Kit?",
introduction: "Why choose the Device Management Kit?",
signers: "What are Device Signer Kits?",
ledgerjs: "Differences with LedgerJS"
};
22 changes: 17 additions & 5 deletions pages/docs/device-interaction/explanation/introduction.mdx
Original file line number Diff line number Diff line change
@@ -1,9 +1,21 @@
# Why Device Management Kit ?
# Why choose the Device Management Kit?

The DMK is a set of tools and libraries that allow you to manage your devices in a secure and efficient way. It is designed to be used in conjunction with the Device Signer Kit, which is a set of tools and libraries that allow you to sign transactions securely.
The Device Management Kit (DMK) offers a comprehensive suite of tools and libraries tailored for secure and efficient management of Ledger devices. It is specifically crafted for external developers to enhance the experience of interacting with Ledger hardware securely.

It has been designed for external developers with the main idea to provide the best experience for developers to interact with Ledger devices.
## Seamless Integration with Device Signer Kit

It should as maximum as possible abstract the complexity of the communication with the device and provide a simple and easy-to-use API.
The DMK works seamlessly with the Device Signer Kit, providing a robust ecosystem for secure transaction signing. This integration ensures that developers have access to comprehensive tools for both device management and transaction handling.

It tends to be a replacement for the LedgerJS libraries, mainly `hw-app-XXX` and `transport-XXX` libraries. These libraries are intended to be deprecated in the future.
## Developer-Centric Design

The DMK is purpose-built for developers, focusing on simplifying interactions with Ledger devices. By providing an intuitive and straightforward API, the DMK abstracts much of the underlying complexity involved in device communication, empowering developers to integrate Ledger functionalities with ease.

## Simplified Device Interaction

One of the core objectives of the DMK is to remove the complexities associated with device communication. Its user-friendly API is designed to provide a simple and efficient way to manage Ledger hardware, allowing developers to focus on building applications rather than dealing with low-level device interactions.

## Future-Proof Solution

The DMK is intended to replace existing LedgerJS libraries, such as `hw-app-XXX` and `transport-XXX`. As these older libraries are set to be deprecated, adopting the DMK represents a forward-looking solution that aligns with Ledger's evolving technology stack, preparing developers for future updates and innovations.

By choosing the Device Management Kit, developers can leverage a powerful, secure, and developer-focused toolkit that simplifies the integration with Ledger devices and supports the secure signing of transactions.
Loading
Loading