Skip to content

Commit

Permalink
✨ (transport-speculos): Create new speculos transport
Browse files Browse the repository at this point in the history
  • Loading branch information
mbertin-ledger committed Feb 12, 2025
1 parent 140b571 commit 31f6c35
Show file tree
Hide file tree
Showing 18 changed files with 475 additions and 48 deletions.
1 change: 1 addition & 0 deletions apps/sample/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"@ledgerhq/device-transport-kit-mockserver": "workspace:*",
"@ledgerhq/device-transport-kit-web-ble": "workspace:*",
"@ledgerhq/device-transport-kit-web-hid": "workspace:*",
"@ledgerhq/device-transport-kit-speculos": "workspace:*",
"@ledgerhq/react-ui": "^0.17.0",
"@playwright/test": "^1.49.0",
"@sentry/nextjs": "^8.42.0",
Expand Down
23 changes: 22 additions & 1 deletion apps/sample/src/components/Header/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, { useCallback, useEffect, useState } from "react";
import { FlipperPluginManager } from "@ledgerhq/device-management-kit-flipper-plugin-client";
import { mockserverIdentifier } from "@ledgerhq/device-transport-kit-mockserver";
import { speculosIdentifier } from "@ledgerhq/device-transport-kit-speculos";
import { webHidIdentifier } from "@ledgerhq/device-transport-kit-web-hid";
import {
Button,
Expand Down Expand Up @@ -54,9 +55,22 @@ export const Header = () => {
},
});
}, [dispatch, transport]);

const onToggleSpeculos = useCallback(() => {
dispatch({
type: "set_transport",
payload: {
transport:
transport === speculosIdentifier
? webHidIdentifier
: speculosIdentifier,
},
});
}, [dispatch, transport]);
const [mockServerStateUrl, setMockServerStateUrl] =
useState<string>(mockServerUrl);
const mockServerEnabled = transport === mockserverIdentifier;
const speculosEnabled = transport === speculosIdentifier;

const validateServerUrl = useCallback(
() =>
Expand Down Expand Up @@ -112,7 +126,14 @@ export const Header = () => {
label="Enable Mock server"
/>
</div>

<div data-testid="switch_speculos">
<Switch
onChange={onToggleSpeculos}
checked={speculosEnabled}
name="switch-speculos"
label="Enable Speculos"
/>
</div>
{mockServerEnabled && (
<UrlInput
value={mockServerStateUrl}
Expand Down
67 changes: 37 additions & 30 deletions apps/sample/src/components/MainView/ConnectDeviceActions.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, { useCallback } from "react";
import { type DmkError } from "@ledgerhq/device-management-kit";
import { mockserverIdentifier } from "@ledgerhq/device-transport-kit-mockserver";
import { speculosIdentifier } from "@ledgerhq/device-transport-kit-speculos";
import { webBleIdentifier } from "@ledgerhq/device-transport-kit-web-ble";
import { webHidIdentifier } from "@ledgerhq/device-transport-kit-web-hid";
import { Button, Flex } from "@ledgerhq/react-ui";
Expand Down Expand Up @@ -65,34 +66,40 @@ export const ConnectDeviceActions = ({
// also we should not have a different appearance when the mock server is enabled
// we should just display the list of active transports somewhere in the sidebar, discreetly

return transport === mockserverIdentifier ? (
<ConnectButton
onClick={() => onSelectDeviceClicked(mockserverIdentifier)}
variant="main"
backgroundColor="main"
size="large"
data-testid="CTA_select-device"
>
Select a device
</ConnectButton>
) : (
<Flex>
<ConnectButton
onClick={() => onSelectDeviceClicked(webHidIdentifier)}
variant="main"
backgroundColor="main"
size="large"
>
Select a USB device
</ConnectButton>
<ConnectButton
onClick={() => onSelectDeviceClicked(webBleIdentifier)}
variant="main"
backgroundColor="main"
size="large"
>
Select a BLE device
</ConnectButton>
</Flex>
);
switch (transport) {
case mockserverIdentifier:
case speculosIdentifier:
return (
<ConnectButton
onClick={() => onSelectDeviceClicked(transport)}
variant="main"
backgroundColor="main"
size="large"
data-testid="CTA_select-device"
>
Select a device
</ConnectButton>
);
default:
return (
<Flex>
<ConnectButton
onClick={() => onSelectDeviceClicked(webHidIdentifier)}
variant="main"
backgroundColor="main"
size="large"
>
Select a USB device
</ConnectButton>
<ConnectButton
onClick={() => onSelectDeviceClicked(webBleIdentifier)}
variant="main"
backgroundColor="main"
size="large"
>
Select a BLE device
</ConnectButton>
</Flex>
);
}
};
40 changes: 33 additions & 7 deletions apps/sample/src/providers/DeviceManagementKitProvider/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ import {
mockserverIdentifier,
mockserverTransportFactory,
} from "@ledgerhq/device-transport-kit-mockserver";
import {
speculosIdentifier,
speculosTransportFactory,
} from "@ledgerhq/device-transport-kit-speculos";
import { webBleTransportFactory } from "@ledgerhq/device-transport-kit-web-ble";
import { webHidTransportFactory } from "@ledgerhq/device-transport-kit-web-hid";

Expand All @@ -30,6 +34,16 @@ function buildDefaultDmk(logsExporter: WebLogsExporterLogger) {
.build();
}

//TODO add speculos URL to config
function buildSpeculosDmk(logsExporter: WebLogsExporterLogger) {
return new DeviceManagementKitBuilder()
.addTransport(speculosTransportFactory)
.addLogger(new ConsoleLogger())
.addLogger(logsExporter)
.addLogger(new FlipperDmkLogger())
.build();
}

function buildMockDmk(url: string, logsExporter: WebLogsExporterLogger) {
return new DeviceManagementKitBuilder()
.addTransport(mockserverTransportFactory)
Expand All @@ -46,23 +60,35 @@ export const DmkProvider: React.FC<PropsWithChildren> = ({ children }) => {
} = useDmkConfigContext();

const mockServerEnabled = transport === mockserverIdentifier;
const speculosEnabled = transport === speculosIdentifier;

const [state, setState] = useState(() => {
const logsExporter = new WebLogsExporterLogger();
const dmk = mockServerEnabled
? buildMockDmk(mockServerUrl, logsExporter)
: buildDefaultDmk(logsExporter);
const dmk = speculosEnabled
? buildSpeculosDmk(logsExporter)
: mockServerEnabled
? buildMockDmk(mockServerUrl, logsExporter)
: buildDefaultDmk(logsExporter);
return { dmk, logsExporter };
});

console.log("transport changed", transport);
const mockServerEnabledChanged = useHasChanged(mockServerEnabled);
const mockServerUrlChanged = useHasChanged(mockServerUrl);
const speculosEnabledChanged = useHasChanged(speculosEnabled);

if (mockServerEnabledChanged || mockServerUrlChanged) {
if (
mockServerEnabledChanged ||
mockServerUrlChanged ||
speculosEnabledChanged
) {
setState(({ logsExporter }) => {
return {
dmk: mockServerEnabled
? buildMockDmk(mockServerUrl, logsExporter)
: buildDefaultDmk(logsExporter),
dmk: speculosEnabled
? buildSpeculosDmk(logsExporter)
: mockServerEnabled
? buildMockDmk(mockServerUrl, logsExporter)
: buildDefaultDmk(logsExporter),
logsExporter,
};
});
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"transport-web-hid": "pnpm --filter @ledgerhq/device-transport-kit-web-hid",
"transport-web-ble": "pnpm --filter @ledgerhq/device-transport-kit-web-ble",
"transport-mockserver": "pnpm --filter @ledgerhq/device-transport-kit-mockserver",
"transport-speculos": "pnpm --filter @ledgerhq/device-transport-kit-speculos",
"flipper": "pnpm --filter @ledgerhq/device-management-kit-flipper-plugin-client",
"sample": "pnpm --filter @ledgerhq/device-management-kit-sample",
"doc": "pnpm --filter @ledgerhq/ledger-dmk-docs",
Expand Down
2 changes: 2 additions & 0 deletions packages/transport/speculos/.prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
lib/*
coverage/*
3 changes: 3 additions & 0 deletions packages/transport/speculos/.prettierrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = {
...require("@ledgerhq/prettier-config-dsdk"),
};
13 changes: 13 additions & 0 deletions packages/transport/speculos/eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import config from "@ledgerhq/eslint-config-dsdk";

export default [
...config,
{
ignores: ["eslint.config.mjs", "lib/*"],
languageOptions: {
parserOptions: {
project: "./tsconfig.json",
},
},
},
];
1 change: 1 addition & 0 deletions packages/transport/speculos/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./src/index";
25 changes: 25 additions & 0 deletions packages/transport/speculos/jest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/* eslint no-restricted-syntax: 0 */
import { type JestConfigWithTsJest, pathsToModuleNameMapper } from "ts-jest";

import { compilerOptions } from "./tsconfig.json";

const paths = pathsToModuleNameMapper(compilerOptions.paths, {
prefix: "<rootDir>/",
});

const config: JestConfigWithTsJest = {
preset: "@ledgerhq/jest-config-dsdk",
// setupFiles: ["<rootDir>/jest.setup.ts"],
testPathIgnorePatterns: ["<rootDir>/lib/esm/", "<rootDir>/lib/cjs/"],
collectCoverageFrom: [
"src/**/*.ts",
"!src/**/*.stub.ts",
"!src/index.ts",
"!src/api/index.ts",
],
moduleNameMapper: {
...paths,
},
};

export default config;
56 changes: 56 additions & 0 deletions packages/transport/speculos/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
{
"name": "@ledgerhq/device-transport-kit-speculos",
"version": "0.0.1",
"license": "Apache-2.0",
"private": true,
"exports": {
".": {
"types": "./lib/types/index.d.ts",
"import": "./lib/esm/index.js",
"require": "./lib/cjs/index.js"
},
"./*": {
"types": "./lib/types/*",
"import": "./lib/esm/*",
"require": "./lib/cjs/*"
}
},
"files": [
"./lib"
],
"scripts": {
"prebuild": "rimraf lib",
"build": "pnpm lmdk-build --entryPoints index.ts,src/**/*.ts --tsconfig tsconfig.prod.json",
"dev": "concurrently \"pnpm watch:builds\" \"pnpm watch:types\"",
"watch:builds": "pnpm lmdk-watch --entryPoints index.ts,src/**/*.ts --tsconfig tsconfig.prod.json",
"watch:types": "concurrently \"tsc --watch -p tsconfig.prod.json\" \"tsc-alias --watch -p tsconfig.prod.json\"",
"lint": "eslint",
"lint:fix": "pnpm lint --fix",
"postpack": "find . -name '*.tgz' -exec cp {} ../../../dist/ \\; ",
"prettier": "prettier . --check",
"prettier:fix": "prettier . --write",
"typecheck": "tsc --noEmit",
"test": "jest --passWithNoTests",
"test:watch": "pnpm test -- --watch",
"test:coverage": "pnpm test -- --coverage"
},
"dependencies": {
"@sentry/minimal": "^6.19.7",
"purify-ts": "^2.1.0",
"axios": "^1.7.9"
},
"devDependencies": {
"@ledgerhq/device-management-kit": "workspace:*",
"@ledgerhq/esbuild-tools": "workspace:*",
"@ledgerhq/eslint-config-dsdk": "workspace:*",
"@ledgerhq/jest-config-dsdk": "workspace:*",
"@ledgerhq/prettier-config-dsdk": "workspace:*",
"@ledgerhq/tsconfig-dsdk": "workspace:*",
"rxjs": "^7.8.1",
"ts-node": "^10.9.2"
},
"peerDependencies": {
"@ledgerhq/device-management-kit": "workspace:*",
"rxjs": "^7.8.1"
}
}
Loading

0 comments on commit 31f6c35

Please sign in to comment.