diff --git a/apps/ledger-live-desktop/package.json b/apps/ledger-live-desktop/package.json
index 36be0e08229d..230f635acd7f 100644
--- a/apps/ledger-live-desktop/package.json
+++ b/apps/ledger-live-desktop/package.json
@@ -73,6 +73,7 @@
"@ledgerhq/live-config": "workspace:^",
"@ledgerhq/live-countervalues": "workspace:^",
"@ledgerhq/live-countervalues-react": "workspace:^",
+ "@ledgerhq/live-dmk": "workspace:*",
"@ledgerhq/live-env": "workspace:^",
"@ledgerhq/live-network": "workspace:^",
"@ledgerhq/live-nft": "workspace:^",
diff --git a/apps/ledger-live-desktop/src/renderer/App.tsx b/apps/ledger-live-desktop/src/renderer/App.tsx
index 6f261193bc3b..f8a2d16c7c3e 100644
--- a/apps/ledger-live-desktop/src/renderer/App.tsx
+++ b/apps/ledger-live-desktop/src/renderer/App.tsx
@@ -5,6 +5,7 @@ import { HashRouter as Router } from "react-router-dom";
import { NftMetadataProvider } from "@ledgerhq/live-nft-react";
import { getCurrencyBridge } from "@ledgerhq/live-common/bridge/index";
import { getFeature } from "@ledgerhq/live-common/featureFlags/index";
+import { DeviceManagementKitProvider } from "@ledgerhq/live-dmk";
import "./global.css";
import "tippy.js/dist/tippy.css";
import "tippy.js/animations/shift-away.css";
@@ -79,30 +80,32 @@ const InnerApp = ({ initialCountervalues }: { initialCountervalues: CounterValue
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/ledger-live-desktop/src/renderer/live-common-setup.ts b/apps/ledger-live-desktop/src/renderer/live-common-setup.ts
index f5070b7769c7..7854d548fcb6 100644
--- a/apps/ledger-live-desktop/src/renderer/live-common-setup.ts
+++ b/apps/ledger-live-desktop/src/renderer/live-common-setup.ts
@@ -16,6 +16,7 @@ import { FeatureId } from "@ledgerhq/types-live";
import { getEnv } from "@ledgerhq/live-env";
import { overriddenFeatureFlagsSelector } from "~/renderer/reducers/settings";
import { State } from "./reducers";
+import { DeviceManagementKitTransport } from "@ledgerhq/live-dmk";
interface Store {
getState: () => State;
@@ -38,12 +39,36 @@ export function registerTransportModules(store: Store) {
logger.debug(log);
});
+ registerTransportModule({
+ id: "sdk",
+ open: (_id: string, timeoutMs?: number, context?: TraceContext) => {
+ const ldmkFeatureFlag = getFeatureWithOverrides("ldmkTransport", store);
+ if (!ldmkFeatureFlag.enabled) return null;
+ if (isSpeculosEnabled && isProxyEnabled) return null;
+ trace({
+ type: "renderer-setup",
+ message: "Open called on registered module",
+ data: {
+ transport: "SDKTransport",
+ timeoutMs,
+ },
+ context: {
+ openContext: context,
+ },
+ });
+ return DeviceManagementKitTransport.open();
+ },
+
+ disconnect: () => Promise.resolve(),
+ });
+
// Register IPC Transport Module
registerTransportModule({
id: "ipc",
open: (id: string, timeoutMs?: number, context?: TraceContext) => {
- const ldmkFeatureFlag = getFeatureWithOverrides("ldmkTransport" as FeatureId, store);
- if (ldmkFeatureFlag.enabled && !isSpeculosEnabled && !isProxyEnabled) return;
+ const ldmkFeatureFlag = getFeatureWithOverrides("ldmkTransport", store);
+ if (ldmkFeatureFlag.enabled) return null;
+ if (!isSpeculosEnabled && !isProxyEnabled) return null;
const originalDeviceMode = currentMode;
// id could be another type of transport such as vault-transport
diff --git a/libs/live-dmk/package.json b/libs/live-dmk/package.json
index 23504ddf3d6d..60423159f581 100644
--- a/libs/live-dmk/package.json
+++ b/libs/live-dmk/package.json
@@ -23,6 +23,7 @@
"build": "tsc && tsc -m ES6 --outDir lib-es",
"prewatch": "pnpm build",
"watch": "tsc --watch",
+ "watch:es": "tsc --watch -m ES6 --outDir lib-es",
"lint": "eslint ./src --no-error-on-unmatched-pattern --ext .ts,.tsx --cache",
"lint:fix": "pnpm lint --fix",
"typecheck": "tsc --noEmit",
diff --git a/libs/live-dmk/src/index.tsx b/libs/live-dmk/src/index.tsx
index 4ecb11a8b8a3..d9ca6e930285 100644
--- a/libs/live-dmk/src/index.tsx
+++ b/libs/live-dmk/src/index.tsx
@@ -7,28 +7,33 @@ import {
type DeviceSessionState,
DeviceStatus,
LogLevel,
+ BuiltinTransports,
} from "@ledgerhq/device-management-kit";
import { BehaviorSubject, firstValueFrom } from "rxjs";
import { LocalTracer } from "@ledgerhq/logs";
-const deviceSdk = new DeviceManagementKitBuilder()
+const deviceManagementKit = new DeviceManagementKitBuilder()
+ .addTransport(BuiltinTransports.USB)
.addLogger(new ConsoleLogger(LogLevel.Debug))
.build();
-export const DeviceSdkContext = createContext(deviceSdk);
+export const DeviceManagementKitContext = createContext(deviceManagementKit);
type Props = {
children: React.ReactNode;
};
-export const DeviceSdkProvider: React.FC = ({ children }) => (
- {children}
+export const DeviceManagementKitProvider: React.FC = ({ children }) => (
+
+ {children}
+
);
-export const useDeviceSdk = (): DeviceManagementKit => useContext(DeviceSdkContext);
+export const useDeviceManagementKit = (): DeviceManagementKit =>
+ useContext(DeviceManagementKitContext);
export const useDeviceSessionState = (): DeviceSessionState | undefined => {
- const sdk = useDeviceSdk();
+ const sdk = useDeviceManagementKit();
const [sessionState, setSessionState] = useState(undefined);
useEffect(() => {
@@ -66,6 +71,8 @@ const activeDeviceSessionSubject: BehaviorSubject<{
transport: DeviceManagementKitTransport;
} | null>(null);
+const tracer = new LocalTracer("live-dmk", { function: "DeviceManagementKitTransport" });
+
export class DeviceManagementKitTransport extends Transport {
readonly sessionId: string;
readonly sdk: DeviceManagementKit;
@@ -75,14 +82,13 @@ export class DeviceManagementKitTransport extends Transport {
this.sessionId = sessionId;
this.sdk = sdk;
this.listenToDisconnect();
- this.tracer = new LocalTracer("live-dmdk", { function: "DeviceManagementKitTransport" });
}
listenToDisconnect = () => {
const subscription = this.sdk.getDeviceSessionState({ sessionId: this.sessionId }).subscribe({
next: (state: { deviceStatus: any }) => {
if (state.deviceStatus === DeviceStatus.NOT_CONNECTED) {
- this.tracer.trace("[listenToDisconnect] Device disconnected, closing transport");
+ tracer.trace("[listenToDisconnect] Device disconnected, closing transport");
activeDeviceSessionSubject.next(null);
this.emit("disconnect");
}
@@ -93,37 +99,37 @@ export class DeviceManagementKitTransport extends Transport {
subscription.unsubscribe();
},
complete: () => {
- this.tracer.trace("[listenToDisconnect] Complete");
+ tracer.trace("[listenToDisconnect] Complete");
this.emit("disconnect");
subscription.unsubscribe();
},
});
};
- async open(): Promise {
+ static async open(): Promise {
const activeSessionId = activeDeviceSessionSubject.value?.sessionId;
if (activeSessionId) {
- this.tracer.trace(`[open] checking existing session ${activeSessionId}`);
+ tracer.trace(`[open] checking existing session ${activeSessionId}`);
const deviceSessionState: DeviceSessionState | null = await firstValueFrom(
- deviceSdk.getDeviceSessionState({ sessionId: activeSessionId }),
+ deviceManagementKit.getDeviceSessionState({ sessionId: activeSessionId }),
).catch(e => {
console.error("[SDKTransport][open] error getting device session state", e);
return null;
});
if (deviceSessionState?.deviceStatus !== DeviceStatus.NOT_CONNECTED) {
- this.tracer.trace("[open] reusing existing session and instantiating a new SdkTransport");
+ tracer.trace("[open] reusing existing session and instantiating a new SdkTransport");
return activeDeviceSessionSubject.value.transport;
}
}
- this.tracer.trace("[open] No active session found, starting discovery");
- const [discoveredDevice] = await firstValueFrom(deviceSdk.listenToKnownDevices());
- const connectedSessionId = await deviceSdk.connect({ device: discoveredDevice });
+ tracer.trace("[open] No active session found, starting discovery");
+ const [discoveredDevice] = await firstValueFrom(deviceManagementKit.listenToKnownDevices());
+ const connectedSessionId = await deviceManagementKit.connect({ device: discoveredDevice });
- this.tracer.trace("[open] Connected");
- const transport = new DeviceManagementKitTransport(deviceSdk, connectedSessionId);
+ tracer.trace("[open] Connected");
+ const transport = new DeviceManagementKitTransport(deviceManagementKit, connectedSessionId);
activeDeviceSessionSubject.next({ sessionId: connectedSessionId, transport });
return transport;
@@ -132,7 +138,7 @@ export class DeviceManagementKitTransport extends Transport {
close: () => Promise = () => Promise.resolve();
async exchange(apdu: Buffer): Promise {
- this.tracer.trace(`[exchange] => ${apdu}`);
+ tracer.trace(`[exchange] => ${apdu}`);
return await this.sdk
.sendApdu({
sessionId: this.sessionId,
@@ -140,7 +146,7 @@ export class DeviceManagementKitTransport extends Transport {
})
.then((apduResponse: { data: Uint8Array; statusCode: Uint8Array }): Buffer => {
const response = Buffer.from([...apduResponse.data, ...apduResponse.statusCode]);
- this.tracer.trace(`[exchange] <= ${response}`);
+ tracer.trace(`[exchange] <= ${response}`);
return response;
});
}
diff --git a/libs/live-dmk/src/useDeviceSessionState.test.tsx b/libs/live-dmk/src/useDeviceSessionState.test.tsx
index 52725612423d..07fb11fc6ad4 100644
--- a/libs/live-dmk/src/useDeviceSessionState.test.tsx
+++ b/libs/live-dmk/src/useDeviceSessionState.test.tsx
@@ -2,18 +2,26 @@ import React from "react";
import { render, act, cleanup } from "@testing-library/react";
import "@testing-library/jest-dom";
import { BehaviorSubject, of } from "rxjs";
-import { DeviceSdkProvider, useDeviceSessionState, useDeviceSdk } from "./index";
+import {
+ DeviceManagementKitProvider,
+ useDeviceSessionState,
+ useDeviceManagementKit,
+} from "./index";
import { DeviceStatus } from "@ledgerhq/device-management-kit";
jest.mock("@ledgerhq/device-management-kit", () => ({
DeviceManagementKitBuilder: jest.fn(() => ({
addLogger: jest.fn().mockReturnThis(),
+ addTransport: jest.fn().mockReturnThis(),
build: jest.fn().mockReturnValue({
getDeviceSessionState: jest.fn(),
startDiscovering: jest.fn(),
connect: jest.fn(),
}),
})),
+ BuiltinTransports: {
+ USB: "USB",
+ },
ConsoleLogger: jest.fn(),
LogLevel: { Debug: "debug" },
DeviceStatus: {
@@ -29,13 +37,13 @@ const activeDeviceSessionSubjectMock = new BehaviorSubject<{
jest.mock("./index", () => ({
...jest.requireActual("./index"),
- useDeviceSdk: jest.fn(),
+ useDeviceManagementKit: jest.fn(),
}));
afterEach(cleanup);
const Wrapper: React.FC<{ children: React.ReactNode }> = ({ children }) => (
- {children}
+ {children}
);
const TestComponent: React.FC = () => {
@@ -48,18 +56,18 @@ const TestComponent: React.FC = () => {
};
describe("useDeviceSessionState", () => {
- let sdkMock: {
+ let deviceManagementKitMock: {
getDeviceSessionState: jest.Mock;
};
beforeEach(() => {
- sdkMock = {
+ deviceManagementKitMock = {
getDeviceSessionState: jest.fn(),
};
- (useDeviceSdk as jest.Mock).mockReturnValue(sdkMock);
+ (useDeviceManagementKit as jest.Mock).mockReturnValue(deviceManagementKitMock);
jest
- .spyOn(sdkMock, "getDeviceSessionState")
+ .spyOn(deviceManagementKitMock, "getDeviceSessionState")
.mockImplementation(({ sessionId }: { sessionId: string }) => {
if (sessionId === "valid-session") {
return of({
@@ -132,7 +140,7 @@ describe("useDeviceSessionState", () => {
activeDeviceSessionSubjectMock.next(null);
await act(async () => {
- sdkMock.getDeviceSessionState.mockReturnValueOnce(
+ deviceManagementKitMock.getDeviceSessionState.mockReturnValueOnce(
of({
deviceStatus: DeviceStatus.NOT_CONNECTED,
}),
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 6645e0e558a2..2b7758da91f0 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -358,6 +358,9 @@ importers:
'@ledgerhq/live-countervalues-react':
specifier: workspace:^
version: link:../../libs/live-countervalues-react
+ '@ledgerhq/live-dmk':
+ specifier: workspace:*
+ version: link:../../libs/live-dmk
'@ledgerhq/live-env':
specifier: workspace:^
version: link:../../libs/env