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

✨ (transport) [NO-ISSUE]: Create new speculos transport #601

Open
wants to merge 1 commit into
base: develop
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
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
Loading