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/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, }),