diff --git a/README.md b/README.md index 152bd5c12..36d095e55 100644 --- a/README.md +++ b/README.md @@ -81,16 +81,18 @@ This project uses [turbo monorepo](https://turbo.build/repo/docs) to build and r A brief description of this project packages: -| Name | Path | Description | -|----------------------------------------|--------------------------------|------------------------------------------------------------------------------------------------------------------------------------------| -| @ledgerhq/device-management-kit-sample | apps/sample | React Next web app used to test & demonstrate the Web Device Management Kit | -| @ledgerhq/eslint-config-dsdk | packages/config/eslint | internal package which contains eslint shared config. Used by `extends: ["@ledgerhq/dsdk"]` in `.eslintrc`. | -| @ledgerhq/jest-config-dsdk | packages/config/jest | internal package which contains jest shared config. Used by `preset: "@ledgerhq/jest-config-dsdk"` in `jest.config.ts` | -| @ledgerhq/tsconfig-dsdk | packages/config/typescript | internal package which contains typescript shared config. Used by `"extends": "@ledgerhq/tsconfig-dsdk/tsconfig.sdk"` in `tsconfig.json` | -| @ledgerhq/device-management-kit | packages/device-management-kit | external package that contains the core of the Web Device Management Kit | -| @ledgerhq/device-signer-kit-ethereum | packages/signer/signer-eth | external package that contains device ethereum coin application dedicated handlers | -| @ledgerhq/device-signer-kit-solana | packages/signer/signer-solana | external package that contains device solana coin application dedicated handlers | -| @ledgerhq/device-management-kit-flipper-plugin-client | packages/flipper-plugin-client | external package that contains [flipper](https://github.com/facebook/flipper) logger for Device Management Kit | +| Name | Path | Description | +|-------------------------------------------------------|--------------------------------|------------------------------------------------------------------------------------------------------------------------------------------| +| @ledgerhq/device-management-kit-sample | apps/sample | React Next web app used to test & demonstrate the Web Device Management Kit | +| @ledgerhq/eslint-config-dsdk | packages/config/eslint | internal package which contains eslint shared config. Used by `extends: ["@ledgerhq/dsdk"]` in `.eslintrc`. | +| @ledgerhq/jest-config-dsdk | packages/config/jest | internal package which contains jest shared config. Used by `preset: "@ledgerhq/jest-config-dsdk"` in `jest.config.ts` | +| @ledgerhq/tsconfig-dsdk | packages/config/typescript | internal package which contains typescript shared config. Used by `"extends": "@ledgerhq/tsconfig-dsdk/tsconfig.sdk"` in `tsconfig.json` | +| @ledgerhq/device-management-kit | packages/device-management-kit | external package that contains the core of the Web Device Management Kit | +| @ledgerhq/device-signer-kit-ethereum | packages/signer/signer-eth | external package that contains device ethereum coin application dedicated handlers | +| @ledgerhq/device-signer-kit-solana | packages/signer/signer-solana | external package that contains device solana coin application dedicated handlers | +| @ledgerhq/device-management-kit-flipper-plugin-client | packages/flipper-plugin-client | external package that contains [flipper](https://github.com/facebook/flipper) logger for Device Management Kit | +| @ledgerhq/device-transport-kit-web-hid | packages/transport/web-hid | external package that contains the Web Hid transport implementation | +| @ledgerhq/device-transport-kit-web-ble | packages/transport/web-ble | external package that contains the Web Ble transport implementation | # Getting started @@ -275,7 +277,7 @@ pnpm hygen with-prompt | workspace | script | description | | --------- | --------------- | ------------------------------------- | -| 📦 core | `module:create` | scaffolds a new _src/internal_ module | +| 📦 dmk | `module:create` | scaffolds a new _src/internal_ module | ## Play with the sample app ? diff --git a/apps/sample/package.json b/apps/sample/package.json index c7a7344c3..f5eb7be8d 100644 --- a/apps/sample/package.json +++ b/apps/sample/package.json @@ -21,6 +21,8 @@ "@ledgerhq/device-signer-kit-ethereum": "workspace:*", "@ledgerhq/device-signer-kit-solana": "workspace:*", "@ledgerhq/device-transport-kit-mock-client": "workspace:*", + "@ledgerhq/device-transport-kit-web-ble": "workspace:*", + "@ledgerhq/device-transport-kit-web-hid": "workspace:*", "@ledgerhq/react-ui": "^0.16.2", "@sentry/nextjs": "^8.32.0", "@playwright/test": "^1.47.0", diff --git a/apps/sample/src/components/CommandsView/Command.tsx b/apps/sample/src/components/CommandsView/Command.tsx index 06311b8b8..2d86a35b5 100644 --- a/apps/sample/src/components/CommandsView/Command.tsx +++ b/apps/sample/src/components/CommandsView/Command.tsx @@ -145,13 +145,13 @@ export function Command< overflowY="scroll" data-testid="box_device-commands-responses" > - {responses.map(({ args, date, response, loading }, index) => ( + {responses.map(({ args, date, response, loading: l }, index) => ( ))} diff --git a/apps/sample/src/components/MainView/ConnectDeviceActions.tsx b/apps/sample/src/components/MainView/ConnectDeviceActions.tsx index 2334173ee..85eee9728 100644 --- a/apps/sample/src/components/MainView/ConnectDeviceActions.tsx +++ b/apps/sample/src/components/MainView/ConnectDeviceActions.tsx @@ -3,6 +3,8 @@ import { BuiltinTransports, type DmkError, } from "@ledgerhq/device-management-kit"; +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"; import styled from "styled-components"; @@ -26,7 +28,7 @@ export const ConnectDeviceActions = ({ const dmk = useDmk(); const onSelectDeviceClicked = useCallback( - (selectedTransport: BuiltinTransports) => { + (selectedTransport: string) => { onError(null); dmk.startDiscovering({ transport: selectedTransport }).subscribe({ next: (device) => { @@ -78,7 +80,7 @@ export const ConnectDeviceActions = ({ ) : ( onSelectDeviceClicked(BuiltinTransports.USB)} + onClick={() => onSelectDeviceClicked(webHidIdentifier)} variant="main" backgroundColor="main" size="large" @@ -86,7 +88,7 @@ export const ConnectDeviceActions = ({ Select a USB device onSelectDeviceClicked(BuiltinTransports.BLE)} + onClick={() => onSelectDeviceClicked(webBleIdentifier)} variant="main" backgroundColor="main" size="large" diff --git a/apps/sample/src/providers/DeviceManagementKitProvider/index.tsx b/apps/sample/src/providers/DeviceManagementKitProvider/index.tsx index f5ce22a18..5f26284aa 100644 --- a/apps/sample/src/providers/DeviceManagementKitProvider/index.tsx +++ b/apps/sample/src/providers/DeviceManagementKitProvider/index.tsx @@ -8,6 +8,8 @@ import { WebLogsExporterLogger, } from "@ledgerhq/device-management-kit"; import { FlipperDmkLogger } from "@ledgerhq/device-management-kit-flipper-plugin-client"; +import { WebBleTransport } from "@ledgerhq/device-transport-kit-web-ble"; +import { WebHidTransport } from "@ledgerhq/device-transport-kit-web-hid"; import { useHasChanged } from "@/hooks/useHasChanged"; import { useDmkConfigContext } from "@/providers/DmkConfig"; @@ -17,23 +19,49 @@ const LogsExporterContext = createContext(null); function buildDefaultDmk(logsExporter: WebLogsExporterLogger) { return new DeviceManagementKitBuilder() - .addTransport(BuiltinTransports.USB) - .addTransport(BuiltinTransports.BLE) + .addTransport( + ({ + deviceModelDataSource, + loggerServiceFactory, + apduSenderServiceFactory, + apduReceiverServiceFactory, + }) => + new WebHidTransport( + deviceModelDataSource, + loggerServiceFactory, + apduSenderServiceFactory, + apduReceiverServiceFactory, + ), + ) + .addTransport( + ({ + deviceModelDataSource, + loggerServiceFactory, + apduSenderServiceFactory, + apduReceiverServiceFactory, + }) => + new WebBleTransport( + deviceModelDataSource, + loggerServiceFactory, + apduSenderServiceFactory, + apduReceiverServiceFactory, + ), + ) .addLogger(new ConsoleLogger()) .addLogger(logsExporter) .addLogger(new FlipperDmkLogger()) .build(); } -function buildMockDmk(url: string, logsExporter: WebLogsExporterLogger) { - return new DeviceManagementKitBuilder() - .addTransport(BuiltinTransports.MOCK_SERVER) - .addLogger(new ConsoleLogger()) - .addLogger(logsExporter) - .addLogger(new FlipperDmkLogger()) - .addConfig({ mockUrl: url }) - .build(); -} +// function buildMockDmk(url: string, logsExporter: WebLogsExporterLogger) { +// return new DeviceManagementKitBuilder() +// .addTransport(BuiltinTransports.MOCK_SERVER) +// .addLogger(new ConsoleLogger()) +// .addLogger(logsExporter) +// .addLogger(new FlipperDmkLogger()) +// .addConfig({ mockUrl: url }) +// .build(); +// } export const DmkProvider: React.FC = ({ children }) => { const { @@ -43,9 +71,10 @@ export const DmkProvider: React.FC = ({ children }) => { const mockServerEnabled = transport === BuiltinTransports.MOCK_SERVER; const [state, setState] = useState(() => { const logsExporter = new WebLogsExporterLogger(); - const dmk = mockServerEnabled - ? buildMockDmk(mockServerUrl, logsExporter) - : buildDefaultDmk(logsExporter); + // const dmk = mockServerEnabled + // ? buildMockDmk(mockServerUrl, logsExporter) + // : buildDefaultDmk(logsExporter); + const dmk = buildDefaultDmk(logsExporter); return { dmk, logsExporter }; }); @@ -55,9 +84,10 @@ export const DmkProvider: React.FC = ({ children }) => { if (mockServerEnabledChanged || mockServerUrlChanged) { setState(({ logsExporter }) => { return { - dmk: mockServerEnabled - ? buildMockDmk(mockServerUrl, logsExporter) - : buildDefaultDmk(logsExporter), + dmk: buildDefaultDmk(logsExporter), + // dmk: mockServerEnabled + // ? buildMockDmk(mockServerUrl, logsExporter) + // : buildDefaultDmk(logsExporter), logsExporter, }; }); diff --git a/package.json b/package.json index 32fec506f..32ad74c1b 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "scripts": { "build": "turbo run build", "build:libs": "turbo run build --filter=./packages/**", - "dev": "turbo run dev", + "dev": "turbo run dev --concurrency 20", "lint": "turbo run lint", "lint:fix": "turbo run lint:fix", "prettier": "turbo run prettier", @@ -20,9 +20,13 @@ "signer-btc": "pnpm --filter @ledgerhq/signer-btc", "signer-eth": "pnpm --filter @ledgerhq/device-signer-kit-ethereum", "signer-solana": "pnpm --filter @ledgerhq/device-signer-kit-solana", + "signer-utils": "pnpm --filter @ledgerhq/signer-utils", "mock-client": "pnpm --filter @ledgerhq/device-transport-kit-mock-client", "trusted-apps": "pnpm --filter @ledgerhq/device-sdk-trusted-apps", "ui": "pnpm --filter @ledgerhq/device-sdk-ui", + "transports": "pnpm --filter @ledgerhq/device-transport-*", + "transport-web-hid": "pnpm --filter @ledgerhq/device-transport-kit-web-hid", + "transport-web-ble": "pnpm --filter @ledgerhq/device-transport-kit-web-ble", "flipper": "pnpm --filter @ledgerhq/device-management-kit-flipper-plugin-client", "sample": "pnpm --filter @ledgerhq/device-management-kit-sample", "bump": "changeset version", @@ -52,7 +56,7 @@ "rimraf": "^6.0.1", "ts-jest": "^29.2.5", "tsc-alias": "^1.8.10", - "turbo": "^2.2.1", + "turbo": "^2.2.3", "typescript": "^5.6.3", "zx": "^8.1.9" }, diff --git a/packages/device-management-kit/eslint.config.mjs b/packages/device-management-kit/eslint.config.mjs index 5d90c9c2e..aeffc26fa 100644 --- a/packages/device-management-kit/eslint.config.mjs +++ b/packages/device-management-kit/eslint.config.mjs @@ -3,7 +3,7 @@ import config from "@ledgerhq/eslint-config-dsdk"; export default [ ...config, { - ignores: ["eslint.config.mjs", "scripts/*.mjs"], + ignores: ["eslint.config.mjs", "scripts/*.mjs", "lib/*"], languageOptions: { parserOptions: { project: "./tsconfig.json", diff --git a/packages/device-management-kit/package.json b/packages/device-management-kit/package.json index e55809b9f..66cd4db7f 100644 --- a/packages/device-management-kit/package.json +++ b/packages/device-management-kit/package.json @@ -44,11 +44,13 @@ "inversify-logger-middleware": "^3.1.0", "purify-ts": "^2.1.0", "reflect-metadata": "^0.2.2", - "rxjs": "^7.8.1", "semver": "^7.6.3", "uuid": "^10.0.0", "xstate": "^5.18.2" }, + "peerDependencies": { + "rxjs": "^7.8.1" + }, "devDependencies": { "@ledgerhq/esbuild-tools": "workspace:*", "@ledgerhq/eslint-config-dsdk": "workspace:*", @@ -57,8 +59,7 @@ "@ledgerhq/tsconfig-dsdk": "workspace:*", "@types/semver": "^7.5.8", "@types/uuid": "^10.0.0", - "@types/w3c-web-hid": "^1.0.6", - "@types/web-bluetooth": "^0.0.20", + "rxjs": "^7.8.1", "ts-node": "^10.9.2" } } diff --git a/packages/device-management-kit/src/api/DeviceManagementKit.ts b/packages/device-management-kit/src/api/DeviceManagementKit.ts index 5499d6152..e461f8dcd 100644 --- a/packages/device-management-kit/src/api/DeviceManagementKit.ts +++ b/packages/device-management-kit/src/api/DeviceManagementKit.ts @@ -59,7 +59,6 @@ export class DeviceManagementKit { constructor({ stub, transports, - customTransports, loggers, config, }: Partial = {}) { @@ -69,7 +68,6 @@ export class DeviceManagementKit { this.container = makeContainer({ stub, transports, - customTransports, loggers, config, }); diff --git a/packages/device-management-kit/src/api/DeviceManagementKitBuilder.ts b/packages/device-management-kit/src/api/DeviceManagementKitBuilder.ts index e8c49120a..4954412a3 100644 --- a/packages/device-management-kit/src/api/DeviceManagementKitBuilder.ts +++ b/packages/device-management-kit/src/api/DeviceManagementKitBuilder.ts @@ -4,8 +4,7 @@ import { } from "@internal/manager-api/model/Const"; import { type LoggerSubscriberService } from "./logger-subscriber/service/LoggerSubscriberService"; -import { type Transport } from "./transport/model/Transport"; -import { type BuiltinTransports } from "./transport/model/TransportIdentifier"; +import { type TransportFactory } from "./transport/model/Transport"; import { DeviceManagementKit } from "./DeviceManagementKit"; import { type DmkConfig } from "./DmkConfig"; @@ -16,8 +15,8 @@ import { type DmkConfig } from "./DmkConfig"; * ``` * const dmk = new LedgerDeviceManagementKitBuilder() * .setStub(false) - * .addTransport(BuiltinTransports.USB) - * .addCustomTransport(new MyTransport()) + * .addTransport((args) => transportFactory(args)) + * .addTransport(transportFactory) * .addLogger(myLogger) * .build(); * ``` @@ -25,8 +24,7 @@ import { type DmkConfig } from "./DmkConfig"; export class DeviceManagementKitBuilder { private stub = false; private readonly loggers: LoggerSubscriberService[] = []; - private readonly transports: BuiltinTransports[] = []; - private readonly customTransports: Transport[] = []; + private readonly transports: TransportFactory[] = []; private config: DmkConfig = { managerApiUrl: DEFAULT_MANAGER_API_BASE_URL, mockUrl: DEFAULT_MOCK_SERVER_BASE_URL, @@ -36,7 +34,6 @@ export class DeviceManagementKitBuilder { return new DeviceManagementKit({ stub: this.stub, transports: this.transports, - customTransports: this.customTransports, loggers: this.loggers, config: this.config, }); @@ -47,16 +44,11 @@ export class DeviceManagementKitBuilder { return this; } - addTransport(transport: BuiltinTransports): DeviceManagementKitBuilder { + addTransport(transport: TransportFactory): DeviceManagementKitBuilder { this.transports.push(transport); return this; } - addCustomTransport(transport: Transport): DeviceManagementKitBuilder { - this.customTransports.push(transport); - return this; - } - /** * Add a logger to the SDK that will receive its logs */ diff --git a/packages/device-management-kit/src/api/apdu/utils/ApduParser.test.ts b/packages/device-management-kit/src/api/apdu/utils/ApduParser.test.ts index 8ede0fad6..84c03c46c 100644 --- a/packages/device-management-kit/src/api/apdu/utils/ApduParser.test.ts +++ b/packages/device-management-kit/src/api/apdu/utils/ApduParser.test.ts @@ -41,7 +41,7 @@ describe("ApduParser", () => { }); it("Test zero length", () => { - const response: ApduResponse = new ApduResponse({ + response = new ApduResponse({ statusCode: STATUS_WORD_SUCCESS, data: RESPONSE_LV_ZERO, }); diff --git a/packages/device-management-kit/src/api/command/use-case/SendCommandUseCase.test.ts b/packages/device-management-kit/src/api/command/use-case/SendCommandUseCase.test.ts index d39728bc3..8877b7c80 100644 --- a/packages/device-management-kit/src/api/command/use-case/SendCommandUseCase.test.ts +++ b/packages/device-management-kit/src/api/command/use-case/SendCommandUseCase.test.ts @@ -2,11 +2,11 @@ import { Left } from "purify-ts"; import { type Command } from "@api/command/Command"; import { CommandResultStatus } from "@api/command/model/CommandResult"; +import { type LoggerPublisherService } from "@api/logger-publisher/service/LoggerPublisherService"; import { deviceSessionStubBuilder } from "@internal/device-session/model/DeviceSession.stub"; import { DefaultDeviceSessionService } from "@internal/device-session/service/DefaultDeviceSessionService"; import { type DeviceSessionService } from "@internal/device-session/service/DeviceSessionService"; import { DefaultLoggerPublisherService } from "@internal/logger-publisher/service/DefaultLoggerPublisherService"; -import { type LoggerPublisherService } from "@internal/logger-publisher/service/LoggerPublisherService"; import { AxiosManagerApiDataSource } from "@internal/manager-api/data/AxiosManagerApiDataSource"; import { type ManagerApiDataSource } from "@internal/manager-api/data/ManagerApiDataSource"; import { DefaultManagerApiService } from "@internal/manager-api/service/DefaultManagerApiService"; diff --git a/packages/device-management-kit/src/api/command/use-case/SendCommandUseCase.ts b/packages/device-management-kit/src/api/command/use-case/SendCommandUseCase.ts index 998382ece..085841e18 100644 --- a/packages/device-management-kit/src/api/command/use-case/SendCommandUseCase.ts +++ b/packages/device-management-kit/src/api/command/use-case/SendCommandUseCase.ts @@ -2,10 +2,10 @@ import { inject, injectable } from "inversify"; import { Command } from "@api/command/Command"; import { CommandResult } from "@api/command/model/CommandResult"; +import { type LoggerPublisherService } from "@api/logger-publisher/service/LoggerPublisherService"; import { deviceSessionTypes } from "@internal/device-session/di/deviceSessionTypes"; import type { DeviceSessionService } from "@internal/device-session/service/DeviceSessionService"; import { loggerTypes } from "@internal/logger-publisher/di/loggerTypes"; -import { LoggerPublisherService } from "@internal/logger-publisher/service/LoggerPublisherService"; export type SendCommandUseCaseArgs = { /** diff --git a/packages/device-management-kit/src/api/device-action/use-case/ExecuteDeviceActionUseCase.ts b/packages/device-management-kit/src/api/device-action/use-case/ExecuteDeviceActionUseCase.ts index 475fbad5e..4363193c3 100644 --- a/packages/device-management-kit/src/api/device-action/use-case/ExecuteDeviceActionUseCase.ts +++ b/packages/device-management-kit/src/api/device-action/use-case/ExecuteDeviceActionUseCase.ts @@ -6,10 +6,10 @@ import { ExecuteDeviceActionReturnType, } from "@api/device-action/DeviceAction"; import { DmkError } from "@api/Error"; +import { LoggerPublisherService } from "@api/logger-publisher/service/LoggerPublisherService"; import { deviceSessionTypes } from "@internal/device-session/di/deviceSessionTypes"; import type { DeviceSessionService } from "@internal/device-session/service/DeviceSessionService"; import { loggerTypes } from "@internal/logger-publisher/di/loggerTypes"; -import { LoggerPublisherService } from "@internal/logger-publisher/service/LoggerPublisherService"; export type ExecuteDeviceActionUseCaseArgs< Output, diff --git a/packages/device-management-kit/src/api/device-model/data/DeviceModelDataSource.ts b/packages/device-management-kit/src/api/device-model/data/DeviceModelDataSource.ts new file mode 100644 index 000000000..c628ecbb1 --- /dev/null +++ b/packages/device-management-kit/src/api/device-model/data/DeviceModelDataSource.ts @@ -0,0 +1,20 @@ +import { type DeviceModelId } from "@api/device/DeviceModel"; +import { type BleDeviceInfos } from "@api/device-model/model/BleDeviceInfos"; +import { type TransportDeviceModel } from "@api/device-model/model/DeviceModel"; + +/** + * Source of truth for the device models + */ +export interface DeviceModelDataSource { + getAllDeviceModels(): TransportDeviceModel[]; + + getDeviceModel(params: { id: DeviceModelId }): TransportDeviceModel; + + filterDeviceModels( + params: Partial, + ): TransportDeviceModel[]; + + getBluetoothServicesInfos(): Record; + + getBluetoothServices(): string[]; +} diff --git a/packages/device-management-kit/src/internal/device-model/data/StaticDeviceModelDataSource.test.ts b/packages/device-management-kit/src/api/device-model/data/StaticDeviceModelDataSource.test.ts similarity index 98% rename from packages/device-management-kit/src/internal/device-model/data/StaticDeviceModelDataSource.test.ts rename to packages/device-management-kit/src/api/device-model/data/StaticDeviceModelDataSource.test.ts index b450b1fd5..f7aaa5c71 100644 --- a/packages/device-management-kit/src/internal/device-model/data/StaticDeviceModelDataSource.test.ts +++ b/packages/device-management-kit/src/api/device-model/data/StaticDeviceModelDataSource.test.ts @@ -1,5 +1,5 @@ import { DeviceModelId } from "@api/device/DeviceModel"; -import { BleDeviceInfos } from "@internal/transport/ble/model/BleDeviceInfos"; +import { BleDeviceInfos } from "@api/device-model/model/BleDeviceInfos"; import { StaticDeviceModelDataSource } from "./StaticDeviceModelDataSource"; diff --git a/packages/device-management-kit/src/internal/device-model/data/StaticDeviceModelDataSource.ts b/packages/device-management-kit/src/api/device-model/data/StaticDeviceModelDataSource.ts similarity index 82% rename from packages/device-management-kit/src/internal/device-model/data/StaticDeviceModelDataSource.ts rename to packages/device-management-kit/src/api/device-model/data/StaticDeviceModelDataSource.ts index 57b7a9d86..f055ee295 100644 --- a/packages/device-management-kit/src/internal/device-model/data/StaticDeviceModelDataSource.ts +++ b/packages/device-management-kit/src/api/device-model/data/StaticDeviceModelDataSource.ts @@ -1,10 +1,9 @@ import { injectable } from "inversify"; import { DeviceModelId } from "@api/device/DeviceModel"; -import { InternalDeviceModel } from "@internal/device-model/model/DeviceModel"; -import { BleDeviceInfos } from "@internal/transport/ble/model/BleDeviceInfos"; - -import { DeviceModelDataSource } from "./DeviceModelDataSource"; +import { DeviceModelDataSource } from "@api/device-model/data/DeviceModelDataSource"; +import { BleDeviceInfos } from "@api/device-model/model/BleDeviceInfos"; +import { TransportDeviceModel } from "@api/device-model/model/DeviceModel"; /** * Static/in memory implementation of the device model data source @@ -12,9 +11,9 @@ import { DeviceModelDataSource } from "./DeviceModelDataSource"; @injectable() export class StaticDeviceModelDataSource implements DeviceModelDataSource { private static deviceModelByIds: { - [key in DeviceModelId]: InternalDeviceModel; + [key in DeviceModelId]: TransportDeviceModel; } = { - [DeviceModelId.NANO_S]: new InternalDeviceModel({ + [DeviceModelId.NANO_S]: new TransportDeviceModel({ id: DeviceModelId.NANO_S, productName: "Ledger Nano S", usbProductId: 0x10, @@ -23,7 +22,7 @@ export class StaticDeviceModelDataSource implements DeviceModelDataSource { memorySize: 320 * 1024, masks: [0x31100000], }), - [DeviceModelId.NANO_SP]: new InternalDeviceModel({ + [DeviceModelId.NANO_SP]: new TransportDeviceModel({ id: DeviceModelId.NANO_SP, productName: "Ledger Nano S Plus", usbProductId: 0x50, @@ -32,7 +31,7 @@ export class StaticDeviceModelDataSource implements DeviceModelDataSource { memorySize: 1533 * 1024, masks: [0x33100000], }), - [DeviceModelId.NANO_X]: new InternalDeviceModel({ + [DeviceModelId.NANO_X]: new TransportDeviceModel({ id: DeviceModelId.NANO_X, productName: "Ledger Nano X", usbProductId: 0x40, @@ -49,7 +48,7 @@ export class StaticDeviceModelDataSource implements DeviceModelDataSource { }, ], }), - [DeviceModelId.STAX]: new InternalDeviceModel({ + [DeviceModelId.STAX]: new TransportDeviceModel({ id: DeviceModelId.STAX, productName: "Ledger Stax", usbProductId: 0x60, @@ -66,7 +65,7 @@ export class StaticDeviceModelDataSource implements DeviceModelDataSource { }, ], }), - [DeviceModelId.FLEX]: new InternalDeviceModel({ + [DeviceModelId.FLEX]: new TransportDeviceModel({ id: DeviceModelId.FLEX, productName: "Ledger Flex", usbProductId: 0x70, @@ -85,11 +84,11 @@ export class StaticDeviceModelDataSource implements DeviceModelDataSource { }), }; - getAllDeviceModels(): InternalDeviceModel[] { + getAllDeviceModels(): TransportDeviceModel[] { return Object.values(StaticDeviceModelDataSource.deviceModelByIds); } - getDeviceModel(params: { id: DeviceModelId }): InternalDeviceModel { + getDeviceModel(params: { id: DeviceModelId }): TransportDeviceModel { return StaticDeviceModelDataSource.deviceModelByIds[params.id]; } @@ -97,11 +96,11 @@ export class StaticDeviceModelDataSource implements DeviceModelDataSource { * Returns the list of device models that match all the given parameters */ filterDeviceModels( - params: Partial, - ): InternalDeviceModel[] { + params: Partial, + ): TransportDeviceModel[] { return this.getAllDeviceModels().filter((deviceModel) => { return Object.entries(params).every(([key, value]) => { - return deviceModel[key as keyof InternalDeviceModel] === value; + return deviceModel[key as keyof TransportDeviceModel] === value; }); }); } diff --git a/packages/device-management-kit/src/internal/transport/ble/model/BleDeviceInfos.ts b/packages/device-management-kit/src/api/device-model/model/BleDeviceInfos.ts similarity index 58% rename from packages/device-management-kit/src/internal/transport/ble/model/BleDeviceInfos.ts rename to packages/device-management-kit/src/api/device-model/model/BleDeviceInfos.ts index 1485dd91c..9f3d31ad9 100644 --- a/packages/device-management-kit/src/internal/transport/ble/model/BleDeviceInfos.ts +++ b/packages/device-management-kit/src/api/device-model/model/BleDeviceInfos.ts @@ -1,8 +1,8 @@ -import { type InternalDeviceModel } from "@internal/device-model/model/DeviceModel"; +import { type TransportDeviceModel } from "@api/device-model/model/DeviceModel"; export class BleDeviceInfos { constructor( - public deviceModel: InternalDeviceModel, + public deviceModel: TransportDeviceModel, public serviceUuid: string, public writeUuid: string, public writeCmdUuid: string, diff --git a/packages/device-management-kit/src/internal/device-model/model/DeviceModel.stub.ts b/packages/device-management-kit/src/api/device-model/model/DeviceModel.stub.ts similarity index 80% rename from packages/device-management-kit/src/internal/device-model/model/DeviceModel.stub.ts rename to packages/device-management-kit/src/api/device-model/model/DeviceModel.stub.ts index 66ef91958..7bc5d10a2 100644 --- a/packages/device-management-kit/src/internal/device-model/model/DeviceModel.stub.ts +++ b/packages/device-management-kit/src/api/device-model/model/DeviceModel.stub.ts @@ -1,9 +1,9 @@ import { DeviceModelId } from "@api/device/DeviceModel"; -import { type InternalDeviceModel } from "@internal/device-model/model/DeviceModel"; +import { type TransportDeviceModel } from "@api/device-model/model/DeviceModel"; export function deviceModelStubBuilder( - props: Partial = {}, -): InternalDeviceModel { + props: Partial = {}, +): TransportDeviceModel { return { id: DeviceModelId.NANO_X, productName: "Ledger Nano X", diff --git a/packages/device-management-kit/src/internal/device-model/model/DeviceModel.test.ts b/packages/device-management-kit/src/api/device-model/model/DeviceModel.test.ts similarity index 79% rename from packages/device-management-kit/src/internal/device-model/model/DeviceModel.test.ts rename to packages/device-management-kit/src/api/device-model/model/DeviceModel.test.ts index 323420f8d..dcf887076 100644 --- a/packages/device-management-kit/src/internal/device-model/model/DeviceModel.test.ts +++ b/packages/device-management-kit/src/api/device-model/model/DeviceModel.test.ts @@ -1,24 +1,24 @@ import { DeviceModelId } from "@api/device/DeviceModel"; -import { InternalDeviceModel } from "./DeviceModel"; +import { TransportDeviceModel } from "./DeviceModel"; import { deviceModelStubBuilder } from "./DeviceModel.stub"; describe("DeviceModel", () => { - let stubDeviceModel: InternalDeviceModel; + let stubDeviceModel: TransportDeviceModel; beforeAll(() => { stubDeviceModel = deviceModelStubBuilder(); }); test("should return the correct block size for Nano X", () => { - const deviceModel = new InternalDeviceModel(stubDeviceModel); + const deviceModel = new TransportDeviceModel(stubDeviceModel); const firmwareVersion = "2.0.0"; expect(deviceModel.getBlockSize(firmwareVersion)).toBe(4 * 1024); }); test("should return the correct block size for Stax", () => { - const deviceModel = new InternalDeviceModel({ + const deviceModel = new TransportDeviceModel({ ...stubDeviceModel, id: DeviceModelId.STAX, }); @@ -28,7 +28,7 @@ describe("DeviceModel", () => { }); test("should return the correct block size for Nano SP", () => { - const deviceModel = new InternalDeviceModel({ + const deviceModel = new TransportDeviceModel({ ...stubDeviceModel, id: DeviceModelId.NANO_SP, }); @@ -38,7 +38,7 @@ describe("DeviceModel", () => { }); test("should return the correct block size for Nano S with version lower than 2.0.0", () => { - const deviceModel = new InternalDeviceModel({ + const deviceModel = new TransportDeviceModel({ ...stubDeviceModel, id: DeviceModelId.NANO_S, }); @@ -48,7 +48,7 @@ describe("DeviceModel", () => { }); test("should return the correct block size for Nano S with version 2.0.0", () => { - const deviceModel = new InternalDeviceModel({ + const deviceModel = new TransportDeviceModel({ ...stubDeviceModel, id: DeviceModelId.NANO_S, }); @@ -59,7 +59,7 @@ describe("DeviceModel", () => { // flex test("should return the correct block size for Flex", () => { - const deviceModel = new InternalDeviceModel({ + const deviceModel = new TransportDeviceModel({ ...stubDeviceModel, id: DeviceModelId.FLEX, }); diff --git a/packages/device-management-kit/src/internal/device-model/model/DeviceModel.ts b/packages/device-management-kit/src/api/device-model/model/DeviceModel.ts similarity index 97% rename from packages/device-management-kit/src/internal/device-model/model/DeviceModel.ts rename to packages/device-management-kit/src/api/device-model/model/DeviceModel.ts index 390785f07..047b51f20 100644 --- a/packages/device-management-kit/src/internal/device-model/model/DeviceModel.ts +++ b/packages/device-management-kit/src/api/device-model/model/DeviceModel.ts @@ -5,7 +5,7 @@ import { DeviceModelId } from "@api/device/DeviceModel"; /** * The info of a device model */ -export class InternalDeviceModel { +export class TransportDeviceModel { id: DeviceModelId; productName: string; usbProductId: number; diff --git a/packages/device-management-kit/src/internal/device-session/data/FramerConst.ts b/packages/device-management-kit/src/api/device-session/data/FramerConst.ts similarity index 100% rename from packages/device-management-kit/src/internal/device-session/data/FramerConst.ts rename to packages/device-management-kit/src/api/device-session/data/FramerConst.ts diff --git a/packages/device-management-kit/src/internal/device-session/service/ApduReceiverService.ts b/packages/device-management-kit/src/api/device-session/service/ApduReceiverService.ts similarity index 60% rename from packages/device-management-kit/src/internal/device-session/service/ApduReceiverService.ts rename to packages/device-management-kit/src/api/device-session/service/ApduReceiverService.ts index d6b56f139..e965b074c 100644 --- a/packages/device-management-kit/src/internal/device-session/service/ApduReceiverService.ts +++ b/packages/device-management-kit/src/api/device-session/service/ApduReceiverService.ts @@ -6,3 +6,11 @@ import { type DmkError } from "@api/Error"; export interface ApduReceiverService { handleFrame(apdu: Uint8Array): Either>; } + +export type ApduReceiverConstructorArgs = { + channel?: Maybe; +}; + +export type ApduReceiverServiceFactory = ( + args?: ApduReceiverConstructorArgs, +) => ApduReceiverService; diff --git a/packages/device-management-kit/src/api/device-session/service/ApduSenderService.ts b/packages/device-management-kit/src/api/device-session/service/ApduSenderService.ts new file mode 100644 index 000000000..0d8bd34f3 --- /dev/null +++ b/packages/device-management-kit/src/api/device-session/service/ApduSenderService.ts @@ -0,0 +1,16 @@ +import { type Maybe } from "purify-ts"; + +import { type Frame } from "@internal/device-session/model/Frame"; + +export interface ApduSenderService { + getFrames: (apdu: Uint8Array) => Frame[]; +} +export type ApduSenderServiceConstructorArgs = { + frameSize: number; + channel?: Maybe; + padding?: boolean; +}; + +export type ApduSenderServiceFactory = ( + args: ApduSenderServiceConstructorArgs, +) => ApduSenderService; diff --git a/packages/device-management-kit/src/api/device-session/service/DefaultApduReceiverService.stub.ts b/packages/device-management-kit/src/api/device-session/service/DefaultApduReceiverService.stub.ts new file mode 100644 index 000000000..e588a7a3e --- /dev/null +++ b/packages/device-management-kit/src/api/device-session/service/DefaultApduReceiverService.stub.ts @@ -0,0 +1,18 @@ +import { Maybe } from "purify-ts"; + +import { type ApduReceiverService } from "@api/device-session/service/ApduReceiverService"; +import { type ApduReceiverConstructorArgs } from "@api/device-session/service/ApduReceiverService"; +import { type LoggerPublisherService } from "@api/logger-publisher/service/LoggerPublisherService"; +import { DefaultApduReceiverService } from "@internal/device-session/service/DefaultApduReceiverService"; + +export const defaultApduReceiverServiceStubBuilder = ( + props: Partial = {}, + loggerFactory: (tag: string) => LoggerPublisherService, +): ApduReceiverService => + new DefaultApduReceiverService( + { + channel: Maybe.of(new Uint8Array([0x12, 0x34])), + ...props, + }, + loggerFactory, + ); diff --git a/packages/device-management-kit/src/api/device-session/service/DefaultApduSenderService.stub.ts b/packages/device-management-kit/src/api/device-session/service/DefaultApduSenderService.stub.ts new file mode 100644 index 000000000..d3e615da3 --- /dev/null +++ b/packages/device-management-kit/src/api/device-session/service/DefaultApduSenderService.stub.ts @@ -0,0 +1,20 @@ +import { Maybe } from "purify-ts"; + +import { type ApduSenderService } from "@api/device-session/service/ApduSenderService"; +import { type ApduSenderServiceConstructorArgs } from "@api/device-session/service/ApduSenderService"; +import { type LoggerPublisherService } from "@api/logger-publisher/service/LoggerPublisherService"; +import { DefaultApduSenderService } from "@internal/device-session/service/DefaultApduSenderService"; + +export const defaultApduSenderServiceStubBuilder = ( + props: Partial = {}, + loggerFactory: (tag: string) => LoggerPublisherService, +): ApduSenderService => + new DefaultApduSenderService( + { + frameSize: 64, + channel: Maybe.of(new Uint8Array([0x12, 0x34])), + padding: true, + ...props, + }, + loggerFactory, + ); diff --git a/packages/device-management-kit/src/internal/device-session/utils/FramerUtils.test.ts b/packages/device-management-kit/src/api/device-session/utils/FramerUtils.test.ts similarity index 100% rename from packages/device-management-kit/src/internal/device-session/utils/FramerUtils.test.ts rename to packages/device-management-kit/src/api/device-session/utils/FramerUtils.test.ts diff --git a/packages/device-management-kit/src/internal/device-session/utils/FramerUtils.ts b/packages/device-management-kit/src/api/device-session/utils/FramerUtils.ts similarity index 100% rename from packages/device-management-kit/src/internal/device-session/utils/FramerUtils.ts rename to packages/device-management-kit/src/api/device-session/utils/FramerUtils.ts diff --git a/packages/device-management-kit/src/api/device/DeviceModel.ts b/packages/device-management-kit/src/api/device/DeviceModel.ts index c2fefbf03..309b60eb5 100644 --- a/packages/device-management-kit/src/api/device/DeviceModel.ts +++ b/packages/device-management-kit/src/api/device/DeviceModel.ts @@ -33,3 +33,5 @@ export class DeviceModel { this.name = name; } } + +export const LEDGER_VENDOR_ID = 0x2c97; diff --git a/packages/device-management-kit/src/api/index.ts b/packages/device-management-kit/src/api/index.ts index 1de6aa487..152fa91f5 100644 --- a/packages/device-management-kit/src/api/index.ts +++ b/packages/device-management-kit/src/api/index.ts @@ -1,57 +1,54 @@ "use strict"; -export { Apdu } from "./apdu/model/Apdu"; -export { APDU_MAX_PAYLOAD, ApduBuilder } from "./apdu/utils/ApduBuilder"; -export { ApduParser } from "./apdu/utils/ApduParser"; -export { ByteArrayBuilder } from "./apdu/utils/ByteArrayBuilder"; -export { ByteArrayParser } from "./apdu/utils/ByteArrayParser"; +export { Apdu } from "@api/apdu/model/Apdu"; +export { APDU_MAX_PAYLOAD, ApduBuilder } from "@api/apdu/utils/ApduBuilder"; +export { ApduParser } from "@api/apdu/utils/ApduParser"; +export * from "@api/apdu/utils/AppBuilderError"; +export { ByteArrayBuilder } from "@api/apdu/utils/ByteArrayBuilder"; +export { ByteArrayParser } from "@api/apdu/utils/ByteArrayParser"; +export { InvalidStatusWordError } from "@api/command/Errors"; export { CommandResultFactory, CommandResultStatus, isSuccessCommandResult, -} from "./command/model/CommandResult"; -export { CloseAppCommand } from "./command/os/CloseAppCommand"; +} from "@api/command/model/CommandResult"; +export { CloseAppCommand } from "@api/command/os/CloseAppCommand"; export { GetAppAndVersionCommand, type GetAppAndVersionResponse, -} from "./command/os/GetAppAndVersionCommand"; +} from "@api/command/os/GetAppAndVersionCommand"; export { BatteryStatusType, type GetBatteryStatusArgs, GetBatteryStatusCommand, type GetBatteryStatusResponse, -} from "./command/os/GetBatteryStatusCommand"; +} from "@api/command/os/GetBatteryStatusCommand"; export { GetOsVersionCommand, type GetOsVersionResponse, -} from "./command/os/GetOsVersionCommand"; +} from "@api/command/os/GetOsVersionCommand"; export { type ListAppsArgs, ListAppsCommand, type ListAppsErrorCodes, type ListAppsResponse, -} from "./command/os/ListAppsCommand"; -export { type OpenAppArgs, OpenAppCommand } from "./command/os/OpenAppCommand"; -export { isCommandErrorCode } from "./command/utils/CommandErrors"; -export { CommandUtils } from "./command/utils/CommandUtils"; +} from "@api/command/os/ListAppsCommand"; +export { + type OpenAppArgs, + OpenAppCommand, +} from "@api/command/os/OpenAppCommand"; +export { isCommandErrorCode } from "@api/command/utils/CommandErrors"; +export { CommandUtils } from "@api/command/utils/CommandUtils"; export { GlobalCommandError, GlobalCommandErrorHandler, -} from "./command/utils/GlobalCommandError"; -export { DeviceModel, DeviceModelId } from "./device/DeviceModel"; -export { DeviceStatus } from "./device/DeviceStatus"; -export { ApduResponse } from "./device-session/ApduResponse"; -export { DeviceManagementKit } from "./DeviceManagementKit"; -export { DeviceManagementKitBuilder } from "./DeviceManagementKitBuilder"; -export { DeviceExchangeError, UnknownDeviceExchangeError } from "./Error"; -export { LogLevel } from "./logger-subscriber/model/LogLevel"; -export { ConsoleLogger } from "./logger-subscriber/service/ConsoleLogger"; -export { WebLogsExporterLogger } from "./logger-subscriber/service/WebLogsExporterLogger"; -export { ConnectedDevice } from "./transport/model/ConnectedDevice"; -export { BuiltinTransports } from "./transport/model/TransportIdentifier"; -export * from "./types"; -export * from "@api/apdu/utils/AppBuilderError"; -export { InvalidStatusWordError } from "@api/command/Errors"; +} from "@api/command/utils/GlobalCommandError"; +export { + DeviceModel, + DeviceModelId, + LEDGER_VENDOR_ID, +} from "@api/device/DeviceModel"; +export { DeviceStatus } from "@api/device/DeviceStatus"; export { type DeviceAction, type DeviceActionIntermediateValue, @@ -117,11 +114,44 @@ export { type DeviceActionStateMachine, XStateDeviceAction, } from "@api/device-action/xstate-utils/XStateDeviceAction"; +export { type DeviceModelDataSource } from "@api/device-model/data/DeviceModelDataSource"; +export { StaticDeviceModelDataSource } from "@api/device-model/data/StaticDeviceModelDataSource"; +export { BleDeviceInfos } from "@api/device-model/model/BleDeviceInfos"; +export { TransportDeviceModel } from "@api/device-model/model/DeviceModel"; +export { ApduResponse } from "@api/device-session/ApduResponse"; +export * from "@api/device-session/data/FramerConst"; export { type DeviceSessionState, DeviceSessionStateType, } from "@api/device-session/DeviceSessionState"; +export { type ApduReceiverService } from "@api/device-session/service/ApduReceiverService"; +export { type ApduReceiverServiceFactory } from "@api/device-session/service/ApduReceiverService"; +export { type ApduSenderServiceFactory } from "@api/device-session/service/ApduSenderService"; +export { type ApduSenderService } from "@api/device-session/service/ApduSenderService"; +// TODO: remove from exported +export { defaultApduReceiverServiceStubBuilder } from "@api/device-session/service/DefaultApduReceiverService.stub"; +export { defaultApduSenderServiceStubBuilder } from "@api/device-session/service/DefaultApduSenderService.stub"; +export { FramerUtils } from "@api/device-session/utils/FramerUtils"; +export { DeviceManagementKit } from "@api/DeviceManagementKit"; +export { DeviceManagementKitBuilder } from "@api/DeviceManagementKitBuilder"; +export { DeviceExchangeError, UnknownDeviceExchangeError } from "@api/Error"; export { type DmkError } from "@api/Error"; +export { type LoggerPublisherService } from "@api/logger-publisher/service/LoggerPublisherService"; +export { LogLevel } from "@api/logger-subscriber/model/LogLevel"; +export { ConsoleLogger } from "@api/logger-subscriber/service/ConsoleLogger"; +export { WebLogsExporterLogger } from "@api/logger-subscriber/service/WebLogsExporterLogger"; +export { ConnectedDevice } from "@api/transport/model/ConnectedDevice"; +export { + type DeviceConnection, + type DisconnectHandler, + type SendApduFnType, +} from "@api/transport/model/DeviceConnection"; +export * from "@api/transport/model/Errors"; +export { TransportConnectedDevice } from "@api/transport/model/TransportConnectedDevice"; +export { connectedDeviceStubBuilder } from "@api/transport/model/TransportConnectedDevice.stub"; +export { type TransportDiscoveredDevice } from "@api/transport/model/TransportDiscoveredDevice"; +export { BuiltinTransports } from "@api/transport/model/TransportIdentifier"; +export * from "@api/types"; export { base64StringToBuffer, isBase64String } from "@api/utils/Base64String"; export { bufferToHexaString, diff --git a/packages/device-management-kit/src/internal/logger-publisher/service/LoggerPublisherService.ts b/packages/device-management-kit/src/api/logger-publisher/service/LoggerPublisherService.ts similarity index 100% rename from packages/device-management-kit/src/internal/logger-publisher/service/LoggerPublisherService.ts rename to packages/device-management-kit/src/api/logger-publisher/service/LoggerPublisherService.ts diff --git a/packages/device-management-kit/src/api/logger-subscriber/service/WebLogsExporterLogger.test.ts b/packages/device-management-kit/src/api/logger-subscriber/service/WebLogsExporterLogger.test.ts index 07fa5e009..b8d0991fd 100644 --- a/packages/device-management-kit/src/api/logger-subscriber/service/WebLogsExporterLogger.test.ts +++ b/packages/device-management-kit/src/api/logger-subscriber/service/WebLogsExporterLogger.test.ts @@ -1,8 +1,8 @@ +import { deviceModelStubBuilder } from "@api/device-model/model/DeviceModel.stub"; +import { type TransportConnectedDevice } from "@api/transport/model/TransportConnectedDevice"; import { BuiltinTransports } from "@api/transport/model/TransportIdentifier"; -import { deviceModelStubBuilder } from "@internal/device-model/model/DeviceModel.stub"; import { DeviceSession } from "@internal/device-session/model/DeviceSession"; import { type ManagerApiService } from "@internal/manager-api/service/ManagerApiService"; -import { type InternalConnectedDevice } from "@internal/transport/model/InternalConnectedDevice"; import { getJSONStringifyReplacer } from "./WebLogsExporterLogger"; @@ -22,7 +22,7 @@ describe("getJSONStringifyReplacer", () => { const stubDeviceModel = deviceModelStubBuilder(); const replacer = getJSONStringifyReplacer(); - const connectedDevice: InternalConnectedDevice = { + const connectedDevice: TransportConnectedDevice = { deviceModel: deviceModelStubBuilder(), type: "USB", id: "mockedDeviceId", diff --git a/packages/device-management-kit/src/api/logger-subscriber/__mocks__/ConsoleLogger.ts b/packages/device-management-kit/src/api/logger-subscriber/service/__mocks__/ConsoleLogger.ts similarity index 100% rename from packages/device-management-kit/src/api/logger-subscriber/__mocks__/ConsoleLogger.ts rename to packages/device-management-kit/src/api/logger-subscriber/service/__mocks__/ConsoleLogger.ts diff --git a/packages/device-management-kit/src/api/transport/model/ConnectedDevice.ts b/packages/device-management-kit/src/api/transport/model/ConnectedDevice.ts index 2fc259206..2a4706687 100644 --- a/packages/device-management-kit/src/api/transport/model/ConnectedDevice.ts +++ b/packages/device-management-kit/src/api/transport/model/ConnectedDevice.ts @@ -1,9 +1,9 @@ import { type DeviceId, type DeviceModelId } from "@api/device/DeviceModel"; import { type ConnectionType } from "@api/discovery/ConnectionType"; -import { type InternalConnectedDevice } from "@internal/transport/model/InternalConnectedDevice"; +import { type TransportConnectedDevice } from "@api/transport/model/TransportConnectedDevice"; type ConnectedDeviceConstructorArgs = { - readonly internalConnectedDevice: InternalConnectedDevice; + readonly transportConnectedDevice: TransportConnectedDevice; }; export class ConnectedDevice { @@ -13,7 +13,7 @@ export class ConnectedDevice { public readonly type: ConnectionType; constructor({ - internalConnectedDevice: { + transportConnectedDevice: { id, deviceModel: { id: deviceModelId, productName: deviceName }, type, diff --git a/packages/device-management-kit/src/internal/transport/model/DeviceConnection.ts b/packages/device-management-kit/src/api/transport/model/DeviceConnection.ts similarity index 100% rename from packages/device-management-kit/src/internal/transport/model/DeviceConnection.ts rename to packages/device-management-kit/src/api/transport/model/DeviceConnection.ts diff --git a/packages/device-management-kit/src/api/transport/model/DeviceConnectionFactory.stub.ts b/packages/device-management-kit/src/api/transport/model/DeviceConnectionFactory.stub.ts new file mode 100644 index 000000000..67d6e2aed --- /dev/null +++ b/packages/device-management-kit/src/api/transport/model/DeviceConnectionFactory.stub.ts @@ -0,0 +1,7 @@ +export class DeviceConnectionFactoryStub { + constructor() {} + + create = jest.fn().mockImplementation(() => ({ + sendApdu: jest.fn(), + })); +} diff --git a/packages/device-management-kit/src/internal/transport/model/Errors.ts b/packages/device-management-kit/src/api/transport/model/Errors.ts similarity index 74% rename from packages/device-management-kit/src/internal/transport/model/Errors.ts rename to packages/device-management-kit/src/api/transport/model/Errors.ts index 037fbb8b2..1a611db45 100644 --- a/packages/device-management-kit/src/internal/transport/model/Errors.ts +++ b/packages/device-management-kit/src/api/transport/model/Errors.ts @@ -1,16 +1,11 @@ import { type DmkError } from "@api/Error"; -export type PromptDeviceAccessError = - | UsbHidTransportNotSupportedError - | BleTransportNotSupportedError - | NoAccessibleDeviceError; - export type ConnectError = | UnknownDeviceError | OpeningConnectionError | DeviceAlreadyConnectedError; -class GeneralDmkError implements DmkError { +export class GeneralDmkError implements DmkError { _tag = "GeneralDmkError"; originalError?: unknown; constructor(err?: unknown) { @@ -22,6 +17,13 @@ class GeneralDmkError implements DmkError { } } +export class DeviceAlreadyConnectedError extends GeneralDmkError { + override readonly _tag = "DeviceAlreadyDiscoveredError"; + constructor(readonly err?: unknown) { + super(err); + } +} + export class DeviceNotRecognizedError extends GeneralDmkError { override readonly _tag = "DeviceNotRecognizedError"; constructor(readonly err?: unknown) { @@ -57,20 +59,6 @@ export class TransportNotSupportedError extends GeneralDmkError { } } -export class BleTransportNotSupportedError extends GeneralDmkError { - override readonly _tag = "BleTransportNotSupportedError"; - constructor(readonly err?: unknown) { - super(err); - } -} - -export class UsbHidTransportNotSupportedError extends GeneralDmkError { - override readonly _tag = "UsbHidTransportNotSupportedError"; - constructor(readonly err?: unknown) { - super(err); - } -} - export class SendApduConcurrencyError extends GeneralDmkError { override readonly _tag = "SendApduConcurrencyError"; constructor(readonly err?: unknown) { @@ -92,13 +80,6 @@ export class ReconnectionFailedError extends GeneralDmkError { } } -export class HidSendReportError extends GeneralDmkError { - override readonly _tag = "HidSendReportError"; - constructor(readonly err?: unknown) { - super(err); - } -} - export class DeviceNotInitializedError extends GeneralDmkError { override readonly _tag = "DeviceNotInitializedError"; constructor(readonly err?: unknown) { @@ -106,15 +87,15 @@ export class DeviceNotInitializedError extends GeneralDmkError { } } -export class BleDeviceGattServerError extends GeneralDmkError { - override readonly _tag = "BleDeviceGattServerError"; +export class NoTransportsProvidedError extends GeneralDmkError { + override readonly _tag = "NoTransportsProvidedError"; constructor(readonly err?: unknown) { super(err); } } -export class DeviceAlreadyConnectedError extends GeneralDmkError { - override readonly _tag = "DeviceAlreadyDiscoveredError"; +export class TransportAlreadyExistsError extends GeneralDmkError { + override readonly _tag = "TransportAlreadyExistsError"; constructor(readonly err?: unknown) { super(err); } diff --git a/packages/device-management-kit/src/api/transport/model/Transport.stub.ts b/packages/device-management-kit/src/api/transport/model/Transport.stub.ts new file mode 100644 index 000000000..e2f29ddf9 --- /dev/null +++ b/packages/device-management-kit/src/api/transport/model/Transport.stub.ts @@ -0,0 +1,12 @@ +import { type Transport } from "./Transport"; + +export class TransportStub implements Transport { + constructor() {} + getIdentifier = jest.fn(); + isSupported = jest.fn(); + startDiscovering = jest.fn(); + stopDiscovering = jest.fn(); + listenToKnownDevices = jest.fn(); + connect = jest.fn(); + disconnect = jest.fn(); +} diff --git a/packages/device-management-kit/src/api/transport/model/Transport.ts b/packages/device-management-kit/src/api/transport/model/Transport.ts index 8a3ff8225..d6ff95646 100644 --- a/packages/device-management-kit/src/api/transport/model/Transport.ts +++ b/packages/device-management-kit/src/api/transport/model/Transport.ts @@ -2,11 +2,17 @@ import { type Either } from "purify-ts"; import { type Observable } from "rxjs"; import { type DeviceId } from "@api/device/DeviceModel"; +import { type DeviceModelDataSource } from "@api/device-model/data/DeviceModelDataSource"; +import { type ApduReceiverServiceFactory } from "@api/device-session/service/ApduReceiverService"; +import { type ApduSenderServiceFactory } from "@api/device-session/service/ApduSenderService"; +import { type DmkConfig } from "@api/DmkConfig"; import { type DmkError } from "@api/Error"; +import { type LoggerPublisherService } from "@api/logger-publisher/service/LoggerPublisherService"; +import { type ConnectError } from "@api/transport/model/Errors"; +import { type TransportDiscoveredDevice } from "@api/transport/model/TransportDiscoveredDevice"; import { type TransportIdentifier } from "@api/transport/model/TransportIdentifier"; -import { type ConnectError } from "@internal/transport/model/Errors"; -import { type InternalConnectedDevice } from "@internal/transport/model/InternalConnectedDevice"; -import { type InternalDiscoveredDevice } from "@internal/transport/model/InternalDiscoveredDevice"; + +import { type TransportConnectedDevice } from "./TransportConnectedDevice"; export type DisconnectHandler = (deviceId: DeviceId) => void; @@ -21,11 +27,11 @@ export interface Transport { isSupported(): boolean; - startDiscovering(): Observable; + startDiscovering(): Observable; stopDiscovering(): void; - listenToKnownDevices(): Observable; + listenToKnownDevices(): Observable; /** * Enables communication with the device by connecting to it. @@ -36,9 +42,19 @@ export interface Transport { connect(params: { deviceId: DeviceId; onDisconnect: DisconnectHandler; - }): Promise>; + }): Promise>; disconnect(params: { - connectedDevice: InternalConnectedDevice; + connectedDevice: TransportConnectedDevice; }): Promise>; } + +export type TransportArgs = { + deviceModelDataSource: DeviceModelDataSource; + loggerServiceFactory: (tag: string) => LoggerPublisherService; + config: DmkConfig; + apduSenderServiceFactory: ApduSenderServiceFactory; + apduReceiverServiceFactory: ApduReceiverServiceFactory; +}; + +export type TransportFactory = (args: TransportArgs) => Transport; diff --git a/packages/device-management-kit/src/internal/transport/model/InternalConnectedDevice.stub.ts b/packages/device-management-kit/src/api/transport/model/TransportConnectedDevice.stub.ts similarity index 68% rename from packages/device-management-kit/src/internal/transport/model/InternalConnectedDevice.stub.ts rename to packages/device-management-kit/src/api/transport/model/TransportConnectedDevice.stub.ts index a9fafacb2..d9f051eb1 100644 --- a/packages/device-management-kit/src/internal/transport/model/InternalConnectedDevice.stub.ts +++ b/packages/device-management-kit/src/api/transport/model/TransportConnectedDevice.stub.ts @@ -1,17 +1,18 @@ import { Right } from "purify-ts"; +import { deviceModelStubBuilder } from "@api/device-model/model/DeviceModel.stub"; import { defaultApduResponseStubBuilder } from "@api/device-session/ApduResponse.stub"; -import { deviceModelStubBuilder } from "@internal/device-model/model/DeviceModel.stub"; + import { type ConnectedDeviceConstructorArgs, - InternalConnectedDevice, -} from "@internal/transport/model/InternalConnectedDevice"; + TransportConnectedDevice, +} from "./TransportConnectedDevice"; export function connectedDeviceStubBuilder( props: Partial = {}, -): InternalConnectedDevice { +): TransportConnectedDevice { const deviceModel = deviceModelStubBuilder(); - return new InternalConnectedDevice({ + return new TransportConnectedDevice({ deviceModel, id: "42", type: "MOCK", diff --git a/packages/device-management-kit/src/internal/transport/model/InternalConnectedDevice.test.ts b/packages/device-management-kit/src/api/transport/model/TransportConnectedDevice.test.ts similarity index 65% rename from packages/device-management-kit/src/internal/transport/model/InternalConnectedDevice.test.ts rename to packages/device-management-kit/src/api/transport/model/TransportConnectedDevice.test.ts index 922f49845..6540e1974 100644 --- a/packages/device-management-kit/src/internal/transport/model/InternalConnectedDevice.test.ts +++ b/packages/device-management-kit/src/api/transport/model/TransportConnectedDevice.test.ts @@ -1,10 +1,11 @@ +import { deviceModelStubBuilder } from "@api/device-model/model/DeviceModel.stub"; import { defaultApduResponseStubBuilder } from "@api/device-session/ApduResponse.stub"; -import { deviceModelStubBuilder } from "@internal/device-model/model/DeviceModel.stub"; -import { InternalConnectedDevice } from "@internal/transport/model/InternalConnectedDevice"; -import { connectedDeviceStubBuilder } from "@internal/transport/model/InternalConnectedDevice.stub"; -describe("InternalConnectedDevice", () => { - let connectedDevice: InternalConnectedDevice; +import { TransportConnectedDevice } from "./TransportConnectedDevice"; +import { connectedDeviceStubBuilder } from "./TransportConnectedDevice.stub"; + +describe("TransportConnectedDevice", () => { + let connectedDevice: TransportConnectedDevice; beforeEach(() => { connectedDevice = connectedDeviceStubBuilder(); @@ -12,7 +13,7 @@ describe("InternalConnectedDevice", () => { it("should create an instance", () => { expect(connectedDevice).toBeDefined(); - expect(connectedDevice).toBeInstanceOf(InternalConnectedDevice); + expect(connectedDevice).toBeInstanceOf(TransportConnectedDevice); }); it("should return the correct id", () => { diff --git a/packages/device-management-kit/src/internal/transport/model/InternalConnectedDevice.ts b/packages/device-management-kit/src/api/transport/model/TransportConnectedDevice.ts similarity index 74% rename from packages/device-management-kit/src/internal/transport/model/InternalConnectedDevice.ts rename to packages/device-management-kit/src/api/transport/model/TransportConnectedDevice.ts index 4e97b4a56..9d3c89bc9 100644 --- a/packages/device-management-kit/src/internal/transport/model/InternalConnectedDevice.ts +++ b/packages/device-management-kit/src/api/transport/model/TransportConnectedDevice.ts @@ -1,23 +1,23 @@ import { type DeviceId } from "@api/device/DeviceModel"; +import { type TransportDeviceModel } from "@api/device-model/model/DeviceModel"; import { type ConnectionType } from "@api/discovery/ConnectionType"; +import { type SendApduFnType } from "@api/transport/model/DeviceConnection"; import { type TransportIdentifier } from "@api/transport/model/TransportIdentifier"; -import { type InternalDeviceModel } from "@internal/device-model/model/DeviceModel"; -import { type SendApduFnType } from "@internal/transport/model/DeviceConnection"; /** * The internal connected device. */ export type ConnectedDeviceConstructorArgs = { id: DeviceId; - deviceModel: InternalDeviceModel; + deviceModel: TransportDeviceModel; type: ConnectionType; transport: TransportIdentifier; sendApdu: SendApduFnType; }; -export class InternalConnectedDevice { +export class TransportConnectedDevice { public readonly id: DeviceId; - public readonly deviceModel: InternalDeviceModel; + public readonly deviceModel: TransportDeviceModel; public readonly sendApdu: SendApduFnType; public readonly type: ConnectionType; public readonly transport: TransportIdentifier; diff --git a/packages/device-management-kit/src/internal/transport/model/InternalDiscoveredDevice.ts b/packages/device-management-kit/src/api/transport/model/TransportDiscoveredDevice.ts similarity index 80% rename from packages/device-management-kit/src/internal/transport/model/InternalDiscoveredDevice.ts rename to packages/device-management-kit/src/api/transport/model/TransportDiscoveredDevice.ts index 469c2d3bf..6429ee27c 100644 --- a/packages/device-management-kit/src/internal/transport/model/InternalDiscoveredDevice.ts +++ b/packages/device-management-kit/src/api/transport/model/TransportDiscoveredDevice.ts @@ -1,11 +1,11 @@ import { type DeviceId } from "@api/device/DeviceModel"; +import { type TransportDeviceModel } from "@api/device-model/model/DeviceModel"; import { type TransportIdentifier } from "@api/transport/model/TransportIdentifier"; -import { type InternalDeviceModel } from "@internal/device-model/model/DeviceModel"; /** * A discovered / scanned (not yet connected to) device. */ -export type InternalDiscoveredDevice = { +export type TransportDiscoveredDevice = { // type: "web-hid", // "node-hid" in the future -> no need as we will only have 1 USB transport implementation running /** @@ -16,6 +16,6 @@ export type InternalDiscoveredDevice = { * privacy feature of Ledger devices. */ id: DeviceId; - deviceModel: InternalDeviceModel; + deviceModel: TransportDeviceModel; transport: TransportIdentifier; }; diff --git a/packages/device-management-kit/src/di.ts b/packages/device-management-kit/src/di.ts index d4c85a931..9e99041cb 100644 --- a/packages/device-management-kit/src/di.ts +++ b/packages/device-management-kit/src/di.ts @@ -8,8 +8,7 @@ import { deviceActionModuleFactory } from "@api/device-action/di/deviceActionMod // import { makeLoggerMiddleware } from "inversify-logger-middleware"; import { type DmkConfig } from "@api/DmkConfig"; import { type LoggerSubscriberService } from "@api/logger-subscriber/service/LoggerSubscriberService"; -import { type Transport } from "@api/transport/model/Transport"; -import { type BuiltinTransports } from "@api/transport/model/TransportIdentifier"; +import { type TransportFactory } from "@api/transport/model/Transport"; import { configModuleFactory } from "@internal/config/di/configModule"; import { deviceModelModuleFactory } from "@internal/device-model/di/deviceModelModule"; import { deviceSessionModuleFactory } from "@internal/device-session/di/deviceSessionModule"; @@ -22,16 +21,14 @@ import { } from "@internal/manager-api/model/Const"; import { sendModuleFactory } from "@internal/send/di/sendModule"; import { transportModuleFactory } from "@internal/transport//di/transportModule"; -import { bleModuleFactory } from "@internal/transport/ble/di/bleModule"; -import { usbModuleFactory } from "@internal/transport/usb/di/usbModule"; // Uncomment this line to enable the logger middleware // const logger = makeLoggerMiddleware(); export type MakeContainerProps = { stub: boolean; - transports: BuiltinTransports[]; - customTransports: Transport[]; + transports: TransportFactory[]; + customTransports: TransportFactory[]; loggers: LoggerSubscriberService[]; config: DmkConfig; }; @@ -39,7 +36,6 @@ export type MakeContainerProps = { export const makeContainer = ({ stub = false, transports = [], - customTransports = [], loggers = [], config = { managerApiUrl: DEFAULT_MANAGER_API_BASE_URL, @@ -54,8 +50,7 @@ export const makeContainer = ({ container.load( configModuleFactory({ stub }), deviceModelModuleFactory({ stub }), - transportModuleFactory({ stub, transports, customTransports, config }), - usbModuleFactory({ stub }), + transportModuleFactory({ stub, transports, config }), managerApiModuleFactory({ stub, config }), discoveryModuleFactory({ stub }), loggerModuleFactory({ subscribers: loggers }), @@ -63,7 +58,6 @@ export const makeContainer = ({ sendModuleFactory({ stub }), commandModuleFactory({ stub }), deviceActionModuleFactory({ stub }), - bleModuleFactory(), // modules go here ); diff --git a/packages/device-management-kit/src/internal/config/service/DefaultConfigService.ts b/packages/device-management-kit/src/internal/config/service/DefaultConfigService.ts index 0c538e2c7..d25c4a61b 100644 --- a/packages/device-management-kit/src/internal/config/service/DefaultConfigService.ts +++ b/packages/device-management-kit/src/internal/config/service/DefaultConfigService.ts @@ -1,5 +1,6 @@ import { inject, injectable } from "inversify"; +import type { LoggerPublisherService } from "@api/logger-publisher/service/LoggerPublisherService"; import type { LocalConfigDataSource, RemoteConfigDataSource, @@ -11,7 +12,6 @@ import { RemoteConfigFailure, } from "@internal/config/model/Errors"; import { loggerTypes } from "@internal/logger-publisher/di/loggerTypes"; -import type { LoggerPublisherService } from "@internal/logger-publisher/service/LoggerPublisherService"; import { ConfigService } from "./ConfigService"; diff --git a/packages/device-management-kit/src/internal/device-model/data/DeviceModelDataSource.ts b/packages/device-management-kit/src/internal/device-model/data/DeviceModelDataSource.ts deleted file mode 100644 index 8d0d9001e..000000000 --- a/packages/device-management-kit/src/internal/device-model/data/DeviceModelDataSource.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { type DeviceModelId } from "@api/device/DeviceModel"; -import { type InternalDeviceModel } from "@internal/device-model/model/DeviceModel"; -import { type BleDeviceInfos } from "@internal/transport/ble/model/BleDeviceInfos"; - -/** - * Source of truth for the device models - */ -export interface DeviceModelDataSource { - getAllDeviceModels(): InternalDeviceModel[]; - - getDeviceModel(params: { id: DeviceModelId }): InternalDeviceModel; - - filterDeviceModels( - params: Partial, - ): InternalDeviceModel[]; - - getBluetoothServicesInfos(): Record; - - getBluetoothServices(): string[]; -} diff --git a/packages/device-management-kit/src/internal/device-model/di/deviceModelModule.test.ts b/packages/device-management-kit/src/internal/device-model/di/deviceModelModule.test.ts index 86301bc64..435b10583 100644 --- a/packages/device-management-kit/src/internal/device-model/di/deviceModelModule.test.ts +++ b/packages/device-management-kit/src/internal/device-model/di/deviceModelModule.test.ts @@ -1,6 +1,6 @@ import { Container } from "inversify"; -import { StaticDeviceModelDataSource } from "@internal/device-model/data/StaticDeviceModelDataSource"; +import { StaticDeviceModelDataSource } from "@api/device-model/data/StaticDeviceModelDataSource"; import { deviceModelModuleFactory } from "./deviceModelModule"; import { deviceModelTypes } from "./deviceModelTypes"; diff --git a/packages/device-management-kit/src/internal/device-model/di/deviceModelModule.ts b/packages/device-management-kit/src/internal/device-model/di/deviceModelModule.ts index 2d9d6ffed..7ddff8e0b 100644 --- a/packages/device-management-kit/src/internal/device-model/di/deviceModelModule.ts +++ b/packages/device-management-kit/src/internal/device-model/di/deviceModelModule.ts @@ -1,6 +1,6 @@ import { ContainerModule } from "inversify"; -import { StaticDeviceModelDataSource } from "@internal/device-model/data/StaticDeviceModelDataSource"; +import { StaticDeviceModelDataSource } from "@api/device-model/data/StaticDeviceModelDataSource"; import { deviceModelTypes } from "./deviceModelTypes"; diff --git a/packages/device-management-kit/src/internal/device-session/di/deviceSessionModule.ts b/packages/device-management-kit/src/internal/device-session/di/deviceSessionModule.ts index 9d5c64fbe..76faffab6 100644 --- a/packages/device-management-kit/src/internal/device-session/di/deviceSessionModule.ts +++ b/packages/device-management-kit/src/internal/device-session/di/deviceSessionModule.ts @@ -1,21 +1,17 @@ import { ContainerModule, type interfaces } from "inversify"; -import { type ApduReceiverService } from "@internal/device-session/service/ApduReceiverService"; -import { type ApduSenderService } from "@internal/device-session/service/ApduSenderService"; -import { - type DefaultApduReceiverConstructorArgs, - DefaultApduReceiverService, -} from "@internal/device-session/service/DefaultApduReceiverService"; -import { - DefaultApduSenderService, - type DefaultApduSenderServiceConstructorArgs, -} from "@internal/device-session/service/DefaultApduSenderService"; +import { type ApduReceiverService } from "@api/device-session/service/ApduReceiverService"; +import { type ApduReceiverConstructorArgs } from "@api/device-session/service/ApduReceiverService"; +import { type ApduSenderService } from "@api/device-session/service/ApduSenderService"; +import { type ApduSenderServiceConstructorArgs } from "@api/device-session/service/ApduSenderService"; +import { type LoggerPublisherService } from "@api/logger-publisher/service/LoggerPublisherService"; +import { DefaultApduReceiverService } from "@internal/device-session/service/DefaultApduReceiverService"; +import { DefaultApduSenderService } from "@internal/device-session/service/DefaultApduSenderService"; import { DefaultDeviceSessionService } from "@internal/device-session/service/DefaultDeviceSessionService"; import { CloseSessionsUseCase } from "@internal/device-session/use-case/CloseSessionsUseCase"; import { GetDeviceSessionStateUseCase } from "@internal/device-session/use-case/GetDeviceSessionStateUseCase"; import { ListDeviceSessionsUseCase } from "@internal/device-session/use-case/ListDeviceSessionsUseCase"; import { loggerTypes } from "@internal/logger-publisher/di/loggerTypes"; -import { type LoggerPublisherService } from "@internal/logger-publisher/service/LoggerPublisherService"; import { StubUseCase } from "@root/src/di.stub"; import { deviceSessionTypes } from "./deviceSessionTypes"; @@ -44,7 +40,7 @@ export const deviceSessionModuleFactory = ( (name: string) => LoggerPublisherService >(loggerTypes.LoggerPublisherServiceFactory); - return (args: DefaultApduSenderServiceConstructorArgs) => { + return (args: ApduSenderServiceConstructorArgs) => { return new DefaultApduSenderService(args, logger); }; }); @@ -56,7 +52,7 @@ export const deviceSessionModuleFactory = ( (name: string) => LoggerPublisherService >(loggerTypes.LoggerPublisherServiceFactory); - return (args: DefaultApduReceiverConstructorArgs = {}) => { + return (args: ApduReceiverConstructorArgs = {}) => { return new DefaultApduReceiverService(args, logger); }; }); diff --git a/packages/device-management-kit/src/internal/device-session/model/DeviceSession.stub.ts b/packages/device-management-kit/src/internal/device-session/model/DeviceSession.stub.ts index a21d9c148..c02517f49 100644 --- a/packages/device-management-kit/src/internal/device-session/model/DeviceSession.stub.ts +++ b/packages/device-management-kit/src/internal/device-session/model/DeviceSession.stub.ts @@ -1,10 +1,10 @@ +import { type LoggerPublisherService } from "@api/logger-publisher/service/LoggerPublisherService"; +import { connectedDeviceStubBuilder } from "@api/transport/model/TransportConnectedDevice.stub"; import { DeviceSession, type SessionConstructorArgs, } from "@internal/device-session/model/DeviceSession"; -import { type LoggerPublisherService } from "@internal/logger-publisher/service/LoggerPublisherService"; import type { ManagerApiService } from "@internal/manager-api/service/ManagerApiService"; -import { connectedDeviceStubBuilder } from "@internal/transport/model/InternalConnectedDevice.stub"; export const deviceSessionStubBuilder = ( props: Partial = {}, diff --git a/packages/device-management-kit/src/internal/device-session/model/DeviceSession.ts b/packages/device-management-kit/src/internal/device-session/model/DeviceSession.ts index 2481525cf..4285bae1f 100644 --- a/packages/device-management-kit/src/internal/device-session/model/DeviceSession.ts +++ b/packages/device-management-kit/src/internal/device-session/model/DeviceSession.ts @@ -18,14 +18,14 @@ import { } from "@api/device-session/DeviceSessionState"; import { type DeviceSessionId } from "@api/device-session/types"; import { type DmkError } from "@api/Error"; -import { type LoggerPublisherService } from "@internal/logger-publisher/service/LoggerPublisherService"; +import { type LoggerPublisherService } from "@api/logger-publisher/service/LoggerPublisherService"; +import { type TransportConnectedDevice } from "@api/transport/model/TransportConnectedDevice"; import { type ManagerApiService } from "@internal/manager-api/service/ManagerApiService"; -import { type InternalConnectedDevice } from "@internal/transport/model/InternalConnectedDevice"; import { DeviceSessionRefresher } from "./DeviceSessionRefresher"; export type SessionConstructorArgs = { - connectedDevice: InternalConnectedDevice; + connectedDevice: TransportConnectedDevice; id?: DeviceSessionId; }; @@ -34,7 +34,7 @@ export type SessionConstructorArgs = { */ export class DeviceSession { private readonly _id: DeviceSessionId; - private readonly _connectedDevice: InternalConnectedDevice; + private readonly _connectedDevice: TransportConnectedDevice; private readonly _deviceState: BehaviorSubject; private readonly _refresher: DeviceSessionRefresher; private readonly _managerApiService: ManagerApiService; diff --git a/packages/device-management-kit/src/internal/device-session/model/DeviceSessionRefresher.test.ts b/packages/device-management-kit/src/internal/device-session/model/DeviceSessionRefresher.test.ts index 54cfd27d0..70c95a9cc 100644 --- a/packages/device-management-kit/src/internal/device-session/model/DeviceSessionRefresher.test.ts +++ b/packages/device-management-kit/src/internal/device-session/model/DeviceSessionRefresher.test.ts @@ -7,8 +7,8 @@ import { } from "@api/command/os/GetAppAndVersionCommand"; import { DeviceStatus } from "@api/device/DeviceStatus"; import { type ApduResponse } from "@api/device-session/ApduResponse"; +import { type LoggerPublisherService } from "@api/logger-publisher/service/LoggerPublisherService"; import { DefaultLoggerPublisherService } from "@internal/logger-publisher/service/DefaultLoggerPublisherService"; -import { type LoggerPublisherService } from "@internal/logger-publisher/service/LoggerPublisherService"; import { DeviceSessionRefresher } from "./DeviceSessionRefresher"; diff --git a/packages/device-management-kit/src/internal/device-session/model/DeviceSessionRefresher.ts b/packages/device-management-kit/src/internal/device-session/model/DeviceSessionRefresher.ts index 8b4abf18c..269b65842 100644 --- a/packages/device-management-kit/src/internal/device-session/model/DeviceSessionRefresher.ts +++ b/packages/device-management-kit/src/internal/device-session/model/DeviceSessionRefresher.ts @@ -11,7 +11,7 @@ import { DeviceSessionStateType, } from "@api/device-session/DeviceSessionState"; import { DmkError } from "@api/Error"; -import { type LoggerPublisherService } from "@internal/logger-publisher/service/LoggerPublisherService"; +import { type LoggerPublisherService } from "@api/logger-publisher/service/LoggerPublisherService"; /** * The arguments for the DeviceSessionRefresher. diff --git a/packages/device-management-kit/src/internal/device-session/model/FrameHeader.ts b/packages/device-management-kit/src/internal/device-session/model/FrameHeader.ts index 334463d60..bb5fdca37 100644 --- a/packages/device-management-kit/src/internal/device-session/model/FrameHeader.ts +++ b/packages/device-management-kit/src/internal/device-session/model/FrameHeader.ts @@ -1,6 +1,6 @@ import { type Maybe } from "purify-ts"; -import { FramerUtils } from "@internal/device-session/utils/FramerUtils"; +import { FramerUtils } from "@api/device-session/utils/FramerUtils"; type FrameHeaderConstructorArgs = { uuid: string; diff --git a/packages/device-management-kit/src/internal/device-session/service/ApduSenderService.ts b/packages/device-management-kit/src/internal/device-session/service/ApduSenderService.ts deleted file mode 100644 index 2065a5ea8..000000000 --- a/packages/device-management-kit/src/internal/device-session/service/ApduSenderService.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { type Frame } from "@internal/device-session/model/Frame"; - -export interface ApduSenderService { - getFrames: (apdu: Uint8Array) => Frame[]; -} diff --git a/packages/device-management-kit/src/internal/device-session/service/DefaultApduReceiverService.stub.ts b/packages/device-management-kit/src/internal/device-session/service/DefaultApduReceiverService.stub.ts deleted file mode 100644 index cda3fea24..000000000 --- a/packages/device-management-kit/src/internal/device-session/service/DefaultApduReceiverService.stub.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Maybe } from "purify-ts"; - -import { type ApduReceiverService } from "@internal/device-session/service/ApduReceiverService"; -import { - type DefaultApduReceiverConstructorArgs, - DefaultApduReceiverService, -} from "@internal/device-session/service/DefaultApduReceiverService"; -import { type LoggerPublisherService } from "@internal/logger-publisher/service/LoggerPublisherService"; - -export const defaultApduReceiverServiceStubBuilder = ( - props: Partial = {}, - loggerFactory: (tag: string) => LoggerPublisherService, -): ApduReceiverService => - new DefaultApduReceiverService( - { - channel: Maybe.of(new Uint8Array([0x12, 0x34])), - ...props, - }, - loggerFactory, - ); diff --git a/packages/device-management-kit/src/internal/device-session/service/DefaultApduReceiverService.test.ts b/packages/device-management-kit/src/internal/device-session/service/DefaultApduReceiverService.test.ts index 5a34df690..c0829545f 100644 --- a/packages/device-management-kit/src/internal/device-session/service/DefaultApduReceiverService.test.ts +++ b/packages/device-management-kit/src/internal/device-session/service/DefaultApduReceiverService.test.ts @@ -4,10 +4,10 @@ jest.mock("uuid"); import { Just, Left, type Maybe, Nothing, Right } from "purify-ts"; import { ApduResponse } from "@api/device-session/ApduResponse"; +import { type ApduReceiverService } from "@api/device-session/service/ApduReceiverService"; import { ReceiverApduError } from "@internal/device-session/model/Errors"; import { DefaultLoggerPublisherService } from "@internal/logger-publisher/service/DefaultLoggerPublisherService"; -import { type ApduReceiverService } from "./ApduReceiverService"; import { DefaultApduReceiverService } from "./DefaultApduReceiverService"; const loggerService = new DefaultLoggerPublisherService([], "frame"); diff --git a/packages/device-management-kit/src/internal/device-session/service/DefaultApduReceiverService.ts b/packages/device-management-kit/src/internal/device-session/service/DefaultApduReceiverService.ts index 077372554..9edeff662 100644 --- a/packages/device-management-kit/src/internal/device-session/service/DefaultApduReceiverService.ts +++ b/packages/device-management-kit/src/internal/device-session/service/DefaultApduReceiverService.ts @@ -3,25 +3,23 @@ import { Either, Just, Left, Maybe, Nothing, Right } from "purify-ts"; import { v4 } from "uuid"; import { ApduResponse } from "@api/device-session/ApduResponse"; -import { APDU_RESPONSE_STATUS_CODE_LENGTH } from "@internal/device-session/data/ApduResponseConst"; import { APDU_DATA_LENGTH_LENGTH, CHANNEL_LENGTH, HEAD_TAG_LENGTH, INDEX_LENGTH, -} from "@internal/device-session/data/FramerConst"; +} from "@api/device-session/data/FramerConst"; +import { + type ApduReceiverConstructorArgs, + ApduReceiverService, +} from "@api/device-session/service/ApduReceiverService"; +import { FramerUtils } from "@api/device-session/utils/FramerUtils"; +import { LoggerPublisherService } from "@api/logger-publisher/service/LoggerPublisherService"; +import { APDU_RESPONSE_STATUS_CODE_LENGTH } from "@internal/device-session/data/ApduResponseConst"; import { ReceiverApduError } from "@internal/device-session/model/Errors"; import { Frame } from "@internal/device-session/model/Frame"; import { FrameHeader } from "@internal/device-session/model/FrameHeader"; -import { FramerUtils } from "@internal/device-session/utils/FramerUtils"; import { loggerTypes } from "@internal/logger-publisher/di/loggerTypes"; -import { LoggerPublisherService } from "@internal/logger-publisher/service/LoggerPublisherService"; - -import { ApduReceiverService } from "./ApduReceiverService"; - -export type DefaultApduReceiverConstructorArgs = { - channel?: Maybe; -}; @injectable() export class DefaultApduReceiverService implements ApduReceiverService { @@ -30,7 +28,7 @@ export class DefaultApduReceiverService implements ApduReceiverService { private _pendingFrames: Frame[]; constructor( - { channel = Maybe.zero() }: DefaultApduReceiverConstructorArgs, + { channel = Maybe.zero() }: ApduReceiverConstructorArgs, @inject(loggerTypes.LoggerPublisherServiceFactory) loggerModuleFactory: (tag: string) => LoggerPublisherService, ) { diff --git a/packages/device-management-kit/src/internal/device-session/service/DefaultApduSenderService.stub.ts b/packages/device-management-kit/src/internal/device-session/service/DefaultApduSenderService.stub.ts deleted file mode 100644 index 7f16c7b32..000000000 --- a/packages/device-management-kit/src/internal/device-session/service/DefaultApduSenderService.stub.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { Maybe } from "purify-ts"; - -import { type ApduSenderService } from "@internal/device-session/service/ApduSenderService"; -import { - DefaultApduSenderService, - type DefaultApduSenderServiceConstructorArgs, -} from "@internal/device-session/service/DefaultApduSenderService"; -import { type LoggerPublisherService } from "@internal/logger-publisher/service/LoggerPublisherService"; - -export const defaultApduSenderServiceStubBuilder = ( - props: Partial = {}, - loggerFactory: (tag: string) => LoggerPublisherService, -): ApduSenderService => - new DefaultApduSenderService( - { - frameSize: 64, - channel: Maybe.of(new Uint8Array([0x12, 0x34])), - padding: true, - ...props, - }, - loggerFactory, - ); diff --git a/packages/device-management-kit/src/internal/device-session/service/DefaultApduSenderService.ts b/packages/device-management-kit/src/internal/device-session/service/DefaultApduSenderService.ts index a6ac32e07..4b0410b2a 100644 --- a/packages/device-management-kit/src/internal/device-session/service/DefaultApduSenderService.ts +++ b/packages/device-management-kit/src/internal/device-session/service/DefaultApduSenderService.ts @@ -8,30 +8,25 @@ import { HEAD_TAG, HEAD_TAG_LENGTH, INDEX_LENGTH, -} from "@internal/device-session/data/FramerConst"; +} from "@api/device-session/data/FramerConst"; +import type { + ApduSenderService, + ApduSenderServiceConstructorArgs, +} from "@api/device-session/service/ApduSenderService"; +import { FramerUtils } from "@api/device-session/utils/FramerUtils"; +import { LoggerPublisherService } from "@api/logger-publisher/service/LoggerPublisherService"; import { FramerApduError, FramerOverflowError, } from "@internal/device-session/model/Errors"; import { Frame } from "@internal/device-session/model/Frame"; import { FrameHeader } from "@internal/device-session/model/FrameHeader"; -import { FramerUtils } from "@internal/device-session/utils/FramerUtils"; import { loggerTypes } from "@internal/logger-publisher/di/loggerTypes"; -import { LoggerPublisherService } from "@internal/logger-publisher/service/LoggerPublisherService"; import { DmkError } from "@root/src/api/Error"; - -import type { ApduSenderService } from "./ApduSenderService"; - -export type DefaultApduSenderServiceConstructorArgs = { - frameSize: number; - channel?: Maybe; - padding?: boolean; -}; - /** * Default implementation of ApduSenderService * - * Split APDU in an array of frames readies to send to a InternalConnectedDevice + * Split APDU in an array of frames readies to send to a TransportConnectedDevice */ @injectable() export class DefaultApduSenderService implements ApduSenderService { @@ -53,7 +48,7 @@ export class DefaultApduSenderService implements ApduSenderService { frameSize, channel = Maybe.zero(), padding = false, - }: DefaultApduSenderServiceConstructorArgs, + }: ApduSenderServiceConstructorArgs, @inject(loggerTypes.LoggerPublisherServiceFactory) loggerServiceFactory: (tag: string) => LoggerPublisherService, ) { diff --git a/packages/device-management-kit/src/internal/device-session/service/DefaultDeviceSessionService.test.ts b/packages/device-management-kit/src/internal/device-session/service/DefaultDeviceSessionService.test.ts index 0f2f1cffb..8843bc441 100644 --- a/packages/device-management-kit/src/internal/device-session/service/DefaultDeviceSessionService.test.ts +++ b/packages/device-management-kit/src/internal/device-session/service/DefaultDeviceSessionService.test.ts @@ -1,5 +1,6 @@ import { Either, Left } from "purify-ts"; +import { connectedDeviceStubBuilder } from "@api/transport/model/TransportConnectedDevice.stub"; import { DeviceSession } from "@internal/device-session/model/DeviceSession"; import { DeviceSessionNotFound } from "@internal/device-session/model/Errors"; import { DefaultLoggerPublisherService } from "@internal/logger-publisher/service/DefaultLoggerPublisherService"; @@ -7,7 +8,6 @@ import { AxiosManagerApiDataSource } from "@internal/manager-api/data/AxiosManag import { type ManagerApiDataSource } from "@internal/manager-api/data/ManagerApiDataSource"; import { DefaultManagerApiService } from "@internal/manager-api/service/DefaultManagerApiService"; import type { ManagerApiService } from "@internal/manager-api/service/ManagerApiService"; -import { connectedDeviceStubBuilder } from "@internal/transport/model/InternalConnectedDevice.stub"; import { DefaultDeviceSessionService } from "./DefaultDeviceSessionService"; diff --git a/packages/device-management-kit/src/internal/device-session/service/DefaultDeviceSessionService.ts b/packages/device-management-kit/src/internal/device-session/service/DefaultDeviceSessionService.ts index 1d2eca8fa..1de041be4 100644 --- a/packages/device-management-kit/src/internal/device-session/service/DefaultDeviceSessionService.ts +++ b/packages/device-management-kit/src/internal/device-session/service/DefaultDeviceSessionService.ts @@ -1,11 +1,11 @@ import { inject, injectable } from "inversify"; import { Maybe } from "purify-ts"; +import { LoggerPublisherService } from "@api/logger-publisher/service/LoggerPublisherService"; import { DeviceSession } from "@internal/device-session/model/DeviceSession"; import { DeviceSessionNotFound } from "@internal/device-session/model/Errors"; import { DeviceSessionService } from "@internal/device-session/service/DeviceSessionService"; import { loggerTypes } from "@internal/logger-publisher/di/loggerTypes"; -import { LoggerPublisherService } from "@internal/logger-publisher/service/LoggerPublisherService"; @injectable() export class DefaultDeviceSessionService implements DeviceSessionService { diff --git a/packages/device-management-kit/src/internal/device-session/use-case/CloseSessionsUseState.test.ts b/packages/device-management-kit/src/internal/device-session/use-case/CloseSessionsUseState.test.ts index 28e3534cb..cce6fee9a 100644 --- a/packages/device-management-kit/src/internal/device-session/use-case/CloseSessionsUseState.test.ts +++ b/packages/device-management-kit/src/internal/device-session/use-case/CloseSessionsUseState.test.ts @@ -1,9 +1,9 @@ +import { type LoggerPublisherService } from "@api/logger-publisher/service/LoggerPublisherService"; import { deviceSessionStubBuilder } from "@internal/device-session/model/DeviceSession.stub"; import { DefaultDeviceSessionService } from "@internal/device-session/service/DefaultDeviceSessionService"; import { type DeviceSessionService } from "@internal/device-session/service/DeviceSessionService"; import { CloseSessionsUseCase } from "@internal/device-session/use-case/CloseSessionsUseCase"; import { DefaultLoggerPublisherService } from "@internal/logger-publisher/service/DefaultLoggerPublisherService"; -import { type LoggerPublisherService } from "@internal/logger-publisher/service/LoggerPublisherService"; import { AxiosManagerApiDataSource } from "@internal/manager-api/data/AxiosManagerApiDataSource"; import { type ManagerApiDataSource } from "@internal/manager-api/data/ManagerApiDataSource"; import { DefaultManagerApiService } from "@internal/manager-api/service/DefaultManagerApiService"; diff --git a/packages/device-management-kit/src/internal/device-session/use-case/GetDeviceSessionStateUseCase.test.ts b/packages/device-management-kit/src/internal/device-session/use-case/GetDeviceSessionStateUseCase.test.ts index 338ed1ee5..6d08ea3c2 100644 --- a/packages/device-management-kit/src/internal/device-session/use-case/GetDeviceSessionStateUseCase.test.ts +++ b/packages/device-management-kit/src/internal/device-session/use-case/GetDeviceSessionStateUseCase.test.ts @@ -1,8 +1,8 @@ +import { type LoggerPublisherService } from "@api/logger-publisher/service/LoggerPublisherService"; import { deviceSessionStubBuilder } from "@internal/device-session/model/DeviceSession.stub"; import { DefaultDeviceSessionService } from "@internal/device-session/service/DefaultDeviceSessionService"; import { type DeviceSessionService } from "@internal/device-session/service/DeviceSessionService"; import { DefaultLoggerPublisherService } from "@internal/logger-publisher/service/DefaultLoggerPublisherService"; -import { type LoggerPublisherService } from "@internal/logger-publisher/service/LoggerPublisherService"; import { AxiosManagerApiDataSource } from "@internal/manager-api/data/AxiosManagerApiDataSource"; import { type ManagerApiDataSource } from "@internal/manager-api/data/ManagerApiDataSource"; import { DefaultManagerApiService } from "@internal/manager-api/service/DefaultManagerApiService"; diff --git a/packages/device-management-kit/src/internal/device-session/use-case/GetDeviceSessionStateUseCase.ts b/packages/device-management-kit/src/internal/device-session/use-case/GetDeviceSessionStateUseCase.ts index d8de29b4d..f8f3573af 100644 --- a/packages/device-management-kit/src/internal/device-session/use-case/GetDeviceSessionStateUseCase.ts +++ b/packages/device-management-kit/src/internal/device-session/use-case/GetDeviceSessionStateUseCase.ts @@ -1,10 +1,10 @@ import { inject, injectable } from "inversify"; import { DeviceSessionId } from "@api/device-session/types"; +import { LoggerPublisherService } from "@api/logger-publisher/service/LoggerPublisherService"; import { deviceSessionTypes } from "@internal/device-session/di/deviceSessionTypes"; import type { DeviceSessionService } from "@internal/device-session/service/DeviceSessionService"; import { loggerTypes } from "@internal/logger-publisher/di/loggerTypes"; -import { LoggerPublisherService } from "@internal/logger-publisher/service/LoggerPublisherService"; export type GetSessionDeviceStateUseCaseArgs = { sessionId: DeviceSessionId; diff --git a/packages/device-management-kit/src/internal/device-session/use-case/ListDeviceSessionsUseCase.test.ts b/packages/device-management-kit/src/internal/device-session/use-case/ListDeviceSessionsUseCase.test.ts index 278f2078b..bf79239c7 100644 --- a/packages/device-management-kit/src/internal/device-session/use-case/ListDeviceSessionsUseCase.test.ts +++ b/packages/device-management-kit/src/internal/device-session/use-case/ListDeviceSessionsUseCase.test.ts @@ -1,8 +1,8 @@ +import { type LoggerPublisherService } from "@api/logger-publisher/service/LoggerPublisherService"; import { deviceSessionStubBuilder } from "@internal/device-session/model/DeviceSession.stub"; import { DefaultDeviceSessionService } from "@internal/device-session/service/DefaultDeviceSessionService"; import { type DeviceSessionService } from "@internal/device-session/service/DeviceSessionService"; import { DefaultLoggerPublisherService } from "@internal/logger-publisher/service/DefaultLoggerPublisherService"; -import { type LoggerPublisherService } from "@internal/logger-publisher/service/LoggerPublisherService"; import { AxiosManagerApiDataSource } from "@internal/manager-api/data/AxiosManagerApiDataSource"; import { type ManagerApiDataSource } from "@internal/manager-api/data/ManagerApiDataSource"; import { DefaultManagerApiService } from "@internal/manager-api/service/DefaultManagerApiService"; diff --git a/packages/device-management-kit/src/internal/device-session/use-case/ListDeviceSessionsUseCase.ts b/packages/device-management-kit/src/internal/device-session/use-case/ListDeviceSessionsUseCase.ts index 9a4cf678f..38b530bcb 100644 --- a/packages/device-management-kit/src/internal/device-session/use-case/ListDeviceSessionsUseCase.ts +++ b/packages/device-management-kit/src/internal/device-session/use-case/ListDeviceSessionsUseCase.ts @@ -1,10 +1,10 @@ import { inject, injectable } from "inversify"; +import { type LoggerPublisherService } from "@api/logger-publisher/service/LoggerPublisherService"; import { deviceSessionTypes } from "@internal/device-session/di/deviceSessionTypes"; import { DeviceSession } from "@internal/device-session/model/DeviceSession"; import type { DeviceSessionService } from "@internal/device-session/service/DeviceSessionService"; import { loggerTypes } from "@internal/logger-publisher/di/loggerTypes"; -import { LoggerPublisherService } from "@internal/logger-publisher/service/LoggerPublisherService"; /** * List all device sessions. diff --git a/packages/device-management-kit/src/internal/discovery/di/discoveryModule.test.ts b/packages/device-management-kit/src/internal/discovery/di/discoveryModule.test.ts index 7ab180a0a..862c43b0e 100644 --- a/packages/device-management-kit/src/internal/discovery/di/discoveryModule.test.ts +++ b/packages/device-management-kit/src/internal/discovery/di/discoveryModule.test.ts @@ -10,8 +10,6 @@ import { StopDiscoveringUseCase } from "@internal/discovery/use-case/StopDiscove import { loggerModuleFactory } from "@internal/logger-publisher/di/loggerModule"; import { managerApiModuleFactory } from "@internal/manager-api/di/managerApiModule"; import { transportModuleFactory } from "@internal/transport/di/transportModule"; -import { usbModuleFactory } from "@internal/transport/usb/di/usbModule"; -import { BuiltinTransports } from "@root/src"; import { discoveryModuleFactory } from "./discoveryModule"; import { discoveryTypes } from "./discoveryTypes"; @@ -26,10 +24,9 @@ describe("discoveryModuleFactory", () => { mod, // The following modules are injected into discovery module loggerModuleFactory(), - usbModuleFactory({ stub: false }), deviceModelModuleFactory({ stub: false }), deviceSessionModuleFactory(), - transportModuleFactory({ transports: [BuiltinTransports.USB] }), + transportModuleFactory({ transports: [] }), managerApiModuleFactory({ config: { managerApiUrl: "http://fake.url", diff --git a/packages/device-management-kit/src/internal/discovery/use-case/ConnectUseCase.test.ts b/packages/device-management-kit/src/internal/discovery/use-case/ConnectUseCase.test.ts index cdeb03f85..bb2f9c545 100644 --- a/packages/device-management-kit/src/internal/discovery/use-case/ConnectUseCase.test.ts +++ b/packages/device-management-kit/src/internal/discovery/use-case/ConnectUseCase.test.ts @@ -1,29 +1,32 @@ -import { Left, Right } from "purify-ts"; +import { Left, Maybe, Right } from "purify-ts"; import * as uuid from "uuid"; jest.mock("uuid"); import { type DeviceModel } from "@api/device/DeviceModel"; +import { type LoggerPublisherService } from "@api/logger-publisher/service/LoggerPublisherService"; import { type DiscoveredDevice } from "@api/transport/model/DiscoveredDevice"; -import { type DeviceModelDataSource } from "@internal/device-model/data/DeviceModelDataSource"; +import { UnknownDeviceError } from "@api/transport/model/Errors"; +import { TransportStub } from "@api/transport/model/Transport.stub"; +import { connectedDeviceStubBuilder } from "@api/transport/model/TransportConnectedDevice.stub"; +import { type Transport } from "@api/types"; import { DefaultDeviceSessionService } from "@internal/device-session/service/DefaultDeviceSessionService"; import { type DeviceSessionService } from "@internal/device-session/service/DeviceSessionService"; import { DefaultLoggerPublisherService } from "@internal/logger-publisher/service/DefaultLoggerPublisherService"; -import { type LoggerPublisherService } from "@internal/logger-publisher/service/LoggerPublisherService"; import { AxiosManagerApiDataSource } from "@internal/manager-api/data/AxiosManagerApiDataSource"; import { type ManagerApiDataSource } from "@internal/manager-api/data/ManagerApiDataSource"; import { DefaultManagerApiService } from "@internal/manager-api/service/DefaultManagerApiService"; import { type ManagerApiService } from "@internal/manager-api/service/ManagerApiService"; -import { UnknownDeviceError } from "@internal/transport/model/Errors"; -import { connectedDeviceStubBuilder } from "@internal/transport/model/InternalConnectedDevice.stub"; -import { usbHidDeviceConnectionFactoryStubBuilder } from "@internal/transport/usb/service/UsbHidDeviceConnectionFactory.stub"; -import { WebUsbHidTransport } from "@internal/transport/usb/transport/WebUsbHidTransport"; +import { type TransportService } from "@internal/transport/service/TransportService"; +import { TransportServiceStub } from "@internal/transport/service/TransportService.stub"; import { ConnectUseCase } from "./ConnectUseCase"; jest.mock("@internal/manager-api/data/AxiosManagerApiDataSource"); // TODO test several transports -let transports: WebUsbHidTransport[]; +// let transports: WebUsbHidTransport[]; +let transport: Transport; +let transportService: TransportService; let logger: LoggerPublisherService; let sessionService: DeviceSessionService; let managerApi: ManagerApiService; @@ -42,19 +45,15 @@ describe("ConnectUseCase", () => { beforeAll(() => { logger = new DefaultLoggerPublisherService([], tag); jest.spyOn(uuid, "v4").mockReturnValue(fakeSessionId); - transports = [ - new WebUsbHidTransport( - {} as DeviceModelDataSource, - () => logger, - usbHidDeviceConnectionFactoryStubBuilder(), - ), - ]; + transport = new TransportStub(); sessionService = new DefaultDeviceSessionService(() => logger); managerApiDataSource = new AxiosManagerApiDataSource({ managerApiUrl: "http://fake.url", mockUrl: "http://fake-mock.url", }); managerApi = new DefaultManagerApiService(managerApiDataSource); + // @ts-expect-error stub + transportService = new TransportServiceStub(); }); afterAll(() => { @@ -63,11 +62,15 @@ describe("ConnectUseCase", () => { test("If connect use case encounter an error, return it", async () => { jest - .spyOn(transports[0]!, "connect") + .spyOn(transport, "connect") .mockResolvedValue(Left(new UnknownDeviceError())); + jest + .spyOn(transportService, "getTransport") + .mockReturnValue(Maybe.of(transport)); + const usecase = new ConnectUseCase( - transports, + transportService, sessionService, () => logger, managerApi, @@ -80,11 +83,15 @@ describe("ConnectUseCase", () => { test("If connect is in success, return a deviceSession id", async () => { jest - .spyOn(transports[0]!, "connect") + .spyOn(transport, "connect") .mockResolvedValue(Promise.resolve(Right(stubConnectedDevice))); + jest + .spyOn(transportService, "getTransport") + .mockReturnValue(Maybe.of(transport)); + const usecase = new ConnectUseCase( - transports, + transportService, sessionService, () => logger, managerApi, diff --git a/packages/device-management-kit/src/internal/discovery/use-case/ConnectUseCase.ts b/packages/device-management-kit/src/internal/discovery/use-case/ConnectUseCase.ts index d12771e3b..45fb14ce4 100644 --- a/packages/device-management-kit/src/internal/discovery/use-case/ConnectUseCase.ts +++ b/packages/device-management-kit/src/internal/discovery/use-case/ConnectUseCase.ts @@ -1,18 +1,19 @@ -import { inject, injectable, multiInject } from "inversify"; +import { inject, injectable } from "inversify"; +import { EitherAsync } from "purify-ts"; import { DeviceSessionId } from "@api/device-session/types"; +import { LoggerPublisherService } from "@api/logger-publisher/service/LoggerPublisherService"; import { DiscoveredDevice } from "@api/transport/model/DiscoveredDevice"; -import type { Transport } from "@api/transport/model/Transport"; +import { TransportNotSupportedError } from "@api/transport/model/Errors"; import { DeviceId } from "@api/types"; import { deviceSessionTypes } from "@internal/device-session/di/deviceSessionTypes"; import { DeviceSession } from "@internal/device-session/model/DeviceSession"; import type { DeviceSessionService } from "@internal/device-session/service/DeviceSessionService"; import { loggerTypes } from "@internal/logger-publisher/di/loggerTypes"; -import { LoggerPublisherService } from "@internal/logger-publisher/service/LoggerPublisherService"; import { managerApiTypes } from "@internal/manager-api/di/managerApiTypes"; import type { ManagerApiService } from "@internal/manager-api/service/ManagerApiService"; import { transportDiTypes } from "@internal/transport/di/transportDiTypes"; -import { TransportNotSupportedError } from "@internal/transport/model/Errors"; +import { TransportService } from "@internal/transport/service/TransportService"; /** * The arguments for the ConnectUseCase. @@ -29,15 +30,15 @@ export type ConnectUseCaseArgs = { */ @injectable() export class ConnectUseCase { - private readonly _transports: Transport[]; + private readonly _transportService: TransportService; private readonly _sessionService: DeviceSessionService; private readonly _loggerFactory: (tag: string) => LoggerPublisherService; private readonly _managerApi: ManagerApiService; private readonly _logger: LoggerPublisherService; constructor( - @multiInject(transportDiTypes.Transport) - transports: Transport[], + @inject(transportDiTypes.TransportService) + transportService: TransportService, @inject(deviceSessionTypes.DeviceSessionService) sessionService: DeviceSessionService, @inject(loggerTypes.LoggerPublisherServiceFactory) @@ -46,7 +47,7 @@ export class ConnectUseCase { managerApi: ManagerApiService, ) { this._sessionService = sessionService; - this._transports = transports; + this._transportService = transportService; this._loggerFactory = loggerFactory; this._logger = loggerFactory("ConnectUseCase"); this._managerApi = managerApi; @@ -61,25 +62,25 @@ export class ConnectUseCase { } async execute({ device }: ConnectUseCaseArgs): Promise { - const transport = this._transports.find( - (t) => t.getIdentifier() === device.transport, - ); - if (!transport) { - throw new TransportNotSupportedError(new Error("Unknown transport")); - } - const either = await transport.connect({ - deviceId: device.id, - onDisconnect: (dId) => this.handleDeviceDisconnect(dId), - }); + const transport = this._transportService.getTransport(device.transport); - return either.caseOf({ - Left: (error) => { + return EitherAsync.liftEither( + transport.toEither( + new TransportNotSupportedError(new Error("Unknown transport")), + ), + ) + .chain(async (t) => { + return t.connect({ + deviceId: device.id, + onDisconnect: (dId) => this.handleDeviceDisconnect(dId), + }); + }) + .ifLeft((error) => { this._logger.error("Error connecting to device", { data: { deviceId: device.id, error }, }); - throw error; - }, - Right: (connectedDevice) => { + }) + .map((connectedDevice) => { const deviceSession = new DeviceSession( { connectedDevice }, this._loggerFactory, @@ -87,7 +88,12 @@ export class ConnectUseCase { ); this._sessionService.addDeviceSession(deviceSession); return deviceSession.id; - }, - }); + }) + .caseOf({ + Left: (error) => { + throw error; + }, + Right: (s) => s, + }); } } diff --git a/packages/device-management-kit/src/internal/discovery/use-case/DisconnectUseCase.test.ts b/packages/device-management-kit/src/internal/discovery/use-case/DisconnectUseCase.test.ts index 11cb0cda1..1b98ef11f 100644 --- a/packages/device-management-kit/src/internal/discovery/use-case/DisconnectUseCase.test.ts +++ b/packages/device-management-kit/src/internal/discovery/use-case/DisconnectUseCase.test.ts @@ -1,6 +1,9 @@ -import { Left, Right } from "purify-ts"; +import { Left, Maybe, Right } from "purify-ts"; -import { type DeviceModelDataSource } from "@internal/device-model/data/DeviceModelDataSource"; +import { DisconnectError } from "@api/transport/model/Errors"; +import { TransportStub } from "@api/transport/model/Transport.stub"; +import { connectedDeviceStubBuilder } from "@api/transport/model/TransportConnectedDevice.stub"; +import { type Transport } from "@api/types"; import { deviceSessionStubBuilder } from "@internal/device-session/model/DeviceSession.stub"; import { DeviceSessionNotFound } from "@internal/device-session/model/Errors"; import { DefaultDeviceSessionService } from "@internal/device-session/service/DefaultDeviceSessionService"; @@ -9,22 +12,21 @@ import { AxiosManagerApiDataSource } from "@internal/manager-api/data/AxiosManag import { type ManagerApiDataSource } from "@internal/manager-api/data/ManagerApiDataSource"; import { DefaultManagerApiService } from "@internal/manager-api/service/DefaultManagerApiService"; import { type ManagerApiService } from "@internal/manager-api/service/ManagerApiService"; -import { DisconnectError } from "@internal/transport/model/Errors"; -import { connectedDeviceStubBuilder } from "@internal/transport/model/InternalConnectedDevice.stub"; -import { usbHidDeviceConnectionFactoryStubBuilder } from "@internal/transport/usb/service/UsbHidDeviceConnectionFactory.stub"; -import { WebUsbHidTransport } from "@internal/transport/usb/transport/WebUsbHidTransport"; +import { type TransportService } from "@internal/transport/service/TransportService"; +import { TransportServiceStub } from "@internal/transport/service/TransportService.stub"; import { DisconnectUseCase } from "./DisconnectUseCase"; let sessionService: DefaultDeviceSessionService; // TODO test several transports -let transports: WebUsbHidTransport[] = []; +let transport: Transport; +let transports: Transport[] = []; const loggerFactory = jest .fn() .mockReturnValue( new DefaultLoggerPublisherService([], "DisconnectUseCaseTest"), ); - +let transportService: TransportService; let managerApi: ManagerApiService; let managerApiDataSource: ManagerApiDataSource; @@ -32,14 +34,14 @@ const sessionId = "sessionId"; describe("DisconnectUseCase", () => { beforeAll(() => { - transports = [ - new WebUsbHidTransport( - {} as DeviceModelDataSource, - loggerFactory, - usbHidDeviceConnectionFactoryStubBuilder(), - ), - ]; + transport = new TransportStub(); + transports = [transport]; sessionService = new DefaultDeviceSessionService(loggerFactory); + // @ts-expect-error stub + transportService = new TransportServiceStub(); + jest + .spyOn(transportService, "getTransport") + .mockReturnValue(Maybe.of(transport)); }); it("should disconnect from a device", async () => { @@ -67,7 +69,7 @@ describe("DisconnectUseCase", () => { .spyOn(transports[0]!, "disconnect") .mockImplementation(() => Promise.resolve(Right(void 0))); const disconnectUseCase = new DisconnectUseCase( - transports, + transportService, sessionService, loggerFactory, ); @@ -85,7 +87,7 @@ describe("DisconnectUseCase", () => { it("should throw an error when deviceSession not found", async () => { // Given const disconnectUseCase = new DisconnectUseCase( - transports, + transportService, sessionService, loggerFactory, ); @@ -115,7 +117,7 @@ describe("DisconnectUseCase", () => { .spyOn(transports[0]!, "disconnect") .mockResolvedValue(Promise.resolve(Left(new DisconnectError()))); const disconnectUseCase = new DisconnectUseCase( - transports, + transportService, sessionService, loggerFactory, ); diff --git a/packages/device-management-kit/src/internal/discovery/use-case/DisconnectUseCase.ts b/packages/device-management-kit/src/internal/discovery/use-case/DisconnectUseCase.ts index dfe0aabb1..dc8936692 100644 --- a/packages/device-management-kit/src/internal/discovery/use-case/DisconnectUseCase.ts +++ b/packages/device-management-kit/src/internal/discovery/use-case/DisconnectUseCase.ts @@ -1,13 +1,14 @@ -import { inject, injectable, multiInject } from "inversify"; +import { inject, injectable } from "inversify"; +import { EitherAsync } from "purify-ts"; -import type { Transport } from "@api/transport/model/Transport"; +import { LoggerPublisherService } from "@api/logger-publisher/service/LoggerPublisherService"; +import { TransportNotSupportedError } from "@api/transport/model/Errors"; import type { DeviceSessionId } from "@api/types"; import { deviceSessionTypes } from "@internal/device-session/di/deviceSessionTypes"; import type { DeviceSessionService } from "@internal/device-session/service/DeviceSessionService"; import { loggerTypes } from "@internal/logger-publisher/di/loggerTypes"; -import { LoggerPublisherService } from "@internal/logger-publisher/service/LoggerPublisherService"; import { transportDiTypes } from "@internal/transport/di/transportDiTypes"; -import { TransportNotSupportedError } from "@internal/transport/model/Errors"; +import { TransportService } from "@internal/transport/service/TransportService"; /** * The arguments for the DisconnectUseCase. @@ -24,54 +25,56 @@ export type DisconnectUseCaseArgs = { */ @injectable() export class DisconnectUseCase { - private readonly _transports: Transport[]; + private readonly _transportService: TransportService; private readonly _sessionService: DeviceSessionService; private readonly _logger: LoggerPublisherService; constructor( - @multiInject(transportDiTypes.Transport) - transports: Transport[], + @inject(transportDiTypes.TransportService) + transportService: TransportService, @inject(deviceSessionTypes.DeviceSessionService) sessionService: DeviceSessionService, @inject(loggerTypes.LoggerPublisherServiceFactory) loggerFactory: (tag: string) => LoggerPublisherService, ) { this._sessionService = sessionService; - this._transports = transports; + this._transportService = transportService; this._logger = loggerFactory("DisconnectUseCase"); } async execute({ sessionId }: DisconnectUseCaseArgs): Promise { - const errorOrSession = this._sessionService.getDeviceSessionById(sessionId); + return EitherAsync(async ({ liftEither }) => { + const session = await liftEither( + this._sessionService.getDeviceSessionById(sessionId).ifLeft((error) => { + this._logger.error("Device session not found", { + data: { sessionId, error }, + }); + }), + ); - return errorOrSession.caseOf({ + const transportIdentifier = session.connectedDevice.transport; + const transport = await liftEither( + this._transportService + .getTransport(transportIdentifier) + .toEither( + new TransportNotSupportedError(new Error("Unknown transport")), + ), + ); + + session.close(); + this._sessionService.removeDeviceSession(sessionId); + + await transport.disconnect({ + connectedDevice: session.connectedDevice, + }); + }).caseOf({ Left: (error) => { - this._logger.error("Device session not found", { - data: { sessionId, error }, + this._logger.error("Error disconnecting from device", { + data: { error }, }); throw error; }, - Right: async (deviceSession) => { - const transportIdentifier = deviceSession.connectedDevice.transport; - const transport = this._transports.find( - (t) => t.getIdentifier() === transportIdentifier, - ); - if (!transport) { - throw new TransportNotSupportedError(new Error("Unknown transport")); - } - - deviceSession.close(); - this._sessionService.removeDeviceSession(sessionId); - await transport - .disconnect({ - connectedDevice: deviceSession.connectedDevice, - }) - .then((errorOrDisconnected) => - errorOrDisconnected.mapLeft((error) => { - throw error; - }), - ); - }, + Right: () => {}, }); } } diff --git a/packages/device-management-kit/src/internal/discovery/use-case/GetConnectedDeviceUseCase.test.ts b/packages/device-management-kit/src/internal/discovery/use-case/GetConnectedDeviceUseCase.test.ts index b4321427f..24d6ca859 100644 --- a/packages/device-management-kit/src/internal/discovery/use-case/GetConnectedDeviceUseCase.test.ts +++ b/packages/device-management-kit/src/internal/discovery/use-case/GetConnectedDeviceUseCase.test.ts @@ -1,9 +1,9 @@ +import { type LoggerPublisherService } from "@api/logger-publisher/service/LoggerPublisherService"; import { ConnectedDevice } from "@api/transport/model/ConnectedDevice"; import { deviceSessionStubBuilder } from "@internal/device-session/model/DeviceSession.stub"; import { DefaultDeviceSessionService } from "@internal/device-session/service/DefaultDeviceSessionService"; import { type DeviceSessionService } from "@internal/device-session/service/DeviceSessionService"; import { DefaultLoggerPublisherService } from "@internal/logger-publisher/service/DefaultLoggerPublisherService"; -import { type LoggerPublisherService } from "@internal/logger-publisher/service/LoggerPublisherService"; import { AxiosManagerApiDataSource } from "@internal/manager-api/data/AxiosManagerApiDataSource"; import { type ManagerApiDataSource } from "@internal/manager-api/data/ManagerApiDataSource"; import { DefaultManagerApiService } from "@internal/manager-api/service/DefaultManagerApiService"; @@ -71,7 +71,7 @@ describe("GetConnectedDevice", () => { // then expect(response).toStrictEqual( new ConnectedDevice({ - internalConnectedDevice: deviceSession.connectedDevice, + transportConnectedDevice: deviceSession.connectedDevice, }), ); }); diff --git a/packages/device-management-kit/src/internal/discovery/use-case/GetConnectedDeviceUseCase.ts b/packages/device-management-kit/src/internal/discovery/use-case/GetConnectedDeviceUseCase.ts index 843764cc5..9ed5d18fa 100644 --- a/packages/device-management-kit/src/internal/discovery/use-case/GetConnectedDeviceUseCase.ts +++ b/packages/device-management-kit/src/internal/discovery/use-case/GetConnectedDeviceUseCase.ts @@ -1,11 +1,11 @@ import { inject, injectable } from "inversify"; import { DeviceSessionId } from "@api/device-session/types"; +import { LoggerPublisherService } from "@api/logger-publisher/service/LoggerPublisherService"; import { ConnectedDevice } from "@api/transport/model/ConnectedDevice"; import { deviceSessionTypes } from "@internal/device-session/di/deviceSessionTypes"; import type { DeviceSessionService } from "@internal/device-session/service/DeviceSessionService"; import { loggerTypes } from "@internal/logger-publisher/di/loggerTypes"; -import { LoggerPublisherService } from "@internal/logger-publisher/service/LoggerPublisherService"; export type GetConnectedDeviceUseCaseArgs = { sessionId: DeviceSessionId; @@ -36,7 +36,7 @@ export class GetConnectedDeviceUseCase { return deviceSessionOrError.caseOf({ Right: (deviceSession) => new ConnectedDevice({ - internalConnectedDevice: deviceSession.connectedDevice, + transportConnectedDevice: deviceSession.connectedDevice, }), Left: (error) => { this._logger.error("Error getting session", { diff --git a/packages/device-management-kit/src/internal/discovery/use-case/ListenToKnownDevicesUseCase.test.ts b/packages/device-management-kit/src/internal/discovery/use-case/ListenToKnownDevicesUseCase.test.ts index bf14ec63e..6c67f2f11 100644 --- a/packages/device-management-kit/src/internal/discovery/use-case/ListenToKnownDevicesUseCase.test.ts +++ b/packages/device-management-kit/src/internal/discovery/use-case/ListenToKnownDevicesUseCase.test.ts @@ -1,12 +1,15 @@ import { Subject } from "rxjs"; import { type DeviceId, type DeviceModel } from "@api/device/DeviceModel"; +import { deviceModelStubBuilder } from "@api/device-model/model/DeviceModel.stub"; +import { type TransportDiscoveredDevice } from "@api/transport/model/TransportDiscoveredDevice"; import { type DiscoveredDevice, type Transport } from "@api/types"; -import { deviceModelStubBuilder } from "@internal/device-model/model/DeviceModel.stub"; -import { type InternalDiscoveredDevice } from "@internal/transport/model/InternalDiscoveredDevice"; +import { type TransportService } from "@internal/transport/service/TransportService"; +import { TransportServiceStub } from "@internal/transport/service/TransportService.stub"; import { ListenToKnownDevicesUseCase } from "./ListenToKnownDevicesUseCase"; +let transportService: TransportService; function makeMockTransport(props: Partial): Transport { return { listenToKnownDevices: jest.fn(), @@ -31,10 +34,10 @@ function makeMockDeviceModel(id: DeviceId): DeviceModel { function setup2MockTransports() { const transportAKnownDevicesSubject = new Subject< - InternalDiscoveredDevice[] + TransportDiscoveredDevice[] >(); const transportBKnownDevicesSubject = new Subject< - InternalDiscoveredDevice[] + TransportDiscoveredDevice[] >(); const transportA = makeMockTransport({ listenToKnownDevices: () => transportAKnownDevicesSubject.asObservable(), @@ -50,9 +53,9 @@ function setup2MockTransports() { }; } -function makeMockInternalDiscoveredDevice( +function makeMockTransportDiscoveredDevice( id: string, -): InternalDiscoveredDevice { +): TransportDiscoveredDevice { return { id, deviceModel: mockInternalDeviceModel, @@ -61,9 +64,17 @@ function makeMockInternalDiscoveredDevice( } describe("ListenToKnownDevicesUseCase", () => { + beforeEach(() => { + jest.clearAllMocks(); + // @ts-expect-error stub + transportService = new TransportServiceStub(); + }); + describe("when no transports are available", () => { it("should return no discovered devices", (done) => { - const useCase = new ListenToKnownDevicesUseCase([]); + jest.spyOn(transportService, "getAllTransports").mockReturnValue([]); + + const useCase = new ListenToKnownDevicesUseCase(transportService); const observedDiscoveredDevices: DiscoveredDevice[][] = []; useCase.execute().subscribe({ @@ -90,8 +101,12 @@ describe("ListenToKnownDevicesUseCase", () => { const { transportA, transportAKnownDevicesSubject } = setup2MockTransports(); + jest + .spyOn(transportService, "getAllTransports") + .mockReturnValue([transportA]); + const observedDiscoveredDevices: DiscoveredDevice[][] = []; - new ListenToKnownDevicesUseCase([transportA]) + new ListenToKnownDevicesUseCase(transportService) .execute() .subscribe((devices) => { observedDiscoveredDevices.push(devices); @@ -99,7 +114,7 @@ describe("ListenToKnownDevicesUseCase", () => { // When transportA emits 1 known device transportAKnownDevicesSubject.next([ - makeMockInternalDiscoveredDevice("transportA-device1"), + makeMockTransportDiscoveredDevice("transportA-device1"), ]); expect(observedDiscoveredDevices[0]).toEqual([ @@ -112,8 +127,8 @@ describe("ListenToKnownDevicesUseCase", () => { // When transportA emits 2 known devices transportAKnownDevicesSubject.next([ - makeMockInternalDiscoveredDevice("transportA-device1"), - makeMockInternalDiscoveredDevice("transportA-device2"), + makeMockTransportDiscoveredDevice("transportA-device1"), + makeMockTransportDiscoveredDevice("transportA-device2"), ]); expect(observedDiscoveredDevices[1]).toEqual([ @@ -131,7 +146,7 @@ describe("ListenToKnownDevicesUseCase", () => { // When transportA emits 1 known device (device1 disconnects) transportAKnownDevicesSubject.next([ - makeMockInternalDiscoveredDevice("transportA-device2"), + makeMockTransportDiscoveredDevice("transportA-device2"), ]); expect(observedDiscoveredDevices[2]).toEqual([ @@ -154,24 +169,26 @@ describe("ListenToKnownDevicesUseCase", () => { const { transportAKnownDevicesSubject, transportA, transportB } = setup2MockTransports(); + jest + .spyOn(transportService, "getAllTransports") + .mockReturnValue([transportA, transportB]); + const observedDiscoveredDevices: DiscoveredDevice[][] = []; const onError = jest.fn(); const onComplete = jest.fn(); - new ListenToKnownDevicesUseCase([transportA, transportB]) - .execute() - .subscribe({ - next: (devices) => { - observedDiscoveredDevices.push(devices); - }, - error: onError, - complete: onComplete, - }); + new ListenToKnownDevicesUseCase(transportService).execute().subscribe({ + next: (devices) => { + observedDiscoveredDevices.push(devices); + }, + error: onError, + complete: onComplete, + }); // When transportA emits 1 known device transportAKnownDevicesSubject.next([ - makeMockInternalDiscoveredDevice("transportA-device1"), + makeMockTransportDiscoveredDevice("transportA-device1"), ]); expect(observedDiscoveredDevices[0]).toEqual([ @@ -199,21 +216,23 @@ describe("ListenToKnownDevicesUseCase", () => { const observedDiscoveredDevices: DiscoveredDevice[][] = []; + jest + .spyOn(transportService, "getAllTransports") + .mockReturnValue([transportA, transportB]); + const onError = jest.fn(); const onComplete = jest.fn(); - new ListenToKnownDevicesUseCase([transportA, transportB]) - .execute() - .subscribe({ - next: (devices) => { - observedDiscoveredDevices.push(devices); - }, - error: onError, - complete: onComplete, - }); + new ListenToKnownDevicesUseCase(transportService).execute().subscribe({ + next: (devices) => { + observedDiscoveredDevices.push(devices); + }, + error: onError, + complete: onComplete, + }); // When transportA emits 1 known device transportAKnownDevicesSubject.next([ - makeMockInternalDiscoveredDevice("transportA-device1"), + makeMockTransportDiscoveredDevice("transportA-device1"), ]); expect(observedDiscoveredDevices[0]).toEqual([ @@ -226,7 +245,7 @@ describe("ListenToKnownDevicesUseCase", () => { // When transportB emits 1 known device transportBKnownDevicesSubject.next([ - makeMockInternalDiscoveredDevice("transportB-device1"), + makeMockTransportDiscoveredDevice("transportB-device1"), ]); expect(observedDiscoveredDevices[1]).toEqual([ @@ -244,8 +263,8 @@ describe("ListenToKnownDevicesUseCase", () => { // When transportB emits 2 known devices transportBKnownDevicesSubject.next([ - makeMockInternalDiscoveredDevice("transportB-device1"), - makeMockInternalDiscoveredDevice("transportB-device2"), + makeMockTransportDiscoveredDevice("transportB-device1"), + makeMockTransportDiscoveredDevice("transportB-device2"), ]); expect(observedDiscoveredDevices[2]).toEqual([ diff --git a/packages/device-management-kit/src/internal/discovery/use-case/ListenToKnownDevicesUseCase.ts b/packages/device-management-kit/src/internal/discovery/use-case/ListenToKnownDevicesUseCase.ts index 1b0560622..7f3980140 100644 --- a/packages/device-management-kit/src/internal/discovery/use-case/ListenToKnownDevicesUseCase.ts +++ b/packages/device-management-kit/src/internal/discovery/use-case/ListenToKnownDevicesUseCase.ts @@ -1,11 +1,12 @@ -import { injectable, multiInject } from "inversify"; +import { inject, injectable } from "inversify"; import { from, map, merge, Observable, scan } from "rxjs"; import { DeviceModel } from "@api/device/DeviceModel"; import type { Transport } from "@api/transport/model/Transport"; +import { TransportDiscoveredDevice } from "@api/transport/model/TransportDiscoveredDevice"; import { DiscoveredDevice } from "@api/types"; import { transportDiTypes } from "@internal/transport/di/transportDiTypes"; -import { InternalDiscoveredDevice } from "@internal/transport/model/InternalDiscoveredDevice"; +import { TransportService } from "@internal/transport/service/TransportService"; /** * Listen to list of known discovered devices (and later BLE). @@ -14,14 +15,14 @@ import { InternalDiscoveredDevice } from "@internal/transport/model/InternalDisc export class ListenToKnownDevicesUseCase { private readonly _transports: Transport[]; constructor( - @multiInject(transportDiTypes.Transport) - transports: Transport[], + @inject(transportDiTypes.TransportService) + transportService: TransportService, ) { - this._transports = transports; + this._transports = transportService.getAllTransports(); } - private mapInternalDiscoveredDeviceToDiscoveredDevice( - discoveredDevice: InternalDiscoveredDevice, + private mapTransportDiscoveredDeviceToDiscoveredDevice( + discoveredDevice: TransportDiscoveredDevice, ): DiscoveredDevice { return { id: discoveredDevice.id, @@ -57,17 +58,17 @@ export class ListenToKnownDevicesUseCase { ); return merge(...observablesWithIndex).pipe( - scan( - (acc, { index, arr }) => { - acc[index] = arr; - return acc; - }, - {} as { [key: number]: Array }, - ), + scan< + { index: number; arr: TransportDiscoveredDevice[] }, + { [key: number]: TransportDiscoveredDevice[] } + >((acc, { index, arr }) => { + acc[index] = arr; + return acc; + }, {}), map((acc) => Object.values(acc) .flat() - .map(this.mapInternalDiscoveredDeviceToDiscoveredDevice), + .map(this.mapTransportDiscoveredDeviceToDiscoveredDevice), ), ); } diff --git a/packages/device-management-kit/src/internal/discovery/use-case/StartDiscoveringUseCase.test.ts b/packages/device-management-kit/src/internal/discovery/use-case/StartDiscoveringUseCase.test.ts index dd31dbaec..67037d6fe 100644 --- a/packages/device-management-kit/src/internal/discovery/use-case/StartDiscoveringUseCase.test.ts +++ b/packages/device-management-kit/src/internal/discovery/use-case/StartDiscoveringUseCase.test.ts @@ -1,38 +1,37 @@ +import { Maybe } from "purify-ts"; import { of } from "rxjs"; import { DeviceModel } from "@api/device/DeviceModel"; -import { type DeviceModelId, type DiscoveredDevice } from "@api/types"; -import { type DeviceModelDataSource } from "@internal/device-model/data/DeviceModelDataSource"; -import { type InternalDeviceModel } from "@internal/device-model/model/DeviceModel"; -import { DefaultLoggerPublisherService } from "@internal/logger-publisher/service/DefaultLoggerPublisherService"; -import { type LoggerPublisherService } from "@internal/logger-publisher/service/LoggerPublisherService"; -import { type InternalDiscoveredDevice } from "@internal/transport/model/InternalDiscoveredDevice"; -import { usbHidDeviceConnectionFactoryStubBuilder } from "@internal/transport/usb/service/UsbHidDeviceConnectionFactory.stub"; -import { WebUsbHidTransport } from "@internal/transport/usb/transport/WebUsbHidTransport"; +import { type TransportDeviceModel } from "@api/device-model/model/DeviceModel"; +import { TransportStub } from "@api/transport/model/Transport.stub"; +import { type TransportDiscoveredDevice } from "@api/transport/model/TransportDiscoveredDevice"; +import { + type DeviceModelId, + type DiscoveredDevice, + type Transport, +} from "@api/types"; +import { type TransportService } from "@internal/transport/service/TransportService"; +import { TransportServiceStub } from "@internal/transport/service/TransportService.stub"; import { StartDiscoveringUseCase } from "./StartDiscoveringUseCase"; -let transport: WebUsbHidTransport; -let logger: LoggerPublisherService; +let transport: Transport; +let transportService: TransportService; describe("StartDiscoveringUseCase", () => { - const stubDiscoveredDevice: InternalDiscoveredDevice = { + const stubDiscoveredDevice: TransportDiscoveredDevice = { id: "internal-discovered-device-id", deviceModel: { id: "nanoSP" as DeviceModelId, productName: "productName", - } as InternalDeviceModel, + } as TransportDeviceModel, transport: "USB", }; - const tag = "logger-tag"; beforeEach(() => { - logger = new DefaultLoggerPublisherService([], tag); - transport = new WebUsbHidTransport( - {} as DeviceModelDataSource, - () => logger, - usbHidDeviceConnectionFactoryStubBuilder(), - ); + transport = new TransportStub(); + // @ts-expect-error stub + transportService = new TransportServiceStub(); }); afterEach(() => { @@ -46,7 +45,12 @@ describe("StartDiscoveringUseCase", () => { jest .spyOn(transport, "startDiscovering") .mockImplementation(mockedStartDiscovering); - const usecase = new StartDiscoveringUseCase([transport]); + + jest + .spyOn(transportService, "getTransport") + .mockReturnValue(Maybe.of(transport)); + + const usecase = new StartDiscoveringUseCase(transportService); const discover = usecase.execute({ transport: "USB" }); diff --git a/packages/device-management-kit/src/internal/discovery/use-case/StartDiscoveringUseCase.ts b/packages/device-management-kit/src/internal/discovery/use-case/StartDiscoveringUseCase.ts index 56b47b404..892dbc7de 100644 --- a/packages/device-management-kit/src/internal/discovery/use-case/StartDiscoveringUseCase.ts +++ b/packages/device-management-kit/src/internal/discovery/use-case/StartDiscoveringUseCase.ts @@ -1,13 +1,13 @@ -import { injectable, multiInject } from "inversify"; +import { inject, injectable } from "inversify"; import { map, mergeMap, Observable, of } from "rxjs"; import { DeviceModel } from "@api/device/DeviceModel"; import { DiscoveredDevice } from "@api/transport/model/DiscoveredDevice"; -import type { Transport } from "@api/transport/model/Transport"; +import { TransportNotSupportedError } from "@api/transport/model/Errors"; +import { TransportDiscoveredDevice } from "@api/transport/model/TransportDiscoveredDevice"; import { TransportIdentifier } from "@api/transport/model/TransportIdentifier"; import { transportDiTypes } from "@internal/transport/di/transportDiTypes"; -import { TransportNotSupportedError } from "@internal/transport/model/Errors"; -import { InternalDiscoveredDevice } from "@internal/transport/model/InternalDiscoveredDevice"; +import { TransportService } from "@internal/transport/service/TransportService"; export type StartDiscoveringUseCaseArgs = { /** @@ -25,12 +25,12 @@ export type StartDiscoveringUseCaseArgs = { @injectable() export class StartDiscoveringUseCase { constructor( - @multiInject(transportDiTypes.Transport) - private transports: Transport[], + @inject(transportDiTypes.TransportService) + private readonly _transportService: TransportService, ) {} private mapDiscoveredDevice( - device: InternalDiscoveredDevice, + device: TransportDiscoveredDevice, ): DiscoveredDevice { const deviceModel = new DeviceModel({ id: device.id, @@ -47,19 +47,9 @@ export class StartDiscoveringUseCase { execute({ transport, }: StartDiscoveringUseCaseArgs): Observable { - if (transport) { - const instance = this.transports.find( - (t) => t.getIdentifier() === transport, - ); - if (!instance) { - throw new TransportNotSupportedError(new Error("Unknown transport")); - } - return instance - .startDiscovering() - .pipe(map((device) => this.mapDiscoveredDevice(device))); - } else { - // Discover from all transports in parallel - return of(...this.transports).pipe( + if (!transport) { + const transports = this._transportService.getAllTransports(); + return of(...transports).pipe( mergeMap((instance) => instance .startDiscovering() @@ -67,5 +57,18 @@ export class StartDiscoveringUseCase { ), ); } + + const instance = this._transportService.getTransport(transport); + + return instance.caseOf({ + Just: (t) => { + return t + .startDiscovering() + .pipe(map((device) => this.mapDiscoveredDevice(device))); + }, + Nothing: () => { + throw new TransportNotSupportedError(new Error("Unknown transport")); + }, + }); } } diff --git a/packages/device-management-kit/src/internal/discovery/use-case/StopDiscoveringUseCase.test.ts b/packages/device-management-kit/src/internal/discovery/use-case/StopDiscoveringUseCase.test.ts index 7551ca79e..376162c98 100644 --- a/packages/device-management-kit/src/internal/discovery/use-case/StopDiscoveringUseCase.test.ts +++ b/packages/device-management-kit/src/internal/discovery/use-case/StopDiscoveringUseCase.test.ts @@ -1,38 +1,38 @@ -import { type DeviceModelDataSource } from "@internal/device-model/data/DeviceModelDataSource"; -import { DefaultLoggerPublisherService } from "@internal/logger-publisher/service/DefaultLoggerPublisherService"; -import { type LoggerPublisherService } from "@internal/logger-publisher/service/LoggerPublisherService"; -import { usbHidDeviceConnectionFactoryStubBuilder } from "@internal/transport/usb/service/UsbHidDeviceConnectionFactory.stub"; -import { WebUsbHidTransport } from "@internal/transport/usb/transport/WebUsbHidTransport"; +import { TransportStub } from "@api/transport/model/Transport.stub"; +import { type Transport } from "@api/types"; +import { type TransportService } from "@internal/transport/service/TransportService"; +import { TransportServiceStub } from "@internal/transport/service/TransportService.stub"; import { StopDiscoveringUseCase } from "./StopDiscoveringUseCase"; // TODO test several transports -let transports: WebUsbHidTransport[]; -let logger: LoggerPublisherService; -const tag = "logger-tag"; +let transport: Transport; +let transports: Transport[]; +let transportService: TransportService; describe("StopDiscoveringUseCase", () => { beforeEach(() => { - logger = new DefaultLoggerPublisherService([], tag); - transports = [ - new WebUsbHidTransport( - {} as DeviceModelDataSource, - () => logger, - usbHidDeviceConnectionFactoryStubBuilder(), - ), - ]; + transport = new TransportStub(); + transports = [transport]; + // @ts-expect-error stub + transportService = new TransportServiceStub(transports); }); afterEach(() => { - jest.restoreAllMocks(); + jest.clearAllMocks(); }); test("should call stop discovering", () => { const mockedStopDiscovering = jest.fn(); jest - .spyOn(transports[0]!, "stopDiscovering") + .spyOn(transport, "stopDiscovering") .mockImplementation(mockedStopDiscovering); - const usecase = new StopDiscoveringUseCase(transports); + + jest + .spyOn(transportService, "getAllTransports") + .mockReturnValue(transports); + + const usecase = new StopDiscoveringUseCase(transportService); usecase.execute(); diff --git a/packages/device-management-kit/src/internal/discovery/use-case/StopDiscoveringUseCase.ts b/packages/device-management-kit/src/internal/discovery/use-case/StopDiscoveringUseCase.ts index d99868901..31001e25a 100644 --- a/packages/device-management-kit/src/internal/discovery/use-case/StopDiscoveringUseCase.ts +++ b/packages/device-management-kit/src/internal/discovery/use-case/StopDiscoveringUseCase.ts @@ -1,7 +1,7 @@ -import { injectable, multiInject } from "inversify"; +import { inject, injectable } from "inversify"; -import type { Transport } from "@api/transport/model/Transport"; import { transportDiTypes } from "@internal/transport/di/transportDiTypes"; +import { TransportService } from "@internal/transport/service/TransportService"; /** * Stops discovering devices connected. @@ -9,12 +9,12 @@ import { transportDiTypes } from "@internal/transport/di/transportDiTypes"; @injectable() export class StopDiscoveringUseCase { constructor( - @multiInject(transportDiTypes.Transport) - private transports: Transport[], + @inject(transportDiTypes.TransportService) + private transportService: TransportService, ) {} execute(): void { - for (const transport of this.transports) { + for (const transport of this.transportService.getAllTransports()) { transport.stopDiscovering(); } } diff --git a/packages/device-management-kit/src/internal/logger-publisher/di/loggerModule.ts b/packages/device-management-kit/src/internal/logger-publisher/di/loggerModule.ts index ec22495a1..7ff140d33 100644 --- a/packages/device-management-kit/src/internal/logger-publisher/di/loggerModule.ts +++ b/packages/device-management-kit/src/internal/logger-publisher/di/loggerModule.ts @@ -1,8 +1,8 @@ import { ContainerModule, type interfaces } from "inversify"; +import { type LoggerPublisherService } from "@api/logger-publisher/service/LoggerPublisherService"; import { type LoggerSubscriberService } from "@api/logger-subscriber/service/LoggerSubscriberService"; import { DefaultLoggerPublisherService } from "@internal/logger-publisher/service/DefaultLoggerPublisherService"; -import { type LoggerPublisherService } from "@internal/logger-publisher/service/LoggerPublisherService"; import { loggerTypes } from "./loggerTypes"; diff --git a/packages/device-management-kit/src/internal/logger-publisher/service/DefaultLoggerPublisherService.stub.ts b/packages/device-management-kit/src/internal/logger-publisher/service/DefaultLoggerPublisherService.stub.ts new file mode 100644 index 000000000..36f297371 --- /dev/null +++ b/packages/device-management-kit/src/internal/logger-publisher/service/DefaultLoggerPublisherService.stub.ts @@ -0,0 +1,14 @@ +import type { LoggerPublisherService } from "@api/logger-publisher/service/LoggerPublisherService"; +import type { LoggerSubscriberService } from "@api/logger-subscriber/service/LoggerSubscriberService"; + +export class DefaultLoggerPublisherServiceStub + implements LoggerPublisherService +{ + subscribers: LoggerSubscriberService[] = []; + + _log = jest.fn(); + info = jest.fn(); + warn = jest.fn(); + debug = jest.fn(); + error = jest.fn(); +} diff --git a/packages/device-management-kit/src/internal/logger-publisher/service/DefaultLoggerPublisherService.ts b/packages/device-management-kit/src/internal/logger-publisher/service/DefaultLoggerPublisherService.ts index ae9dc62d6..41dc0cec8 100644 --- a/packages/device-management-kit/src/internal/logger-publisher/service/DefaultLoggerPublisherService.ts +++ b/packages/device-management-kit/src/internal/logger-publisher/service/DefaultLoggerPublisherService.ts @@ -1,12 +1,11 @@ import { injectable } from "inversify"; +import { LoggerPublisherService } from "@api/logger-publisher/service/LoggerPublisherService"; import { LogLevel } from "@api/logger-subscriber/model/LogLevel"; import { LogSubscriberOptions } from "@api/logger-subscriber/model/LogSubscriberOptions"; import { LoggerSubscriberService } from "@api/logger-subscriber/service/LoggerSubscriberService"; import { LogPublisherOptions } from "@internal/logger-publisher/model/LogPublisherOptions"; -import { LoggerPublisherService } from "./LoggerPublisherService"; - @injectable() export class DefaultLoggerPublisherService implements LoggerPublisherService { subscribers: LoggerSubscriberService[]; diff --git a/packages/device-management-kit/src/internal/logger-publisher/service/__mocks__/DefaultLoggerService.ts b/packages/device-management-kit/src/internal/logger-publisher/service/__mocks__/DefaultLoggerService.ts deleted file mode 100644 index 04da66364..000000000 --- a/packages/device-management-kit/src/internal/logger-publisher/service/__mocks__/DefaultLoggerService.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { type LoggerSubscriberService } from "@api/logger-subscriber/service/LoggerSubscriberService"; -import type { LoggerPublisherService } from "@internal/logger-publisher/service/LoggerPublisherService"; - -export class DefaultLoggerPublisherService implements LoggerPublisherService { - subscribers: LoggerSubscriberService[] = []; - - _log = jest.fn(); - info = jest.fn(); - warn = jest.fn(); - debug = jest.fn(); - error = jest.fn(); -} diff --git a/packages/device-management-kit/src/internal/send/use-case/SendApduUseCase.test.ts b/packages/device-management-kit/src/internal/send/use-case/SendApduUseCase.test.ts index 167bf754c..0fe5e06e0 100644 --- a/packages/device-management-kit/src/internal/send/use-case/SendApduUseCase.test.ts +++ b/packages/device-management-kit/src/internal/send/use-case/SendApduUseCase.test.ts @@ -1,5 +1,7 @@ import { Left } from "purify-ts"; +import { type LoggerPublisherService } from "@api/logger-publisher/service/LoggerPublisherService"; +import { connectedDeviceStubBuilder } from "@api/transport/model/TransportConnectedDevice.stub"; import { deviceSessionStubBuilder } from "@internal/device-session/model/DeviceSession.stub"; import { DeviceSessionNotFound, @@ -8,13 +10,11 @@ import { import { DefaultDeviceSessionService } from "@internal/device-session/service/DefaultDeviceSessionService"; import { type DeviceSessionService } from "@internal/device-session/service/DeviceSessionService"; import { DefaultLoggerPublisherService } from "@internal/logger-publisher/service/DefaultLoggerPublisherService"; -import { type LoggerPublisherService } from "@internal/logger-publisher/service/LoggerPublisherService"; import { AxiosManagerApiDataSource } from "@internal/manager-api/data/AxiosManagerApiDataSource"; import { type ManagerApiDataSource } from "@internal/manager-api/data/ManagerApiDataSource"; import { DefaultManagerApiService } from "@internal/manager-api/service/DefaultManagerApiService"; import { type ManagerApiService } from "@internal/manager-api/service/ManagerApiService"; import { SendApduUseCase } from "@internal/send/use-case/SendApduUseCase"; -import { connectedDeviceStubBuilder } from "@internal/transport/model/InternalConnectedDevice.stub"; jest.mock("@internal/manager-api/data/AxiosManagerApiDataSource"); diff --git a/packages/device-management-kit/src/internal/send/use-case/SendApduUseCase.ts b/packages/device-management-kit/src/internal/send/use-case/SendApduUseCase.ts index d730d2001..f2b24cf9a 100644 --- a/packages/device-management-kit/src/internal/send/use-case/SendApduUseCase.ts +++ b/packages/device-management-kit/src/internal/send/use-case/SendApduUseCase.ts @@ -1,11 +1,11 @@ import { inject, injectable } from "inversify"; import { ApduResponse } from "@api/device-session/ApduResponse"; +import { type LoggerPublisherService } from "@api/logger-publisher/service/LoggerPublisherService"; import { DeviceSessionId } from "@api/types"; import { deviceSessionTypes } from "@internal/device-session/di/deviceSessionTypes"; import type { DeviceSessionService } from "@internal/device-session/service/DeviceSessionService"; import { loggerTypes } from "@internal/logger-publisher/di/loggerTypes"; -import { LoggerPublisherService } from "@internal/logger-publisher/service/LoggerPublisherService"; /** * The arguments for the SendApduUseCase. diff --git a/packages/device-management-kit/src/internal/transport/ble/di/bleDiTypes.ts b/packages/device-management-kit/src/internal/transport/ble/di/bleDiTypes.ts deleted file mode 100644 index 1b25bc98e..000000000 --- a/packages/device-management-kit/src/internal/transport/ble/di/bleDiTypes.ts +++ /dev/null @@ -1,3 +0,0 @@ -export const bleDiTypes = { - BleDeviceConnectionFactory: Symbol.for("BleDeviceConnectionFactory"), -}; diff --git a/packages/device-management-kit/src/internal/transport/ble/di/bleModule.test.ts b/packages/device-management-kit/src/internal/transport/ble/di/bleModule.test.ts deleted file mode 100644 index f55f1f483..000000000 --- a/packages/device-management-kit/src/internal/transport/ble/di/bleModule.test.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Container } from "inversify"; - -import { deviceModelModuleFactory } from "@internal/device-model/di/deviceModelModule"; -import { deviceSessionModuleFactory } from "@internal/device-session/di/deviceSessionModule"; -import { loggerModuleFactory } from "@internal/logger-publisher/di/loggerModule"; - -import { bleModuleFactory } from "./bleModule"; - -describe("bleModuleFactory", () => { - let container: Container; - let mod: ReturnType; - beforeEach(() => { - mod = bleModuleFactory(); - container = new Container(); - container.load(loggerModuleFactory()); - container.load( - mod, - deviceModelModuleFactory({ stub: false }), - deviceSessionModuleFactory(), - ); - }); - - it("should return the usb module", () => { - expect(mod).toBeDefined(); - }); -}); diff --git a/packages/device-management-kit/src/internal/transport/ble/di/bleModule.ts b/packages/device-management-kit/src/internal/transport/ble/di/bleModule.ts deleted file mode 100644 index ae18d5100..000000000 --- a/packages/device-management-kit/src/internal/transport/ble/di/bleModule.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { ContainerModule } from "inversify"; - -import { BleDeviceConnectionFactory } from "@internal/transport/ble/service/BleDeviceConnectionFactory"; - -import { bleDiTypes } from "./bleDiTypes"; - -export const bleModuleFactory = () => - new ContainerModule((bind, _unbind, _isBound, _rebind) => { - bind(bleDiTypes.BleDeviceConnectionFactory).to(BleDeviceConnectionFactory); - }); diff --git a/packages/device-management-kit/src/internal/transport/ble/service/BleDeviceConnectionFactory.stub.ts b/packages/device-management-kit/src/internal/transport/ble/service/BleDeviceConnectionFactory.stub.ts deleted file mode 100644 index aed2fa665..000000000 --- a/packages/device-management-kit/src/internal/transport/ble/service/BleDeviceConnectionFactory.stub.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { defaultApduReceiverServiceStubBuilder } from "@internal/device-session/service/DefaultApduReceiverService.stub"; -import { defaultApduSenderServiceStubBuilder } from "@internal/device-session/service/DefaultApduSenderService.stub"; -import { DefaultLoggerPublisherService } from "@internal/logger-publisher/service/__mocks__/DefaultLoggerService"; -import { BleDeviceConnectionFactory } from "@internal/transport/ble/service/BleDeviceConnectionFactory"; - -const loggerFactory = () => new DefaultLoggerPublisherService(); - -export const bleDeviceConnectionFactoryStubBuilder = () => - new BleDeviceConnectionFactory( - () => defaultApduSenderServiceStubBuilder({}, loggerFactory), - () => defaultApduReceiverServiceStubBuilder({}, loggerFactory), - loggerFactory, - ); diff --git a/packages/device-management-kit/src/internal/transport/ble/service/BleDeviceConnectionFactory.ts b/packages/device-management-kit/src/internal/transport/ble/service/BleDeviceConnectionFactory.ts deleted file mode 100644 index 44f79726c..000000000 --- a/packages/device-management-kit/src/internal/transport/ble/service/BleDeviceConnectionFactory.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { inject, injectable } from "inversify"; - -import { deviceSessionTypes } from "@internal/device-session/di/deviceSessionTypes"; -import { ApduReceiverService } from "@internal/device-session/service/ApduReceiverService"; -import { ApduSenderService } from "@internal/device-session/service/ApduSenderService"; -import { DefaultApduSenderServiceConstructorArgs } from "@internal/device-session/service/DefaultApduSenderService"; -import { loggerTypes } from "@internal/logger-publisher/di/loggerTypes"; -import { LoggerPublisherService } from "@internal/logger-publisher/service/LoggerPublisherService"; -import { BleDeviceConnection } from "@internal/transport/ble/transport/BleDeviceConnection"; - -@injectable() -export class BleDeviceConnectionFactory { - constructor( - @inject(deviceSessionTypes.ApduSenderServiceFactory) - private readonly apduSenderFactory: ( - args: DefaultApduSenderServiceConstructorArgs, - ) => ApduSenderService, - @inject(deviceSessionTypes.ApduReceiverServiceFactory) - private readonly apduReceiverFactory: () => ApduReceiverService, - @inject(loggerTypes.LoggerPublisherServiceFactory) - private readonly loggerFactory: (name: string) => LoggerPublisherService, - ) {} - - public create( - writeCharacteristic: BluetoothRemoteGATTCharacteristic, - notifyCharacteristic: BluetoothRemoteGATTCharacteristic, - ): BleDeviceConnection { - return new BleDeviceConnection( - { - writeCharacteristic, - notifyCharacteristic, - apduSenderFactory: this.apduSenderFactory, - apduReceiverFactory: this.apduReceiverFactory, - }, - this.loggerFactory, - ); - } -} diff --git a/packages/device-management-kit/src/internal/transport/ble/transport/__mocks__/WebBleTransport.ts b/packages/device-management-kit/src/internal/transport/ble/transport/__mocks__/WebBleTransport.ts deleted file mode 100644 index 095afd9b9..000000000 --- a/packages/device-management-kit/src/internal/transport/ble/transport/__mocks__/WebBleTransport.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { type Observable } from "rxjs"; - -import { type Transport } from "@api/transport/model/Transport"; -import { type InternalDiscoveredDevice } from "@internal/transport/model/InternalDiscoveredDevice"; - -export class WebBleTransport implements Transport { - listenToKnownDevices(): Observable { - throw new Error("Method not implemented."); - } - isSupported = jest.fn(); - getIdentifier = jest.fn(); - connect = jest.fn(); - startDiscovering = jest.fn(); - stopDiscovering = jest.fn(); - - disconnect = jest.fn(); -} - -export function usbHidTransportMockBuilder( - props: Partial = {}, -): Transport { - return { - isSupported: jest.fn(), - getIdentifier: jest.fn(), - startDiscovering: jest.fn(), - stopDiscovering: jest.fn(), - connect: jest.fn(), - disconnect: jest.fn(), - listenToKnownDevices: jest.fn(), - ...props, - }; -} diff --git a/packages/device-management-kit/src/internal/transport/data/TransportDataSource.ts b/packages/device-management-kit/src/internal/transport/data/TransportDataSource.ts deleted file mode 100644 index 01a491198..000000000 --- a/packages/device-management-kit/src/internal/transport/data/TransportDataSource.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { type interfaces } from "inversify"; - -import { type Transport } from "@api/transport/model/Transport"; -import { BuiltinTransports } from "@api/transport/model/TransportIdentifier"; -import { WebBleTransport } from "@internal/transport/ble/transport/WebBleTransport"; -import { MockTransport } from "@internal/transport/mockserver/MockserverTransport"; -import { WebUsbHidTransport } from "@internal/transport/usb/transport/WebUsbHidTransport"; - -export class TransportDataSource { - private static transports: { - [transport in BuiltinTransports]: interfaces.Newable; - } = { - [BuiltinTransports.USB]: WebUsbHidTransport, - [BuiltinTransports.MOCK_SERVER]: MockTransport, - [BuiltinTransports.BLE]: WebBleTransport, - }; - - static get(transport: BuiltinTransports): interfaces.Newable { - return TransportDataSource.transports[transport]; - } -} diff --git a/packages/device-management-kit/src/internal/transport/di/transportDiTypes.ts b/packages/device-management-kit/src/internal/transport/di/transportDiTypes.ts index 5bba8cebe..0fd7743eb 100644 --- a/packages/device-management-kit/src/internal/transport/di/transportDiTypes.ts +++ b/packages/device-management-kit/src/internal/transport/di/transportDiTypes.ts @@ -1,4 +1,6 @@ export const transportDiTypes = { Transport: Symbol.for("Transport"), DmkConfig: Symbol.for("TransportDmkConfig"), + TransportService: Symbol.for("TransportService"), + TransportsInput: Symbol.for("TransportsInput"), }; diff --git a/packages/device-management-kit/src/internal/transport/di/transportModule.ts b/packages/device-management-kit/src/internal/transport/di/transportModule.ts index b85737287..7874e33cf 100644 --- a/packages/device-management-kit/src/internal/transport/di/transportModule.ts +++ b/packages/device-management-kit/src/internal/transport/di/transportModule.ts @@ -1,35 +1,30 @@ import { ContainerModule } from "inversify"; import { type DmkConfig } from "@api/DmkConfig"; -import { type Transport } from "@api/transport/model/Transport"; -import { type BuiltinTransports } from "@api/transport/model/TransportIdentifier"; -import { TransportDataSource } from "@internal/transport/data/TransportDataSource"; +import { type TransportFactory } from "@api/transport/model/Transport"; +import { TransportService } from "@internal/transport/service/TransportService"; import { transportDiTypes } from "./transportDiTypes"; type FactoryProps = { stub: boolean; - transports: BuiltinTransports[]; - customTransports: Transport[]; + transports: TransportFactory[]; config: DmkConfig; }; export const transportModuleFactory = ({ stub = false, transports = [], - customTransports = [], config, }: Partial = {}) => new ContainerModule((bind, _unbind, _isBound, _rebind) => { - for (const transport of transports) { - bind(transportDiTypes.Transport) - .to(TransportDataSource.get(transport)) - .inSingletonScope(); - } - for (const transport of customTransports) { - bind(transportDiTypes.Transport).toConstantValue(transport); - } + bind(transportDiTypes.TransportService) + .to(TransportService) + .inSingletonScope(); + bind(transportDiTypes.DmkConfig).toConstantValue(config); + + bind(transportDiTypes.TransportsInput).toConstantValue(transports); if (stub) { // Add stubs here } diff --git a/packages/device-management-kit/src/internal/transport/mockserver/MockserverTransport.ts b/packages/device-management-kit/src/internal/transport/mockserver/MockserverTransport.ts index 454184711..fd2880d96 100644 --- a/packages/device-management-kit/src/internal/transport/mockserver/MockserverTransport.ts +++ b/packages/device-management-kit/src/internal/transport/mockserver/MockserverTransport.ts @@ -10,27 +10,27 @@ import { inject, injectable } from "inversify"; import { Either, Left, Right } from "purify-ts"; import { from, mergeMap, Observable } from "rxjs"; -import { DeviceId } from "@api/device/DeviceModel"; +import { DeviceId, DeviceModelId } from "@api/device/DeviceModel"; import { ApduResponse } from "@api/device-session/ApduResponse"; import type { DmkConfig } from "@api/DmkConfig"; import { DmkError } from "@api/Error"; +import { LoggerPublisherService } from "@api/logger-publisher/service/LoggerPublisherService"; +import { DisconnectHandler } from "@api/transport/model/DeviceConnection"; +import { + ConnectError, + DisconnectError, + NoAccessibleDeviceError, + OpeningConnectionError, +} from "@api/transport/model/Errors"; import { Transport } from "@api/transport/model/Transport"; +import { TransportConnectedDevice } from "@api/transport/model/TransportConnectedDevice"; +import { TransportDiscoveredDevice } from "@api/transport/model/TransportDiscoveredDevice"; import { BuiltinTransports, TransportIdentifier, } from "@api/transport/model/TransportIdentifier"; import { loggerTypes } from "@internal/logger-publisher/di/loggerTypes"; -import { LoggerPublisherService } from "@internal/logger-publisher/service/LoggerPublisherService"; import { transportDiTypes } from "@internal/transport/di/transportDiTypes"; -import { DisconnectHandler } from "@internal/transport/model/DeviceConnection"; -import { - ConnectError, - DisconnectError, - NoAccessibleDeviceError, - OpeningConnectionError, -} from "@internal/transport/model/Errors"; -import { InternalConnectedDevice } from "@internal/transport/model/InternalConnectedDevice"; -import { InternalDiscoveredDevice } from "@internal/transport/model/InternalDiscoveredDevice"; @injectable() export class MockTransport implements Transport { @@ -56,33 +56,30 @@ export class MockTransport implements Transport { return this.identifier; } - listenToKnownDevices(): Observable { + listenToKnownDevices(): Observable { return from([]); } - startDiscovering(): Observable { + startDiscovering(): Observable { this.logger.debug("startDiscovering"); return from( this.mockClient.scan().then((devices: Device[]) => { - return devices.map((device: Device) => { - return { - id: device.id, - deviceModel: { - id: device.device_type, - productName: device.name, - usbProductId: 0x10, - legacyUsbProductId: 0x0001, - bootloaderUsbProductId: 0x10, - getBlockSize() { - return 32; - }, - usbOnly: true, - memorySize: 320 * 1024, - masks: [0x31100000], + return devices.map((device: Device) => ({ + id: device.id, + deviceModel: { + id: device.device_type as DeviceModelId, + productName: device.name, + usbProductId: 0x10, + bootloaderUsbProductId: 0x0001, + getBlockSize() { + return 32; }, - transport: this.identifier, - } as InternalDiscoveredDevice; - }); + usbOnly: true, + memorySize: 320 * 1024, + masks: [0x31100000], + }, + transport: this.identifier, + })); }), ).pipe(mergeMap((device) => device)); } @@ -95,7 +92,7 @@ export class MockTransport implements Transport { async connect(params: { deviceId: DeviceId; onDisconnect: DisconnectHandler; - }): Promise> { + }): Promise> { this.logger.debug("connect"); const sessionId: string = params.deviceId; try { @@ -125,7 +122,7 @@ export class MockTransport implements Transport { id: params.deviceId, type: session.device.connectivity_type, transport: this.identifier, - } as InternalConnectedDevice; + } as TransportConnectedDevice; return Right(connectedDevice); } catch (error) { return Left(new OpeningConnectionError(error as Error)); @@ -133,7 +130,7 @@ export class MockTransport implements Transport { } async disconnect(params: { - connectedDevice: InternalConnectedDevice; + connectedDevice: TransportConnectedDevice; }): Promise> { this.logger.debug("disconnect"); const sessionId: string = params.connectedDevice.id; diff --git a/packages/device-management-kit/src/internal/transport/service/TransportService.stub.ts b/packages/device-management-kit/src/internal/transport/service/TransportService.stub.ts new file mode 100644 index 000000000..c2d89d6ec --- /dev/null +++ b/packages/device-management-kit/src/internal/transport/service/TransportService.stub.ts @@ -0,0 +1,20 @@ +import { type DeviceModelDataSource } from "@api/device-model/data/DeviceModelDataSource"; +import { type DmkConfig } from "@api/DmkConfig"; +import { type Transport } from "@api/types"; +import { DefaultLoggerPublisherServiceStub } from "@internal/logger-publisher/service/DefaultLoggerPublisherService.stub"; + +const loggerFactory = (_arg: string) => new DefaultLoggerPublisherServiceStub(); +export class TransportServiceStub { + _transports: Map = new Map(); + _loggerModuleFactory = loggerFactory; + _config = {} as DmkConfig; + _deviceModelDataSource = {} as DeviceModelDataSource; + _apduSenderServiceFactory = jest.fn(); + _apduReceiverServiceFactory = jest.fn(); + _logger = loggerFactory("TransportServiceStub"); + + addTransport = jest.fn(); + addTransportInternal = jest.fn(); + getTransport = jest.fn(); + getAllTransports = jest.fn(); +} diff --git a/packages/device-management-kit/src/internal/transport/service/TransportService.ts b/packages/device-management-kit/src/internal/transport/service/TransportService.ts new file mode 100644 index 000000000..ed4414d99 --- /dev/null +++ b/packages/device-management-kit/src/internal/transport/service/TransportService.ts @@ -0,0 +1,100 @@ +import { inject, injectable } from "inversify"; +import { Maybe } from "purify-ts"; + +import { type DeviceModelDataSource } from "@api/device-model/data/DeviceModelDataSource"; +import { type ApduReceiverServiceFactory } from "@api/device-session/service/ApduReceiverService"; +import { type ApduSenderServiceFactory } from "@api/device-session/service/ApduSenderService"; +import { type DmkConfig } from "@api/DmkConfig"; +import { LoggerPublisherService } from "@api/logger-publisher/service/LoggerPublisherService"; +import { TransportAlreadyExistsError } from "@api/transport/model/Errors"; +import { TransportFactory } from "@api/transport/model/Transport"; +import { Transport } from "@api/types"; +import { deviceModelTypes } from "@internal/device-model/di/deviceModelTypes"; +import { deviceSessionTypes } from "@internal/device-session/di/deviceSessionTypes"; +import { loggerTypes } from "@internal/logger-publisher/di/loggerTypes"; +import { transportDiTypes } from "@internal/transport/di/transportDiTypes"; + +@injectable() +export class TransportService { + private _transports: Map = new Map(); + private _loggerModuleFactory: (tag: string) => LoggerPublisherService; + private _logger: LoggerPublisherService; + private _config: DmkConfig; + private _deviceModelDataSource: DeviceModelDataSource; + private _apduSenderServiceFactory: ApduSenderServiceFactory; + private _apduReceiverServiceFactory: ApduReceiverServiceFactory; + + constructor( + @inject(transportDiTypes.TransportsInput) + _transports: TransportFactory[], + @inject(transportDiTypes.DmkConfig) + _config: DmkConfig, + @inject(loggerTypes.LoggerPublisherServiceFactory) + _loggerModuleFactory: (tag: string) => LoggerPublisherService, + @inject(deviceModelTypes.DeviceModelDataSource) + _deviceModelDataSource: DeviceModelDataSource, + @inject(deviceSessionTypes.ApduSenderServiceFactory) + _apduSenderServiceFactory: ApduSenderServiceFactory, + @inject(deviceSessionTypes.ApduReceiverServiceFactory) + _apduReceiverServiceFactory: ApduReceiverServiceFactory, + ) { + this._logger = _loggerModuleFactory("TransportService"); + + if (_transports.length === 0) { + // TODO: Handle throwing error here + this._logger.warn( + "No transports provided, please check your configuration", + ); + } + + this._loggerModuleFactory = _loggerModuleFactory; + this._config = _config; + this._deviceModelDataSource = _deviceModelDataSource; + this._apduSenderServiceFactory = _apduSenderServiceFactory; + this._apduReceiverServiceFactory = _apduReceiverServiceFactory; + + console.log(`😵 Transports: ${_transports} 🎉`); + + for (const transport of _transports) { + this.addTransport(transport); + } + } + + addTransport(factory: TransportFactory) { + const transport = factory({ + deviceModelDataSource: this._deviceModelDataSource, + loggerServiceFactory: this._loggerModuleFactory, + config: this._config, + apduSenderServiceFactory: this._apduSenderServiceFactory, + apduReceiverServiceFactory: this._apduReceiverServiceFactory, + }); + + this.addTransportInternal(transport); + } + + private addTransportInternal(transport: Transport) { + const MaybeTransport = this.getTransport(transport.getIdentifier()); + + if (MaybeTransport.isJust()) { + this._logger.warn( + `Transport ${transport.getIdentifier()} already exists, please check your configuration`, + ); + + throw new TransportAlreadyExistsError( + `Transport ${transport.getIdentifier()} already exists, please check your configuration`, + ); + } + + this._transports.set(transport.getIdentifier(), transport); + } + + getTransport(identifier?: string): Maybe { + return Maybe.fromNullable( + identifier ? this._transports.get(identifier) : undefined, + ); + } + + getAllTransports(): Transport[] { + return Array.from(this._transports.values()); + } +} diff --git a/packages/device-management-kit/src/internal/transport/usb/di/usbDiTypes.ts b/packages/device-management-kit/src/internal/transport/usb/di/usbDiTypes.ts deleted file mode 100644 index 64d4b2737..000000000 --- a/packages/device-management-kit/src/internal/transport/usb/di/usbDiTypes.ts +++ /dev/null @@ -1,3 +0,0 @@ -export const usbDiTypes = { - UsbHidDeviceConnectionFactory: Symbol.for("UsbHidDeviceConnectionFactory"), -}; diff --git a/packages/device-management-kit/src/internal/transport/usb/di/usbModule.test.ts b/packages/device-management-kit/src/internal/transport/usb/di/usbModule.test.ts deleted file mode 100644 index 92b3e8d09..000000000 --- a/packages/device-management-kit/src/internal/transport/usb/di/usbModule.test.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Container } from "inversify"; - -import { deviceModelModuleFactory } from "@internal/device-model/di/deviceModelModule"; -import { deviceSessionModuleFactory } from "@internal/device-session/di/deviceSessionModule"; -import { loggerModuleFactory } from "@internal/logger-publisher/di/loggerModule"; - -import { usbModuleFactory } from "./usbModule"; - -describe("usbModuleFactory", () => { - let container: Container; - let mod: ReturnType; - beforeEach(() => { - mod = usbModuleFactory({ stub: false }); - container = new Container(); - container.load(loggerModuleFactory()); - container.load( - mod, - deviceModelModuleFactory({ stub: false }), - deviceSessionModuleFactory(), - ); - }); - - it("should return the usb module", () => { - expect(mod).toBeDefined(); - }); -}); diff --git a/packages/device-management-kit/src/internal/transport/usb/di/usbModule.ts b/packages/device-management-kit/src/internal/transport/usb/di/usbModule.ts deleted file mode 100644 index 0750d8521..000000000 --- a/packages/device-management-kit/src/internal/transport/usb/di/usbModule.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { ContainerModule } from "inversify"; - -import { UsbHidDeviceConnectionFactory } from "@internal/transport/usb/service/UsbHidDeviceConnectionFactory"; - -import { usbDiTypes } from "./usbDiTypes"; - -type FactoryProps = { - stub: boolean; -}; - -export const usbModuleFactory = ({ - stub = false, -}: Partial = {}) => - new ContainerModule((bind, _unbind, _isBound, _rebind) => { - // UsbHidDeviceConnectionFactory - bind(usbDiTypes.UsbHidDeviceConnectionFactory).to( - UsbHidDeviceConnectionFactory, - ); - - if (stub) { - // Add stubs here - } - }); diff --git a/packages/device-management-kit/src/internal/transport/usb/service/UsbHidDeviceConnectionFactory.stub.ts b/packages/device-management-kit/src/internal/transport/usb/service/UsbHidDeviceConnectionFactory.stub.ts deleted file mode 100644 index 3829fb7e3..000000000 --- a/packages/device-management-kit/src/internal/transport/usb/service/UsbHidDeviceConnectionFactory.stub.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { defaultApduReceiverServiceStubBuilder } from "@internal/device-session/service/DefaultApduReceiverService.stub"; -import { defaultApduSenderServiceStubBuilder } from "@internal/device-session/service/DefaultApduSenderService.stub"; -import { DefaultLoggerPublisherService } from "@internal/logger-publisher/service/__mocks__/DefaultLoggerService"; -import { UsbHidDeviceConnectionFactory } from "@internal/transport/usb/service/UsbHidDeviceConnectionFactory"; - -const loggerFactory = () => new DefaultLoggerPublisherService(); - -export const usbHidDeviceConnectionFactoryStubBuilder = () => - new UsbHidDeviceConnectionFactory( - () => defaultApduSenderServiceStubBuilder({}, loggerFactory), - () => defaultApduReceiverServiceStubBuilder({}, loggerFactory), - loggerFactory, - ); diff --git a/packages/device-management-kit/src/internal/transport/usb/service/UsbHidDeviceConnectionFactory.ts b/packages/device-management-kit/src/internal/transport/usb/service/UsbHidDeviceConnectionFactory.ts deleted file mode 100644 index 59d8bdcb4..000000000 --- a/packages/device-management-kit/src/internal/transport/usb/service/UsbHidDeviceConnectionFactory.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { inject, injectable } from "inversify"; -import { Maybe } from "purify-ts"; - -import { DeviceId } from "@api/types"; -import { CHANNEL_LENGTH } from "@internal/device-session/data/FramerConst"; -import { deviceSessionTypes } from "@internal/device-session/di/deviceSessionTypes"; -import { ApduReceiverService } from "@internal/device-session/service/ApduReceiverService"; -import { ApduSenderService } from "@internal/device-session/service/ApduSenderService"; -import { DefaultApduReceiverConstructorArgs } from "@internal/device-session/service/DefaultApduReceiverService"; -import { DefaultApduSenderServiceConstructorArgs } from "@internal/device-session/service/DefaultApduSenderService"; -import { FramerUtils } from "@internal/device-session/utils/FramerUtils"; -import { loggerTypes } from "@internal/logger-publisher/di/loggerTypes"; -import { LoggerPublisherService } from "@internal/logger-publisher/service/LoggerPublisherService"; -import { FRAME_SIZE } from "@internal/transport/usb/data/UsbHidConfig"; -import { UsbHidDeviceConnection } from "@internal/transport/usb/transport/UsbHidDeviceConnection"; - -@injectable() -export class UsbHidDeviceConnectionFactory { - randomChannel = Math.floor(Math.random() * 0xffff); - - constructor( - @inject(deviceSessionTypes.ApduSenderServiceFactory) - private readonly apduSenderFactory: ( - args: DefaultApduSenderServiceConstructorArgs, - ) => ApduSenderService, - @inject(deviceSessionTypes.ApduReceiverServiceFactory) - private readonly apduReceiverFactory: ( - args: DefaultApduReceiverConstructorArgs, - ) => ApduReceiverService, - @inject(loggerTypes.LoggerPublisherServiceFactory) - private readonly loggerFactory: (name: string) => LoggerPublisherService, - ) {} - - public create( - device: HIDDevice, - params: { onConnectionTerminated: () => void; deviceId: DeviceId }, - channel = Maybe.of( - FramerUtils.numberToByteArray(this.randomChannel, CHANNEL_LENGTH), - ), - ): UsbHidDeviceConnection { - return new UsbHidDeviceConnection( - { - device, - deviceId: params.deviceId, - apduSender: this.apduSenderFactory({ - frameSize: FRAME_SIZE, - channel, - padding: true, - }), - apduReceiver: this.apduReceiverFactory({ channel }), - onConnectionTerminated: params.onConnectionTerminated, - }, - this.loggerFactory, - ); - } -} diff --git a/packages/device-management-kit/src/internal/transport/usb/transport/__mocks__/WebUsbHidTransport.ts b/packages/device-management-kit/src/internal/transport/usb/transport/__mocks__/WebUsbHidTransport.ts deleted file mode 100644 index b336e8c07..000000000 --- a/packages/device-management-kit/src/internal/transport/usb/transport/__mocks__/WebUsbHidTransport.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { type Transport } from "@api/transport/model/Transport"; - -export class WebUsbHidTransport implements Transport { - isSupported = jest.fn(); - getIdentifier = jest.fn(); - connect = jest.fn(); - startDiscovering = jest.fn(); - stopDiscovering = jest.fn(); - - disconnect = jest.fn(); - listenToKnownDevices = jest.fn(); -} - -export function usbHidTransportMockBuilder( - props: Partial = {}, -): Transport { - return { - isSupported: jest.fn(), - getIdentifier: jest.fn(), - startDiscovering: jest.fn(), - stopDiscovering: jest.fn(), - connect: jest.fn(), - disconnect: jest.fn(), - listenToKnownDevices: jest.fn(), - ...props, - }; -} diff --git a/packages/flipper-plugin-client/eslint.config.mjs b/packages/flipper-plugin-client/eslint.config.mjs index 4cccdf597..1c6e9d768 100644 --- a/packages/flipper-plugin-client/eslint.config.mjs +++ b/packages/flipper-plugin-client/eslint.config.mjs @@ -3,7 +3,7 @@ import config from "@ledgerhq/eslint-config-dsdk"; export default [ ...config, { - ignores: ["eslint.config.mjs", "scripts/*.mjs"], + ignores: ["eslint.config.mjs", "scripts/*.mjs", "lib/*"], languageOptions: { parserOptions: { project: "./tsconfig.json", diff --git a/packages/signer/context-module/eslint.config.mjs b/packages/signer/context-module/eslint.config.mjs index 5d90c9c2e..aeffc26fa 100644 --- a/packages/signer/context-module/eslint.config.mjs +++ b/packages/signer/context-module/eslint.config.mjs @@ -3,7 +3,7 @@ import config from "@ledgerhq/eslint-config-dsdk"; export default [ ...config, { - ignores: ["eslint.config.mjs", "scripts/*.mjs"], + ignores: ["eslint.config.mjs", "scripts/*.mjs", "lib/*"], languageOptions: { parserOptions: { project: "./tsconfig.json", diff --git a/packages/signer/context-module/src/typed-data/domain/DefaultTypedDataContextLoader.ts b/packages/signer/context-module/src/typed-data/domain/DefaultTypedDataContextLoader.ts index 759a244a1..7b0b7fcf1 100644 --- a/packages/signer/context-module/src/typed-data/domain/DefaultTypedDataContextLoader.ts +++ b/packages/signer/context-module/src/typed-data/domain/DefaultTypedDataContextLoader.ts @@ -86,8 +86,8 @@ export class DefaultTypedDataContextLoader implements TypedDataContextLoader { address, chainId, }); - payload.ifRight((payload) => { - mappedTokens[tokenIndex] = payload; + payload.ifRight((p) => { + mappedTokens[tokenIndex] = p; }); } } @@ -105,8 +105,8 @@ export class DefaultTypedDataContextLoader implements TypedDataContextLoader { address, chainId, }); - payload.ifRight((payload) => { - mappedTokens[tokenIndex] = payload; + payload.ifRight((p) => { + mappedTokens[tokenIndex] = p; }); } } diff --git a/packages/signer/signer-btc/eslint.config.mjs b/packages/signer/signer-btc/eslint.config.mjs index efdcd9509..39852fc4b 100644 --- a/packages/signer/signer-btc/eslint.config.mjs +++ b/packages/signer/signer-btc/eslint.config.mjs @@ -3,7 +3,7 @@ import config from "@ledgerhq/eslint-config-dsdk"; export default [ ...config, { - ignores: ["eslint.config.mjs"], + ignores: ["eslint.config.mjs", "lib/*"], languageOptions: { parserOptions: { project: "./tsconfig.json", diff --git a/packages/signer/signer-eth/eslint.config.mjs b/packages/signer/signer-eth/eslint.config.mjs index efdcd9509..39852fc4b 100644 --- a/packages/signer/signer-eth/eslint.config.mjs +++ b/packages/signer/signer-eth/eslint.config.mjs @@ -3,7 +3,7 @@ import config from "@ledgerhq/eslint-config-dsdk"; export default [ ...config, { - ignores: ["eslint.config.mjs"], + ignores: ["eslint.config.mjs", "lib/*"], languageOptions: { parserOptions: { project: "./tsconfig.json", diff --git a/packages/signer/signer-solana/eslint.config.mjs b/packages/signer/signer-solana/eslint.config.mjs index efdcd9509..39852fc4b 100644 --- a/packages/signer/signer-solana/eslint.config.mjs +++ b/packages/signer/signer-solana/eslint.config.mjs @@ -3,7 +3,7 @@ import config from "@ledgerhq/eslint-config-dsdk"; export default [ ...config, { - ignores: ["eslint.config.mjs"], + ignores: ["eslint.config.mjs", "lib/*"], languageOptions: { parserOptions: { project: "./tsconfig.json", diff --git a/packages/signer/signer-utils/eslint.config.mjs b/packages/signer/signer-utils/eslint.config.mjs index efdcd9509..39852fc4b 100644 --- a/packages/signer/signer-utils/eslint.config.mjs +++ b/packages/signer/signer-utils/eslint.config.mjs @@ -3,7 +3,7 @@ import config from "@ledgerhq/eslint-config-dsdk"; export default [ ...config, { - ignores: ["eslint.config.mjs"], + ignores: ["eslint.config.mjs", "lib/*"], languageOptions: { parserOptions: { project: "./tsconfig.json", diff --git a/packages/tools/esbuild-tools/build.mjs b/packages/tools/esbuild-tools/build.mjs index 1ab858c37..d0b9be02a 100755 --- a/packages/tools/esbuild-tools/build.mjs +++ b/packages/tools/esbuild-tools/build.mjs @@ -16,7 +16,7 @@ const config = { // metafile: true, }; -const { entryPoints, tsconfig } = argv; +const { entryPoints, tsconfig, platform } = argv; if (!entryPoints) { console.error(chalk.red("Entry points are required")); @@ -93,8 +93,25 @@ const buildTypes = async () => { await $`tsc-alias --project ${tsconfig}`; }; -const build = async () => - spinner(() => Promise.all([buildBrowser(), buildNode(), buildTypes()])); +const build = async () => { + const p = []; + if (platform === "web") { + console.log(chalk.magenta("Target:", platform)); + p.push(buildBrowser()); + p.push(buildTypes()); + } else if (platform === "node") { + console.log(chalk.magenta("Target:", platform)); + p.push(buildNode()); + p.push(buildTypes()); + } else { + console.log(chalk.magenta("Building for both web and node")); + p.push(buildBrowser()); + p.push(buildNode()); + p.push(buildTypes()); + } + + return spinner(() => Promise.all(p)); +}; build() .then(() => { diff --git a/packages/tools/esbuild-tools/watch.mjs b/packages/tools/esbuild-tools/watch.mjs index 35590075b..fdd2f641c 100755 --- a/packages/tools/esbuild-tools/watch.mjs +++ b/packages/tools/esbuild-tools/watch.mjs @@ -16,7 +16,7 @@ const config = { // metafile: true, }; -const { entryPoints, tsconfig } = argv; +const { entryPoints, tsconfig, platform } = argv; if (!entryPoints) { console.error(chalk.red("Entry points are required")); @@ -92,8 +92,14 @@ const getNodeContext = async () => { const watch = async () => { const browserContext = await getBrowserContext(); const nodeContext = await getNodeContext(); - await browserContext.watch(); - await nodeContext.watch(); + if (platform === "web") { + await browserContext.watch(); + } else if (platform === "node") { + await nodeContext.watch(); + } else { + await browserContext.watch(); + await nodeContext.watch(); + } }; watch().catch((e) => { diff --git a/packages/transport-mock/eslint.config.mjs b/packages/transport-mock/eslint.config.mjs index 4cccdf597..1c6e9d768 100644 --- a/packages/transport-mock/eslint.config.mjs +++ b/packages/transport-mock/eslint.config.mjs @@ -3,7 +3,7 @@ import config from "@ledgerhq/eslint-config-dsdk"; export default [ ...config, { - ignores: ["eslint.config.mjs", "scripts/*.mjs"], + ignores: ["eslint.config.mjs", "scripts/*.mjs", "lib/*"], languageOptions: { parserOptions: { project: "./tsconfig.json", diff --git a/packages/transport/web-ble/.prettierignore b/packages/transport/web-ble/.prettierignore new file mode 100644 index 000000000..2d4df5a66 --- /dev/null +++ b/packages/transport/web-ble/.prettierignore @@ -0,0 +1,2 @@ +lib/* +coverage/* \ No newline at end of file diff --git a/packages/transport/web-ble/.prettierrc.js b/packages/transport/web-ble/.prettierrc.js new file mode 100644 index 000000000..9601e1776 --- /dev/null +++ b/packages/transport/web-ble/.prettierrc.js @@ -0,0 +1,3 @@ +module.exports = { + ...require("@ledgerhq/prettier-config-dsdk"), +}; diff --git a/packages/transport/web-ble/eslint.config.mjs b/packages/transport/web-ble/eslint.config.mjs new file mode 100644 index 000000000..39852fc4b --- /dev/null +++ b/packages/transport/web-ble/eslint.config.mjs @@ -0,0 +1,13 @@ +import config from "@ledgerhq/eslint-config-dsdk"; + +export default [ + ...config, + { + ignores: ["eslint.config.mjs", "lib/*"], + languageOptions: { + parserOptions: { + project: "./tsconfig.json", + }, + }, + }, +]; diff --git a/packages/transport/web-ble/jest.config.ts b/packages/transport/web-ble/jest.config.ts new file mode 100644 index 000000000..10dd23196 --- /dev/null +++ b/packages/transport/web-ble/jest.config.ts @@ -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: "/", +}); + +const config: JestConfigWithTsJest = { + preset: "@ledgerhq/jest-config-dsdk", + // setupFiles: ["/jest.setup.ts"], + testPathIgnorePatterns: ["/lib/esm/", "/lib/cjs/"], + collectCoverageFrom: [ + "src/**/*.ts", + "!src/**/*.stub.ts", + "!src/index.ts", + "!src/api/index.ts", + ], + moduleNameMapper: { + ...paths, + }, +}; + +export default config; diff --git a/packages/transport/web-ble/package.json b/packages/transport/web-ble/package.json new file mode 100644 index 000000000..a2db2a222 --- /dev/null +++ b/packages/transport/web-ble/package.json @@ -0,0 +1,58 @@ +{ + "name": "@ledgerhq/device-transport-kit-web-ble", + "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 src/index.ts,src/**/*.ts --tsconfig tsconfig.prod.json --platform web", + "dev": "concurrently \"pnpm watch:builds\" \"pnpm watch:types\"", + "watch:builds": "pnpm lmdk-watch --entryPoints src/index.ts,src/**/*.ts --tsconfig tsconfig.prod.json --platform web", + "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", + "uuid": "^10.0.0" + }, + "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:*", + "@types/uuid": "^10.0.0", + "@types/web-bluetooth": "^0.0.20", + "rxjs": "^7.8.1", + "ts-node": "^10.9.2" + }, + "peerDependencies": { + "@ledgerhq/device-management-kit": "workspace:*", + "rxjs": "^7.8.1" + } +} diff --git a/packages/transport/web-ble/src/api/data/WebBleConfig.ts b/packages/transport/web-ble/src/api/data/WebBleConfig.ts new file mode 100644 index 000000000..bf22523df --- /dev/null +++ b/packages/transport/web-ble/src/api/data/WebBleConfig.ts @@ -0,0 +1 @@ +export const RECONNECT_DEVICE_TIMEOUT = 6000; diff --git a/packages/transport/web-ble/src/api/index.ts b/packages/transport/web-ble/src/api/index.ts new file mode 100644 index 000000000..17c6d054b --- /dev/null +++ b/packages/transport/web-ble/src/api/index.ts @@ -0,0 +1,2 @@ +export * from "./model/Errors"; +export { webBleIdentifier, WebBleTransport } from "./transport/WebBleTransport"; diff --git a/packages/device-management-kit/src/internal/transport/ble/model/BleDevice.stub.ts b/packages/transport/web-ble/src/api/model/BleDevice.stub.ts similarity index 100% rename from packages/device-management-kit/src/internal/transport/ble/model/BleDevice.stub.ts rename to packages/transport/web-ble/src/api/model/BleDevice.stub.ts diff --git a/packages/transport/web-ble/src/api/model/Errors.ts b/packages/transport/web-ble/src/api/model/Errors.ts new file mode 100644 index 000000000..3edbecd0c --- /dev/null +++ b/packages/transport/web-ble/src/api/model/Errors.ts @@ -0,0 +1,14 @@ +import { GeneralDmkError } from "@ledgerhq/device-management-kit"; + +export class BleTransportNotSupportedError extends GeneralDmkError { + override readonly _tag = "BleTransportNotSupportedError"; + constructor(readonly err?: unknown) { + super(err); + } +} +export class BleDeviceGattServerError extends GeneralDmkError { + override readonly _tag = "BleDeviceGattServerError"; + constructor(readonly err?: unknown) { + super(err); + } +} diff --git a/packages/device-management-kit/src/internal/transport/ble/transport/BleDeviceConnection.test.ts b/packages/transport/web-ble/src/api/transport/BleDeviceConnection.test.ts similarity index 81% rename from packages/device-management-kit/src/internal/transport/ble/transport/BleDeviceConnection.test.ts rename to packages/transport/web-ble/src/api/transport/BleDeviceConnection.test.ts index 5318de88a..0f438b597 100644 --- a/packages/device-management-kit/src/internal/transport/ble/transport/BleDeviceConnection.test.ts +++ b/packages/transport/web-ble/src/api/transport/BleDeviceConnection.test.ts @@ -1,13 +1,16 @@ +import { + type ApduReceiverService, + ApduResponse, + type ApduSenderService, + defaultApduReceiverServiceStubBuilder, + defaultApduSenderServiceStubBuilder, + DeviceNotInitializedError, + type LoggerPublisherService, + type LoggerSubscriberService, +} from "@ledgerhq/device-management-kit"; import { Left, Right } from "purify-ts"; -import { type ApduReceiverService } from "@internal/device-session/service/ApduReceiverService"; -import { type ApduSenderService } from "@internal/device-session/service/ApduSenderService"; -import { defaultApduReceiverServiceStubBuilder } from "@internal/device-session/service/DefaultApduReceiverService.stub"; -import { defaultApduSenderServiceStubBuilder } from "@internal/device-session/service/DefaultApduSenderService.stub"; -import { DefaultLoggerPublisherService } from "@internal/logger-publisher/service/DefaultLoggerPublisherService"; -import { bleCharacteristicStubBuilder } from "@internal/transport/ble/model/BleDevice.stub"; -import { DeviceNotInitializedError } from "@internal/transport/model/Errors"; -import { ApduResponse } from "@root/src"; +import { bleCharacteristicStubBuilder } from "@api/model/BleDevice.stub"; import { BleDeviceConnection, type DataViewEvent } from "./BleDeviceConnection"; @@ -19,12 +22,25 @@ const EMPTY_APDU_RESPONSE = Uint8Array.from([ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ]); +class LoggerPublisherServiceStub implements LoggerPublisherService { + subscribers: LoggerSubscriberService[] = []; + tag: string; + constructor(subscribers: LoggerSubscriberService[], tag: string) { + this.subscribers = subscribers; + this.tag = tag; + } + error = jest.fn(); + warn = jest.fn(); + info = jest.fn(); + debug = jest.fn(); +} + describe("BleDeviceConnection", () => { let writeCharacteristic: BluetoothRemoteGATTCharacteristic; let notifyCharacteristic: BluetoothRemoteGATTCharacteristic; let apduSenderFactory: () => ApduSenderService; let apduReceiverFactory: () => ApduReceiverService; - const logger = (tag: string) => new DefaultLoggerPublisherService([], tag); + const logger = (tag: string) => new LoggerPublisherServiceStub([], tag); beforeEach(() => { writeCharacteristic = bleCharacteristicStubBuilder(); diff --git a/packages/device-management-kit/src/internal/transport/ble/transport/BleDeviceConnection.ts b/packages/transport/web-ble/src/api/transport/BleDeviceConnection.ts similarity index 89% rename from packages/device-management-kit/src/internal/transport/ble/transport/BleDeviceConnection.ts rename to packages/transport/web-ble/src/api/transport/BleDeviceConnection.ts index baaa2e642..fb6c920c7 100644 --- a/packages/device-management-kit/src/internal/transport/ble/transport/BleDeviceConnection.ts +++ b/packages/transport/web-ble/src/api/transport/BleDeviceConnection.ts @@ -1,25 +1,23 @@ -import { type Either, Left, Maybe, Nothing, Right } from "purify-ts"; - -import { CommandUtils } from "@api/command/utils/CommandUtils"; -import { type ApduResponse } from "@api/device-session/ApduResponse"; -import { type DmkError } from "@api/Error"; -import { type ApduReceiverService } from "@internal/device-session/service/ApduReceiverService"; -import { type ApduSenderService } from "@internal/device-session/service/ApduSenderService"; -import { type DefaultApduSenderServiceConstructorArgs } from "@internal/device-session/service/DefaultApduSenderService"; -import type { LoggerPublisherService } from "@internal/logger-publisher/service/LoggerPublisherService"; -import { type DeviceConnection } from "@internal/transport/model/DeviceConnection"; import { + type ApduReceiverService, + type ApduReceiverServiceFactory, + type ApduResponse, + type ApduSenderService, + type ApduSenderServiceFactory, + CommandUtils, + type DeviceConnection, DeviceNotInitializedError, + type DmkError, + type LoggerPublisherService, ReconnectionFailedError, -} from "@internal/transport/model/Errors"; +} from "@ledgerhq/device-management-kit"; +import { type Either, Left, Maybe, Nothing, Right } from "purify-ts"; type BleDeviceConnectionConstructorArgs = { writeCharacteristic: BluetoothRemoteGATTCharacteristic; notifyCharacteristic: BluetoothRemoteGATTCharacteristic; - apduSenderFactory: ( - args: DefaultApduSenderServiceConstructorArgs, - ) => ApduSenderService; - apduReceiverFactory: () => ApduReceiverService; + apduSenderFactory: ApduSenderServiceFactory; + apduReceiverFactory: ApduReceiverServiceFactory; }; export type DataViewEvent = Event & { @@ -33,9 +31,7 @@ export class BleDeviceConnection implements DeviceConnection { private _notifyCharacteristic: BluetoothRemoteGATTCharacteristic; private readonly _logger: LoggerPublisherService; private _apduSender: Maybe; - private readonly _apduSenderFactory: ( - args: DefaultApduSenderServiceConstructorArgs, - ) => ApduSenderService; + private readonly _apduSenderFactory: ApduSenderServiceFactory; private readonly _apduReceiver: ApduReceiverService; private _isDeviceReady: boolean; private _sendApduPromiseResolver: Maybe<{ diff --git a/packages/device-management-kit/src/internal/transport/ble/transport/WebBleTransport.test.ts b/packages/transport/web-ble/src/api/transport/WebBleTransport.test.ts similarity index 89% rename from packages/device-management-kit/src/internal/transport/ble/transport/WebBleTransport.test.ts rename to packages/transport/web-ble/src/api/transport/WebBleTransport.test.ts index c417bb831..2a9b188a3 100644 --- a/packages/device-management-kit/src/internal/transport/ble/transport/WebBleTransport.test.ts +++ b/packages/transport/web-ble/src/api/transport/WebBleTransport.test.ts @@ -1,38 +1,56 @@ -import { Left, Right } from "purify-ts"; - -import { type DeviceModel } from "@api/device/DeviceModel"; -import { StaticDeviceModelDataSource } from "@internal/device-model/data/StaticDeviceModelDataSource"; -import { DefaultLoggerPublisherService } from "@internal/logger-publisher/service/DefaultLoggerPublisherService"; -import { bleDeviceStubBuilder } from "@internal/transport/ble/model/BleDevice.stub"; -import { bleDeviceConnectionFactoryStubBuilder } from "@internal/transport/ble/service/BleDeviceConnectionFactory.stub"; import { - BleDeviceGattServerError, - BleTransportNotSupportedError, + type ApduReceiverServiceFactory, + type ApduSenderServiceFactory, + type DeviceModel, + type LoggerPublisherService, + type LoggerSubscriberService, NoAccessibleDeviceError, OpeningConnectionError, + StaticDeviceModelDataSource, + type TransportDiscoveredDevice, UnknownDeviceError, -} from "@internal/transport/model/Errors"; -import { type InternalDiscoveredDevice } from "@internal/transport/model/InternalDiscoveredDevice"; -import { RECONNECT_DEVICE_TIMEOUT } from "@internal/transport/usb/data/UsbHidConfig"; +} from "@ledgerhq/device-management-kit"; +import { Left, Right } from "purify-ts"; + +import { RECONNECT_DEVICE_TIMEOUT } from "@api/data/WebBleConfig"; +import { bleDeviceStubBuilder } from "@api/model/BleDevice.stub"; +import { BleTransportNotSupportedError } from "@api/model/Errors"; +import { BleDeviceGattServerError } from "@api/model/Errors"; import { WebBleTransport } from "./WebBleTransport"; -jest.mock("@internal/logger-publisher/service/LoggerPublisherService"); +class LoggerPublisherServiceStub implements LoggerPublisherService { + subscribers: LoggerSubscriberService[] = []; + tag: string; + constructor(subscribers: LoggerSubscriberService[], tag: string) { + this.subscribers = subscribers; + this.tag = tag; + } + error = jest.fn(); + warn = jest.fn(); + info = jest.fn(); + debug = jest.fn(); +} // Our StaticDeviceModelDataSource can directly be used in our unit tests const bleDeviceModelDataSource = new StaticDeviceModelDataSource(); -const logger = new DefaultLoggerPublisherService([], "web-ble"); +const logger = new LoggerPublisherServiceStub([], "web-ble"); const stubDevice: BluetoothDevice = bleDeviceStubBuilder(); describe("WebBleTransport", () => { let transport: WebBleTransport; + let apduReceiverServiceFactoryStub: ApduReceiverServiceFactory; + let apduSenderServiceFactoryStub: ApduSenderServiceFactory; beforeEach(() => { + apduReceiverServiceFactoryStub = jest.fn(); + apduSenderServiceFactoryStub = jest.fn(); transport = new WebBleTransport( bleDeviceModelDataSource, () => logger, - bleDeviceConnectionFactoryStubBuilder(), + apduSenderServiceFactoryStub, + apduReceiverServiceFactoryStub, ); jest.useFakeTimers(); }); @@ -42,7 +60,7 @@ describe("WebBleTransport", () => { }); const discoverDevice = ( - onSuccess: (discoveredDevice: InternalDiscoveredDevice) => void, + onSuccess: (discoveredDevice: TransportDiscoveredDevice) => void, onError?: (error: unknown) => void, ) => { transport.startDiscovering().subscribe({ diff --git a/packages/device-management-kit/src/internal/transport/ble/transport/WebBleTransport.ts b/packages/transport/web-ble/src/api/transport/WebBleTransport.ts similarity index 80% rename from packages/device-management-kit/src/internal/transport/ble/transport/WebBleTransport.ts rename to packages/transport/web-ble/src/api/transport/WebBleTransport.ts index 06491abf8..f72a9378f 100644 --- a/packages/device-management-kit/src/internal/transport/ble/transport/WebBleTransport.ts +++ b/packages/transport/web-ble/src/api/transport/WebBleTransport.ts @@ -1,39 +1,38 @@ -import { inject, injectable } from "inversify"; -import { Either, EitherAsync, Left, Maybe, Right } from "purify-ts"; -import { from, Observable, switchMap, timer } from "rxjs"; -import { v4 as uuid } from "uuid"; - -import { DeviceId } from "@api/device/DeviceModel"; -import { ConnectionType } from "@api/discovery/ConnectionType"; -import { DmkError } from "@api/Error"; -import { Transport } from "@api/transport/model/Transport"; -import { - BuiltinTransports, - TransportIdentifier, -} from "@api/transport/model/TransportIdentifier"; -import type { DeviceModelDataSource } from "@internal/device-model/data/DeviceModelDataSource"; -import { deviceModelTypes } from "@internal/device-model/di/deviceModelTypes"; -import { loggerTypes } from "@internal/logger-publisher/di/loggerTypes"; -import type { LoggerPublisherService } from "@internal/logger-publisher/service/LoggerPublisherService"; -import { bleDiTypes } from "@internal/transport/ble/di/bleDiTypes"; -import { BleDeviceInfos } from "@internal/transport/ble/model/BleDeviceInfos"; -import { BleDeviceConnectionFactory } from "@internal/transport/ble/service/BleDeviceConnectionFactory"; -import { BleDeviceConnection } from "@internal/transport/ble/transport/BleDeviceConnection"; -import { DisconnectHandler } from "@internal/transport/model/DeviceConnection"; import { - BleDeviceGattServerError, - BleTransportNotSupportedError, - ConnectError, + type ApduReceiverServiceFactory, + type ApduSenderServiceFactory, + type BleDeviceInfos, + type ConnectError, + type ConnectionType, DeviceAlreadyConnectedError, + type DeviceId, + type DeviceModelDataSource, DeviceNotRecognizedError, + type DisconnectHandler, + type DmkError, + type LoggerPublisherService, NoAccessibleDeviceError, OpeningConnectionError, - type PromptDeviceAccessError, + type Transport, + TransportConnectedDevice, + type TransportDiscoveredDevice, + type TransportIdentifier, UnknownDeviceError, -} from "@internal/transport/model/Errors"; -import { InternalConnectedDevice } from "@internal/transport/model/InternalConnectedDevice"; -import { InternalDiscoveredDevice } from "@internal/transport/model/InternalDiscoveredDevice"; -import { RECONNECT_DEVICE_TIMEOUT } from "@internal/transport/usb/data/UsbHidConfig"; +} from "@ledgerhq/device-management-kit"; +import { type Either, EitherAsync, Left, Maybe, Right } from "purify-ts"; +import { from, type Observable, switchMap, timer } from "rxjs"; +import { v4 as uuid } from "uuid"; + +import { RECONNECT_DEVICE_TIMEOUT } from "@api/data/WebBleConfig"; +import { + BleDeviceGattServerError, + BleTransportNotSupportedError, +} from "@api/model/Errors"; +import { BleDeviceConnection } from "@api/transport/BleDeviceConnection"; + +type PromptDeviceAccessError = + | NoAccessibleDeviceError + | BleTransportNotSupportedError; // An attempt to manage the state of several devices with one transport. Not final. type WebBleInternalDevice = { @@ -41,10 +40,11 @@ type WebBleInternalDevice = { bleDevice: BluetoothDevice; bleDeviceInfos: BleDeviceInfos; bleGattService: BluetoothRemoteGATTService; - discoveredDevice: InternalDiscoveredDevice; + discoveredDevice: TransportDiscoveredDevice; }; -@injectable() +export const webBleIdentifier: TransportIdentifier = "WEB-BLE"; + export class WebBleTransport implements Transport { private readonly _connectedDevices: Array; private readonly _internalDevicesById: Map; @@ -52,21 +52,21 @@ export class WebBleTransport implements Transport { private _disconnectionHandlersById: Map void>; private _logger: LoggerPublisherService; private readonly connectionType: ConnectionType = "BLE"; - private readonly identifier: TransportIdentifier = BuiltinTransports.BLE; + private readonly identifier: TransportIdentifier = webBleIdentifier; constructor( - @inject(deviceModelTypes.DeviceModelDataSource) - private _deviceModelDataSource: DeviceModelDataSource, - @inject(loggerTypes.LoggerPublisherServiceFactory) - loggerServiceFactory: (tag: string) => LoggerPublisherService, - @inject(bleDiTypes.BleDeviceConnectionFactory) - private _bleDeviceConnectionFactory: BleDeviceConnectionFactory, + private readonly _deviceModelDataSource: DeviceModelDataSource, + private readonly _loggerServiceFactory: ( + tag: string, + ) => LoggerPublisherService, + private readonly _apduSenderFactory: ApduSenderServiceFactory, + private readonly _apduReceiverFactory: ApduReceiverServiceFactory, ) { this._connectedDevices = []; this._internalDevicesById = new Map(); this._deviceConnectionById = new Map(); this._disconnectionHandlersById = new Map(); - this._logger = loggerServiceFactory("WebBleTransport"); + this._logger = _loggerServiceFactory("WebBleTransport"); } /** @@ -94,7 +94,7 @@ export class WebBleTransport implements Transport { return this.identifier; } - listenToKnownDevices(): Observable { + listenToKnownDevices(): Observable { return from([]); } @@ -183,7 +183,7 @@ export class WebBleTransport implements Transport { */ private getDiscoveredDeviceFrom( bleDeviceInfos: BleDeviceInfos, - ): InternalDiscoveredDevice { + ): TransportDiscoveredDevice { return { id: uuid(), deviceModel: bleDeviceInfos.deviceModel, @@ -200,7 +200,7 @@ export class WebBleTransport implements Transport { * @private */ private setInternalDeviceFrom( - discoveredDevice: InternalDiscoveredDevice, + discoveredDevice: TransportDiscoveredDevice, bleDevice: BluetoothDevice, bleDeviceInfos: BleDeviceInfos, bleGattService: BluetoothRemoteGATTService, @@ -221,9 +221,9 @@ export class WebBleTransport implements Transport { /** * Main method to get a device from a button click handler - * The GATT connection is done here in order to populate InternalDiscoveredDevice with deviceModel + * The GATT connection is done here in order to populate TransportDiscoveredDevice with deviceModel */ - startDiscovering(): Observable { + startDiscovering(): Observable { this._logger.debug("startDiscovering"); return from(this.promptDeviceAccess()).pipe( @@ -284,7 +284,7 @@ export class WebBleTransport implements Transport { }: { deviceId: DeviceId; onDisconnect: DisconnectHandler; - }): Promise> { + }): Promise> { const internalDevice = this._internalDevicesById.get(deviceId); if (!internalDevice) { @@ -315,12 +315,20 @@ export class WebBleTransport implements Transport { internalDevice.bleDeviceInfos.notifyUuid, ), ]); - const deviceConnection = this._bleDeviceConnectionFactory.create( - writeCharacteristic, - notifyCharacteristic, + + const deviceConnection = new BleDeviceConnection( + { + writeCharacteristic, + notifyCharacteristic, + apduReceiverFactory: this._apduReceiverFactory, + apduSenderFactory: this._apduSenderFactory, + }, + this._loggerServiceFactory, ); + await deviceConnection.setup(); - const connectedDevice = new InternalConnectedDevice({ + + const connectedDevice = new TransportConnectedDevice({ sendApdu: (apdu, triggersDisconnection) => deviceConnection.sendApdu(apdu, triggersDisconnection), deviceModel, @@ -328,20 +336,27 @@ export class WebBleTransport implements Transport { type: this.connectionType, transport: this.identifier, }); + internalDevice.bleDevice.ongattserverdisconnected = this._getDeviceDisconnectedHandler(internalDevice, deviceConnection); + this._deviceConnectionById.set(internalDevice.id, deviceConnection); this._disconnectionHandlersById.set(internalDevice.id, () => { this.disconnect({ connectedDevice }).then(() => onDisconnect(deviceId)); }); + this._connectedDevices.push(internalDevice.bleDevice); + return Right(connectedDevice); } catch (error) { await internalDevice.bleDevice.forget(); + this._internalDevicesById.delete(deviceId); + this._logger.error("Error while getting characteristics", { data: { error }, }); + return Left(new OpeningConnectionError(error)); } } @@ -369,12 +384,16 @@ export class WebBleTransport implements Transport { disconnectHandler.map((handler) => handler()); }, ); + // connect to the navigator device await internalDevice.bleDevice.gatt?.connect(); + // cancel disconnection timeout disconnectObserver.unsubscribe(); + // retrieve new ble characteristics const service = await this.getBleGattService(internalDevice.bleDevice); + if (service.isRight()) { const [writeC, notifyC] = await Promise.all([ service @@ -384,6 +403,7 @@ export class WebBleTransport implements Transport { .extract() .getCharacteristic(internalDevice.bleDeviceInfos.notifyUuid), ]); + // reconnect device connection await deviceConnection.reconnect(writeC, notifyC); } @@ -393,32 +413,40 @@ export class WebBleTransport implements Transport { /** * Disconnect from a BLE device and delete its handlers * - * @param params { connectedDevice: InternalConnectedDevice } + * @param params { connectedDevice: TransportConnectedDevice } */ async disconnect(params: { - connectedDevice: InternalConnectedDevice; + connectedDevice: TransportConnectedDevice; }): Promise> { // retrieve internal device const maybeInternalDevice = Maybe.fromNullable( this._internalDevicesById.get(params.connectedDevice.id), ); + this._logger.debug("disconnect device", { data: { connectedDevice: params.connectedDevice }, }); if (maybeInternalDevice.isNothing()) { this._logger.error(`Unknown device ${params.connectedDevice.id}`); - return Left( - new UnknownDeviceError(`Unknown device ${params.connectedDevice.id}`), + + return Promise.resolve( + Left( + new UnknownDeviceError(`Unknown device ${params.connectedDevice.id}`), + ), ); } + maybeInternalDevice.map((device) => { const { bleDevice } = device; + // retrieve device connection and disconnect it const maybeDeviceConnection = Maybe.fromNullable( this._deviceConnectionById.get(device.id), ); + maybeDeviceConnection.map((dConnection) => dConnection.disconnect()); + // disconnect device gatt server if (bleDevice.gatt?.connected) { bleDevice.gatt.disconnect(); @@ -427,6 +455,7 @@ export class WebBleTransport implements Transport { this._internalDevicesById.delete(device.id); this._deviceConnectionById.delete(device.id); this._disconnectionHandlersById.delete(device.id); + if (this._connectedDevices.includes(bleDevice)) { delete this._connectedDevices[ this._connectedDevices.indexOf(bleDevice) @@ -434,6 +463,6 @@ export class WebBleTransport implements Transport { } }); - return Right(void 0); + return Promise.resolve(Right(undefined)); } } diff --git a/packages/transport/web-ble/src/index.ts b/packages/transport/web-ble/src/index.ts new file mode 100644 index 000000000..d158c5764 --- /dev/null +++ b/packages/transport/web-ble/src/index.ts @@ -0,0 +1 @@ +export * from "./api"; diff --git a/packages/transport/web-ble/tsconfig.json b/packages/transport/web-ble/tsconfig.json new file mode 100644 index 000000000..3df06a391 --- /dev/null +++ b/packages/transport/web-ble/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "@ledgerhq/tsconfig-dsdk/tsconfig.sdk", + "compilerOptions": { + "baseUrl": ".", + "outDir": "./lib/types", + "module": "esnext", + "target": "esnext", + "moduleResolution": "bundler", + "emitDeclarationOnly": true, + "paths": { + "@api/*": ["./src/api/*"] + }, + "resolveJsonModule": true + }, + "include": ["src", "jest.*.ts"] +} diff --git a/packages/transport/web-ble/tsconfig.prod.json b/packages/transport/web-ble/tsconfig.prod.json new file mode 100644 index 000000000..b90fc83e0 --- /dev/null +++ b/packages/transport/web-ble/tsconfig.prod.json @@ -0,0 +1,4 @@ +{ + "extends": "./tsconfig.json", + "include": ["src"] +} diff --git a/packages/transport/web-hid/.prettierignore b/packages/transport/web-hid/.prettierignore new file mode 100644 index 000000000..2d4df5a66 --- /dev/null +++ b/packages/transport/web-hid/.prettierignore @@ -0,0 +1,2 @@ +lib/* +coverage/* \ No newline at end of file diff --git a/packages/transport/web-hid/.prettierrc.js b/packages/transport/web-hid/.prettierrc.js new file mode 100644 index 000000000..9601e1776 --- /dev/null +++ b/packages/transport/web-hid/.prettierrc.js @@ -0,0 +1,3 @@ +module.exports = { + ...require("@ledgerhq/prettier-config-dsdk"), +}; diff --git a/packages/transport/web-hid/eslint.config.mjs b/packages/transport/web-hid/eslint.config.mjs new file mode 100644 index 000000000..39852fc4b --- /dev/null +++ b/packages/transport/web-hid/eslint.config.mjs @@ -0,0 +1,13 @@ +import config from "@ledgerhq/eslint-config-dsdk"; + +export default [ + ...config, + { + ignores: ["eslint.config.mjs", "lib/*"], + languageOptions: { + parserOptions: { + project: "./tsconfig.json", + }, + }, + }, +]; diff --git a/packages/transport/web-hid/jest.config.ts b/packages/transport/web-hid/jest.config.ts new file mode 100644 index 000000000..10dd23196 --- /dev/null +++ b/packages/transport/web-hid/jest.config.ts @@ -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: "/", +}); + +const config: JestConfigWithTsJest = { + preset: "@ledgerhq/jest-config-dsdk", + // setupFiles: ["/jest.setup.ts"], + testPathIgnorePatterns: ["/lib/esm/", "/lib/cjs/"], + collectCoverageFrom: [ + "src/**/*.ts", + "!src/**/*.stub.ts", + "!src/index.ts", + "!src/api/index.ts", + ], + moduleNameMapper: { + ...paths, + }, +}; + +export default config; diff --git a/packages/transport/web-hid/package.json b/packages/transport/web-hid/package.json new file mode 100644 index 000000000..5fa4332c6 --- /dev/null +++ b/packages/transport/web-hid/package.json @@ -0,0 +1,58 @@ +{ + "name": "@ledgerhq/device-transport-kit-web-hid", + "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 src/index.ts,src/**/*.ts --tsconfig tsconfig.prod.json --platform web", + "dev": "concurrently \"pnpm watch:builds\" \"pnpm watch:types\"", + "watch:builds": "pnpm lmdk-watch --entryPoints src/index.ts,src/**/*.ts --tsconfig tsconfig.prod.json --platform web", + "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", + "uuid": "^10.0.0" + }, + "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:*", + "@types/uuid": "^10.0.0", + "@types/w3c-web-hid": "^1.0.6", + "rxjs": "^7.8.1", + "ts-node": "^10.9.2" + }, + "peerDependencies": { + "@ledgerhq/device-management-kit": "workspace:*", + "rxjs": "^7.8.1" + } +} diff --git a/packages/device-management-kit/src/internal/transport/usb/data/UsbHidConfig.ts b/packages/transport/web-hid/src/api/data/WebHidConfig.ts similarity index 69% rename from packages/device-management-kit/src/internal/transport/usb/data/UsbHidConfig.ts rename to packages/transport/web-hid/src/api/data/WebHidConfig.ts index 536229a68..d6bbe8e17 100644 --- a/packages/device-management-kit/src/internal/transport/usb/data/UsbHidConfig.ts +++ b/packages/transport/web-hid/src/api/data/WebHidConfig.ts @@ -1,4 +1,2 @@ -// [SHOULD] Move it to device-model module -export const LEDGER_VENDOR_ID = 0x2c97; export const FRAME_SIZE = 64; export const RECONNECT_DEVICE_TIMEOUT = 6000; // in some cases, when opening/closing an app, it takes up to 6s between the HID "disconnect" and "connect" events diff --git a/packages/transport/web-hid/src/api/index.ts b/packages/transport/web-hid/src/api/index.ts new file mode 100644 index 000000000..73641295f --- /dev/null +++ b/packages/transport/web-hid/src/api/index.ts @@ -0,0 +1,5 @@ +export * from "@api/model/Errors"; +export { + webHidIdentifier, + WebHidTransport, +} from "@api/transport/WebHidTransport"; diff --git a/packages/transport/web-hid/src/api/model/Errors.ts b/packages/transport/web-hid/src/api/model/Errors.ts new file mode 100644 index 000000000..756316df0 --- /dev/null +++ b/packages/transport/web-hid/src/api/model/Errors.ts @@ -0,0 +1,14 @@ +import { GeneralDmkError } from "@ledgerhq/device-management-kit"; + +export class WebHidTransportNotSupportedError extends GeneralDmkError { + override readonly _tag = "WebHidTransportNotSupportedError"; + constructor(readonly err?: unknown) { + super(err); + } +} +export class WebHidSendReportError extends GeneralDmkError { + override readonly _tag = "WebHidSendReportError"; + constructor(readonly err?: unknown) { + super(err); + } +} diff --git a/packages/device-management-kit/src/internal/transport/usb/model/HIDDevice.stub.ts b/packages/transport/web-hid/src/api/model/HIDDevice.stub.ts similarity index 100% rename from packages/device-management-kit/src/internal/transport/usb/model/HIDDevice.stub.ts rename to packages/transport/web-hid/src/api/model/HIDDevice.stub.ts diff --git a/packages/transport/web-hid/src/api/transport/WebHidDeviceConnection.stub.ts b/packages/transport/web-hid/src/api/transport/WebHidDeviceConnection.stub.ts new file mode 100644 index 000000000..b7fca30d7 --- /dev/null +++ b/packages/transport/web-hid/src/api/transport/WebHidDeviceConnection.stub.ts @@ -0,0 +1,8 @@ +import { + type DeviceConnection, + type SendApduFnType, +} from "@ledgerhq/device-management-kit"; + +export class WebHidDeviceConnectionStub implements DeviceConnection { + sendApdu: SendApduFnType = jest.fn(); +} diff --git a/packages/device-management-kit/src/internal/transport/usb/transport/UsbHidDeviceConnection.test.ts b/packages/transport/web-hid/src/api/transport/WebHidDeviceConnection.test.ts similarity index 84% rename from packages/device-management-kit/src/internal/transport/usb/transport/UsbHidDeviceConnection.test.ts rename to packages/transport/web-hid/src/api/transport/WebHidDeviceConnection.test.ts index 634a76030..22b4220bd 100644 --- a/packages/device-management-kit/src/internal/transport/usb/transport/UsbHidDeviceConnection.test.ts +++ b/packages/transport/web-hid/src/api/transport/WebHidDeviceConnection.test.ts @@ -1,15 +1,19 @@ +import { + type ApduReceiverService, + type ApduSenderService, + defaultApduReceiverServiceStubBuilder, + defaultApduSenderServiceStubBuilder, + type DeviceId, + type LoggerPublisherService, + type LoggerSubscriberService, + ReconnectionFailedError, +} from "@ledgerhq/device-management-kit"; import { Left, Right } from "purify-ts"; -import { type DeviceId } from "@api/types"; -import { type ApduReceiverService } from "@internal/device-session/service/ApduReceiverService"; -import { type ApduSenderService } from "@internal/device-session/service/ApduSenderService"; -import { defaultApduReceiverServiceStubBuilder } from "@internal/device-session/service/DefaultApduReceiverService.stub"; -import { defaultApduSenderServiceStubBuilder } from "@internal/device-session/service/DefaultApduSenderService.stub"; -import { DefaultLoggerPublisherService } from "@internal/logger-publisher/service/DefaultLoggerPublisherService"; -import { ReconnectionFailedError } from "@internal/transport/model/Errors"; -import { RECONNECT_DEVICE_TIMEOUT } from "@internal/transport/usb/data/UsbHidConfig"; -import { hidDeviceStubBuilder } from "@internal/transport/usb/model/HIDDevice.stub"; -import { UsbHidDeviceConnection } from "@internal/transport/usb/transport/UsbHidDeviceConnection"; +import { RECONNECT_DEVICE_TIMEOUT } from "@api/data/WebHidConfig"; +import { hidDeviceStubBuilder } from "@api/model/HIDDevice.stub"; + +import { WebHidDeviceConnection } from "./WebHidDeviceConnection"; jest.useFakeTimers(); @@ -29,6 +33,19 @@ const RESPONSE_SUCCESS = new Uint8Array([ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ]); +class LoggerPublisherServiceStub implements LoggerPublisherService { + tag: string; + constructor(subscribers: LoggerSubscriberService[], tag: string) { + this.subscribers = subscribers; + this.tag = tag; + } + subscribers: LoggerSubscriberService[] = []; + error = jest.fn(); + warn = jest.fn(); + debug = jest.fn(); + info = jest.fn(); +} + /** * Flushes all pending promises */ @@ -38,13 +55,13 @@ const flushPromises = () => jest.useFakeTimers(); -describe("UsbHidDeviceConnection", () => { +describe("WebHidDeviceConnection", () => { let device: HIDDevice; let apduSender: ApduSenderService; let apduReceiver: ApduReceiverService; const onConnectionTerminated = () => {}; const deviceId: DeviceId = "test-device-id"; - const logger = (tag: string) => new DefaultLoggerPublisherService([], tag); + const logger = (tag: string) => new LoggerPublisherServiceStub([], tag); beforeEach(() => { device = hidDeviceStubBuilder({ opened: true }); @@ -54,7 +71,7 @@ describe("UsbHidDeviceConnection", () => { it("should get device", () => { // given - const connection = new UsbHidDeviceConnection( + const connection = new WebHidDeviceConnection( { device, apduSender, apduReceiver, onConnectionTerminated, deviceId }, logger, ); @@ -66,7 +83,7 @@ describe("UsbHidDeviceConnection", () => { it("should send APDU through hid report", () => { // given - const connection = new UsbHidDeviceConnection( + const connection = new WebHidDeviceConnection( { device, apduSender, apduReceiver, onConnectionTerminated, deviceId }, logger, ); @@ -86,7 +103,7 @@ describe("UsbHidDeviceConnection", () => { } as HIDInputReportEvent), ), ); - const connection = new UsbHidDeviceConnection( + const connection = new WebHidDeviceConnection( { device, apduSender, apduReceiver, onConnectionTerminated, deviceId }, logger, ); @@ -112,7 +129,7 @@ describe("UsbHidDeviceConnection", () => { } as HIDInputReportEvent), ), ); - const connection = new UsbHidDeviceConnection( + const connection = new WebHidDeviceConnection( { device, apduSender, apduReceiver, onConnectionTerminated, deviceId }, logger, ); @@ -156,7 +173,7 @@ describe("UsbHidDeviceConnection", () => { } as HIDInputReportEvent), ), ); - const connection = new UsbHidDeviceConnection( + const connection = new WebHidDeviceConnection( { device, apduSender, apduReceiver, onConnectionTerminated, deviceId }, logger, ); @@ -183,7 +200,7 @@ describe("UsbHidDeviceConnection", () => { } as HIDInputReportEvent), ), ); - const connection = new UsbHidDeviceConnection( + const connection = new WebHidDeviceConnection( { device, apduSender, apduReceiver, onConnectionTerminated, deviceId }, logger, ); @@ -212,7 +229,7 @@ describe("UsbHidDeviceConnection", () => { } as HIDInputReportEvent), ), ); - const connection = new UsbHidDeviceConnection( + const connection = new WebHidDeviceConnection( { device, apduSender, apduReceiver, onConnectionTerminated, deviceId }, logger, ); @@ -238,7 +255,7 @@ describe("UsbHidDeviceConnection", () => { } as HIDInputReportEvent), ), ); - const connection = new UsbHidDeviceConnection( + const connection = new WebHidDeviceConnection( { device, apduSender, apduReceiver, onConnectionTerminated, deviceId }, logger, ); diff --git a/packages/device-management-kit/src/internal/transport/usb/transport/UsbHidDeviceConnection.ts b/packages/transport/web-hid/src/api/transport/WebHidDeviceConnection.ts similarity index 81% rename from packages/device-management-kit/src/internal/transport/usb/transport/UsbHidDeviceConnection.ts rename to packages/transport/web-hid/src/api/transport/WebHidDeviceConnection.ts index d9275e4c1..1fb09cea9 100644 --- a/packages/device-management-kit/src/internal/transport/usb/transport/UsbHidDeviceConnection.ts +++ b/packages/transport/web-hid/src/api/transport/WebHidDeviceConnection.ts @@ -1,21 +1,21 @@ -import { inject } from "inversify"; -import { Either, Left, Right } from "purify-ts"; +import { + type ApduReceiverService, + type ApduResponse, + type ApduSenderService, + CommandUtils, + type DeviceConnection, + type DeviceId, + type DmkError, + type LoggerPublisherService, + ReconnectionFailedError, +} from "@ledgerhq/device-management-kit"; +import { type Either, Left, Right } from "purify-ts"; import { Subject } from "rxjs"; -import { CommandUtils } from "@api/command/utils/CommandUtils"; -import { ApduResponse } from "@api/device-session/ApduResponse"; -import { DmkError } from "@api/Error"; -import { DeviceId } from "@api/types"; -import { ApduReceiverService } from "@internal/device-session/service/ApduReceiverService"; -import { ApduSenderService } from "@internal/device-session/service/ApduSenderService"; -import { loggerTypes } from "@internal/logger-publisher/di/loggerTypes"; -import type { LoggerPublisherService } from "@internal/logger-publisher/service/LoggerPublisherService"; -import { DeviceConnection } from "@internal/transport/model/DeviceConnection"; -import { ReconnectionFailedError } from "@internal/transport/model/Errors"; -import { HidSendReportError } from "@internal/transport/model/Errors"; -import { RECONNECT_DEVICE_TIMEOUT } from "@internal/transport/usb/data/UsbHidConfig"; - -type UsbHidDeviceConnectionConstructorArgs = { +import { RECONNECT_DEVICE_TIMEOUT } from "@api/data/WebHidConfig"; +import { WebHidSendReportError } from "@api/model/Errors"; + +type WebHidDeviceConnectionConstructorArgs = { device: HIDDevice; deviceId: DeviceId; apduSender: ApduSenderService; @@ -28,7 +28,7 @@ type UsbHidDeviceConnectionConstructorArgs = { * It sends APDU commands to the device and receives the responses. * It handles temporary disconnections and reconnections. */ -export class UsbHidDeviceConnection implements DeviceConnection { +export class WebHidDeviceConnection implements DeviceConnection { private _device: HIDDevice; private _deviceId: DeviceId; private readonly _apduSender: ApduSenderService; @@ -54,14 +54,13 @@ export class UsbHidDeviceConnection implements DeviceConnection { apduSender, apduReceiver, onConnectionTerminated, - }: UsbHidDeviceConnectionConstructorArgs, - @inject(loggerTypes.LoggerPublisherServiceFactory) + }: WebHidDeviceConnectionConstructorArgs, loggerServiceFactory: (tag: string) => LoggerPublisherService, ) { this._apduSender = apduSender; this._apduReceiver = apduReceiver; this._onConnectionTerminated = onConnectionTerminated; - this._logger = loggerServiceFactory("UsbHidDeviceConnection"); + this._logger = loggerServiceFactory("WebHidDeviceConnection"); this._device = device; this._device.oninputreport = (event) => this.receiveHidInputReport(event); this._deviceId = deviceId; @@ -125,7 +124,7 @@ export class UsbHidDeviceConnection implements DeviceConnection { await this._device.sendReport(0, frame.getRawData()); } catch (error) { this._logger.error("Error sending frame", { data: { error } }); - return Promise.resolve(Left(new HidSendReportError(error))); + return Promise.resolve(Left(new WebHidSendReportError(error))); } } diff --git a/packages/device-management-kit/src/internal/transport/usb/transport/WebUsbHidTransport.test.ts b/packages/transport/web-hid/src/api/transport/WebHidTransport.test.ts similarity index 89% rename from packages/device-management-kit/src/internal/transport/usb/transport/WebUsbHidTransport.test.ts rename to packages/transport/web-hid/src/api/transport/WebHidTransport.test.ts index 071f88380..44c230a10 100644 --- a/packages/device-management-kit/src/internal/transport/usb/transport/WebUsbHidTransport.test.ts +++ b/packages/transport/web-hid/src/api/transport/WebHidTransport.test.ts @@ -1,30 +1,44 @@ -import { Left, Right } from "purify-ts"; -import { Subject } from "rxjs"; - -import { type DeviceModel, DeviceModelId } from "@api/device/DeviceModel"; -import { StaticDeviceModelDataSource } from "@internal/device-model/data/StaticDeviceModelDataSource"; -import { type InternalDeviceModel } from "@internal/device-model/model/DeviceModel"; -import { DefaultLoggerPublisherService } from "@internal/logger-publisher/service/DefaultLoggerPublisherService"; import { + type ApduReceiverServiceFactory, + type ApduSenderServiceFactory, + connectedDeviceStubBuilder, + type DeviceModel, + DeviceModelId, DeviceNotRecognizedError, + type LoggerPublisherService, + type LoggerSubscriberService, NoAccessibleDeviceError, OpeningConnectionError, + StaticDeviceModelDataSource, + type TransportDeviceModel, + type TransportDiscoveredDevice, UnknownDeviceError, - UsbHidTransportNotSupportedError, -} from "@internal/transport/model/Errors"; -import { connectedDeviceStubBuilder } from "@internal/transport/model/InternalConnectedDevice.stub"; -import { type InternalDiscoveredDevice } from "@internal/transport/model/InternalDiscoveredDevice"; -import { RECONNECT_DEVICE_TIMEOUT } from "@internal/transport/usb/data/UsbHidConfig"; -import { hidDeviceStubBuilder } from "@internal/transport/usb/model/HIDDevice.stub"; -import { usbHidDeviceConnectionFactoryStubBuilder } from "@internal/transport/usb/service/UsbHidDeviceConnectionFactory.stub"; +} from "@ledgerhq/device-management-kit"; +import { Left, Right } from "purify-ts"; +import { Subject } from "rxjs"; -import { WebUsbHidTransport } from "./WebUsbHidTransport"; +import { RECONNECT_DEVICE_TIMEOUT } from "@api/data/WebHidConfig"; +import { WebHidTransportNotSupportedError } from "@api/model/Errors"; +import { hidDeviceStubBuilder } from "@api/model/HIDDevice.stub"; -jest.mock("@internal/logger-publisher/service/LoggerPublisherService"); +import { WebHidTransport } from "./WebHidTransport"; + +class LoggerPublisherServiceStub implements LoggerPublisherService { + constructor(subscribers: LoggerSubscriberService[], tag: string) { + this.subscribers = subscribers; + this.tag = tag; + } + subscribers: LoggerSubscriberService[] = []; + tag: string = ""; + error = jest.fn(); + warn = jest.fn(); + info = jest.fn(); + debug = jest.fn(); +} // Our StaticDeviceModelDataSource can directly be used in our unit tests const usbDeviceModelDataSource = new StaticDeviceModelDataSource(); -const logger = new DefaultLoggerPublisherService([], "web-usb-hid"); +const logger = new LoggerPublisherServiceStub([], "web-usb-hid"); const stubDevice: HIDDevice = hidDeviceStubBuilder(); @@ -35,14 +49,19 @@ const flushPromises = () => // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access new Promise(jest.requireActual("timers").setImmediate); -describe("WebUsbHidTransport", () => { - let transport: WebUsbHidTransport; +describe("WebHidTransport", () => { + let transport: WebHidTransport; + let apduReceiverServiceFactoryStub: ApduReceiverServiceFactory; + let apduSenderServiceFactoryStub: ApduSenderServiceFactory; function initializeTransport() { - transport = new WebUsbHidTransport( + apduReceiverServiceFactoryStub = jest.fn(); + apduSenderServiceFactoryStub = jest.fn(); + transport = new WebHidTransport( usbDeviceModelDataSource, () => logger, - usbHidDeviceConnectionFactoryStubBuilder(), + apduSenderServiceFactoryStub, + apduReceiverServiceFactoryStub, ); } @@ -56,7 +75,7 @@ describe("WebUsbHidTransport", () => { }); const discoverDevice = ( - onSuccess: (discoveredDevice: InternalDiscoveredDevice) => void, + onSuccess: (discoveredDevice: TransportDiscoveredDevice) => void, onError?: (error: unknown) => void, ) => { transport.startDiscovering().subscribe({ @@ -76,7 +95,7 @@ describe("WebUsbHidTransport", () => { done("Should not emit any value"); }, (error) => { - expect(error).toBeInstanceOf(UsbHidTransportNotSupportedError); + expect(error).toBeInstanceOf(WebHidTransportNotSupportedError); done(); }, ); @@ -123,7 +142,7 @@ describe("WebUsbHidTransport", () => { }); afterEach(() => { - jest.restoreAllMocks(); + jest.clearAllMocks(); global.navigator = undefined as unknown as Navigator; }); @@ -154,6 +173,7 @@ describe("WebUsbHidTransport", () => { }, ]; }); + testCases.forEach((testCase) => { it(testCase.testTitle, (done) => { mockedRequestDevice.mockResolvedValueOnce([testCase.hidDevice]); @@ -474,6 +494,7 @@ describe("WebUsbHidTransport", () => { it("should disconnect if the device is connected", (done) => { mockedRequestDevice.mockResolvedValueOnce([stubDevice]); + mockedGetDevices.mockResolvedValue([stubDevice]); discoverDevice( (discoveredDevice) => { @@ -488,7 +509,7 @@ describe("WebUsbHidTransport", () => { transport .disconnect({ connectedDevice: device }) .then((value) => { - expect(value).toStrictEqual(Right(void 0)); + expect(value).toStrictEqual(Right(undefined)); done(); }) .catch((error) => { @@ -513,10 +534,23 @@ describe("WebUsbHidTransport", () => { // given const onDisconnect = jest.fn(); mockedRequestDevice.mockResolvedValueOnce([stubDevice]); + mockedGetDevices.mockResolvedValue([stubDevice]); // when transport.startDiscovering().subscribe({ next: (discoveredDevice) => { + const mock = { + sendApdu: jest.fn(), + device: stubDevice, + deviceId: discoveredDevice.id, + disconnect: onDisconnect, + lostConnection: jest.fn().mockImplementation(() => { + setTimeout(() => { + mock.disconnect(); + }, RECONNECT_DEVICE_TIMEOUT); + }), + }; + transport .connect({ deviceId: discoveredDevice.id, @@ -543,7 +577,7 @@ describe("WebUsbHidTransport", () => { }); describe("reconnect", () => { - it("should stop disconnection if reconnection happen", (done) => { + it.only("should stop disconnection if reconnection happen", (done) => { // given const onDisconnect = jest.fn(); @@ -554,6 +588,18 @@ describe("WebUsbHidTransport", () => { mockedGetDevices.mockResolvedValue([hidDevice1, hidDevice2]); discoverDevice(async (discoveredDevice) => { + const mock = { + sendApdu: jest.fn(), + device: hidDevice2, + deviceId: discoveredDevice.id, + disconnect: onDisconnect, + lostConnection: jest.fn().mockImplementation(() => { + setTimeout(() => { + mock.disconnect(); + }, RECONNECT_DEVICE_TIMEOUT); + }), + }; + try { await transport.connect({ deviceId: discoveredDevice.id, @@ -572,6 +618,7 @@ describe("WebUsbHidTransport", () => { expect(hidDevice2.open).toHaveBeenCalled(); await Promise.resolve(); // wait for the next tick so the hidDevice2.open promise is resolved + jest.advanceTimersByTime(RECONNECT_DEVICE_TIMEOUT); expect(onDisconnect).not.toHaveBeenCalled(); done(); @@ -638,6 +685,7 @@ describe("WebUsbHidTransport", () => { }); }); }); + describe("Connection event typeguard", () => { it("should validate type of an HIDConnectionEvent", () => { // given @@ -670,7 +718,7 @@ describe("WebUsbHidTransport", () => { const onComplete = jest.fn(); const onError = jest.fn(); - let observedDevices: InternalDiscoveredDevice[] = []; + let observedDevices: TransportDiscoveredDevice[] = []; // when transport.listenToKnownDevices().subscribe({ next: (knownDevices) => { @@ -686,7 +734,7 @@ describe("WebUsbHidTransport", () => { expect.objectContaining({ deviceModel: expect.objectContaining({ id: DeviceModelId.NANO_X, - }) as InternalDeviceModel, + }) as TransportDeviceModel, }), ]); expect(onComplete).not.toHaveBeenCalled(); @@ -712,7 +760,7 @@ describe("WebUsbHidTransport", () => { const onComplete = jest.fn(); const onError = jest.fn(); - let observedDevices: InternalDiscoveredDevice[] = []; + let observedDevices: TransportDiscoveredDevice[] = []; // when transport.listenToKnownDevices().subscribe({ next: (knownDevices) => { @@ -728,7 +776,7 @@ describe("WebUsbHidTransport", () => { expect.objectContaining({ deviceModel: expect.objectContaining({ id: DeviceModelId.NANO_X, - }) as InternalDeviceModel, + }) as TransportDeviceModel, }), ]); @@ -741,12 +789,12 @@ describe("WebUsbHidTransport", () => { expect.objectContaining({ deviceModel: expect.objectContaining({ id: DeviceModelId.NANO_X, - }) as InternalDeviceModel, + }) as TransportDeviceModel, }), expect.objectContaining({ deviceModel: expect.objectContaining({ id: DeviceModelId.STAX, - }) as InternalDeviceModel, + }) as TransportDeviceModel, }), ]); @@ -759,7 +807,7 @@ describe("WebUsbHidTransport", () => { expect.objectContaining({ deviceModel: expect.objectContaining({ id: DeviceModelId.STAX, - }) as InternalDeviceModel, + }) as TransportDeviceModel, }), ]); @@ -775,7 +823,7 @@ describe("WebUsbHidTransport", () => { const onComplete = jest.fn(); const onError = jest.fn(); - let observedDevices: InternalDiscoveredDevice[] = []; + let observedDevices: TransportDiscoveredDevice[] = []; // when transport.listenToKnownDevices().subscribe({ next: (knownDevices) => { @@ -814,7 +862,7 @@ describe("WebUsbHidTransport", () => { expect.objectContaining({ deviceModel: expect.objectContaining({ id: DeviceModelId.NANO_X, - }) as InternalDeviceModel, + }) as TransportDeviceModel, }), ]); diff --git a/packages/device-management-kit/src/internal/transport/usb/transport/WebUsbHidTransport.ts b/packages/transport/web-hid/src/api/transport/WebHidTransport.ts similarity index 71% rename from packages/device-management-kit/src/internal/transport/usb/transport/WebUsbHidTransport.ts rename to packages/transport/web-hid/src/api/transport/WebHidTransport.ts index 336f877df..d717b506f 100644 --- a/packages/device-management-kit/src/internal/transport/usb/transport/WebUsbHidTransport.ts +++ b/packages/transport/web-hid/src/api/transport/WebHidTransport.ts @@ -1,95 +1,93 @@ -import * as Sentry from "@sentry/minimal"; -import { inject, injectable } from "inversify"; -import { Either, EitherAsync, Left, Maybe, Right } from "purify-ts"; -import { BehaviorSubject, from, map, Observable, switchMap } from "rxjs"; -import { v4 as uuid } from "uuid"; - -import { DeviceId } from "@api/device/DeviceModel"; -import { ConnectionType } from "@api/discovery/ConnectionType"; -import { DmkError } from "@api/Error"; -import { Transport } from "@api/transport/model/Transport"; import { - BuiltinTransports, - TransportIdentifier, -} from "@api/transport/model/TransportIdentifier"; -import type { DeviceModelDataSource } from "@internal/device-model/data/DeviceModelDataSource"; -import { deviceModelTypes } from "@internal/device-model/di/deviceModelTypes"; -import { InternalDeviceModel } from "@internal/device-model/model/DeviceModel"; -import { loggerTypes } from "@internal/logger-publisher/di/loggerTypes"; -import type { LoggerPublisherService } from "@internal/logger-publisher/service/LoggerPublisherService"; -import { DisconnectHandler } from "@internal/transport/model/DeviceConnection"; -import { - ConnectError, + type ApduReceiverServiceFactory, + type ApduSenderServiceFactory, + type ConnectError, + type ConnectionType, + type DeviceId, + type DeviceModelDataSource, DeviceNotRecognizedError, + type DisconnectHandler, + type DmkError, + FramerUtils, + LEDGER_VENDOR_ID, + type LoggerPublisherService, NoAccessibleDeviceError, OpeningConnectionError, - type PromptDeviceAccessError, + type Transport, + TransportConnectedDevice, + type TransportDeviceModel, + type TransportDiscoveredDevice, + type TransportIdentifier, UnknownDeviceError, - UsbHidTransportNotSupportedError, -} from "@internal/transport/model/Errors"; -import { InternalConnectedDevice } from "@internal/transport/model/InternalConnectedDevice"; -import { InternalDiscoveredDevice } from "@internal/transport/model/InternalDiscoveredDevice"; -import { LEDGER_VENDOR_ID } from "@internal/transport/usb/data/UsbHidConfig"; -import { usbDiTypes } from "@internal/transport/usb/di/usbDiTypes"; -import { UsbHidDeviceConnectionFactory } from "@internal/transport/usb/service/UsbHidDeviceConnectionFactory"; -import { UsbHidDeviceConnection } from "@internal/transport/usb/transport/UsbHidDeviceConnection"; - -type WebUsbHidInternalDiscoveredDevice = InternalDiscoveredDevice & { +} from "@ledgerhq/device-management-kit"; +import * as Sentry from "@sentry/minimal"; +import { type Either, EitherAsync, Left, Maybe, Right } from "purify-ts"; +import { BehaviorSubject, from, map, type Observable, switchMap } from "rxjs"; +import { v4 as uuid } from "uuid"; + +import { FRAME_SIZE } from "@api/data/WebHidConfig"; +import { WebHidTransportNotSupportedError } from "@api/model/Errors"; +import { WebHidDeviceConnection } from "@api/transport/WebHidDeviceConnection"; + +type PromptDeviceAccessError = + | NoAccessibleDeviceError + | WebHidTransportNotSupportedError; + +type WebWebHidTransportDiscoveredDevice = TransportDiscoveredDevice & { hidDevice: HIDDevice; }; -@injectable() -export class WebUsbHidTransport implements Transport { +export const webHidIdentifier: TransportIdentifier = "WEB-HID"; + +export class WebHidTransport implements Transport { /** List of HID devices that have been discovered */ - private _internalDiscoveredDevices: BehaviorSubject< - Array - > = new BehaviorSubject>([]); + private _transportDiscoveredDevices: BehaviorSubject< + Array + > = new BehaviorSubject>([]); - /** Map of *connected* HIDDevice to their UsbHidDeviceConnection */ + /** Map of *connected* HIDDevice to their WebHidDeviceConnection */ private _deviceConnectionsByHidDevice: Map< HIDDevice, - UsbHidDeviceConnection + WebHidDeviceConnection > = new Map(); /** - * Set of UsbHidDeviceConnection for which the HIDDevice has been + * Set of WebHidDeviceConnection for which the HIDDevice has been * disconnected, so they are waiting for a reconnection */ - private _deviceConnectionsPendingReconnection: Set = + private _deviceConnectionsPendingReconnection: Set = new Set(); /** AbortController to stop listening to HID connection events */ private _connectionListenersAbortController: AbortController = new AbortController(); private _logger: LoggerPublisherService; - private _usbHidDeviceConnectionFactory: UsbHidDeviceConnectionFactory; private readonly connectionType: ConnectionType = "USB"; - private readonly identifier: TransportIdentifier = BuiltinTransports.USB; + private readonly identifier: TransportIdentifier = webHidIdentifier; constructor( - @inject(deviceModelTypes.DeviceModelDataSource) - private deviceModelDataSource: DeviceModelDataSource, - @inject(loggerTypes.LoggerPublisherServiceFactory) - loggerServiceFactory: (tag: string) => LoggerPublisherService, - @inject(usbDiTypes.UsbHidDeviceConnectionFactory) - usbHidDeviceConnectionFactory: UsbHidDeviceConnectionFactory, + private readonly _deviceModelDataSource: DeviceModelDataSource, + private readonly _loggerServiceFactory: ( + tag: string, + ) => LoggerPublisherService, + private readonly _apduSenderFactory: ApduSenderServiceFactory, + private readonly _apduReceiverFactory: ApduReceiverServiceFactory, ) { - this._logger = loggerServiceFactory("WebUsbHidTransport"); - this._usbHidDeviceConnectionFactory = usbHidDeviceConnectionFactory; + this._logger = _loggerServiceFactory("WebWebHidTransport"); this.startListeningToConnectionEvents(); } /** * Get the WebHID API if supported or error - * @returns `Either` + * @returns `Either` */ - private get hidApi(): Either { + private get hidApi(): Either { if (this.isSupported()) { return Right(navigator.hid); } - return Left(new UsbHidTransportNotSupportedError("WebHID not supported")); + return Left(new WebHidTransportNotSupportedError("WebHID not supported")); } isSupported() { @@ -113,32 +111,31 @@ export class WebUsbHidTransport implements Transport { * previously granted access through `navigator.hid.requestDevice()`. */ private async getDevices(): Promise> { - return EitherAsync.liftEither(this.hidApi) - .map(async (hidApi) => { - try { - const allDevices = await hidApi.getDevices(); - return allDevices.filter( - (hidDevice) => hidDevice.vendorId === LEDGER_VENDOR_ID, - ); - } catch (error) { - const deviceError = new NoAccessibleDeviceError(error); - this._logger.error(`getDevices: error getting devices`, { - data: { error }, - }); - Sentry.captureException(deviceError); - throw deviceError; - } - }) - .run(); + return EitherAsync.liftEither(this.hidApi).map(async (hidApi) => { + try { + const allDevices = await hidApi.getDevices(); + + return allDevices.filter( + (hidDevice) => hidDevice.vendorId === LEDGER_VENDOR_ID, + ); + } catch (error) { + const deviceError = new NoAccessibleDeviceError(error); + this._logger.error(`getDevices: error getting devices`, { + data: { error }, + }); + Sentry.captureException(deviceError); + throw deviceError; + } + }); } /** - * Map a HIDDevice to an InternalDiscoveredDevice, either by creating a new one or returning an existing one + * Map a HIDDevice to an TransportDiscoveredDevice, either by creating a new one or returning an existing one */ - private mapHIDDeviceToInternalDiscoveredDevice( + private mapHIDDeviceToTransportDiscoveredDevice( hidDevice: HIDDevice, - ): WebUsbHidInternalDiscoveredDevice { - const existingDiscoveredDevice = this._internalDiscoveredDevices + ): WebWebHidTransportDiscoveredDevice { + const existingDiscoveredDevice = this._transportDiscoveredDevices .getValue() .find((internalDevice) => internalDevice.hidDevice === hidDevice); @@ -182,15 +179,16 @@ export class WebUsbHidTransport implements Transport { /** * Listen to known devices (devices to which the user has granted access) */ - public listenToKnownDevices(): Observable { - this.updateInternalDiscoveredDevices(); - return this._internalDiscoveredDevices.pipe( + public listenToKnownDevices(): Observable { + this.updateTransportDiscoveredDevices(); + return this._transportDiscoveredDevices.pipe( map((devices) => devices.map(({ hidDevice, ...device }) => device)), ); } - private async updateInternalDiscoveredDevices(): Promise { + private async updateTransportDiscoveredDevices(): Promise { const eitherDevices = await this.getDevices(); + eitherDevices.caseOf({ Left: (error) => { this._logger.error("Error while getting accessible device", { @@ -199,9 +197,9 @@ export class WebUsbHidTransport implements Transport { Sentry.captureException(error); }, Right: (hidDevices) => { - this._internalDiscoveredDevices.next( + this._transportDiscoveredDevices.next( hidDevices.map((hidDevice) => - this.mapHIDDeviceToInternalDiscoveredDevice(hidDevice), + this.mapHIDDeviceToTransportDiscoveredDevice(hidDevice), ), ); }, @@ -224,7 +222,7 @@ export class WebUsbHidTransport implements Transport { hidDevices = await hidApi.requestDevice({ filters: [{ vendorId: LEDGER_VENDOR_ID }], }); - await this.updateInternalDiscoveredDevices(); + await this.updateTransportDiscoveredDevices(); } catch (error) { const deviceError = new NoAccessibleDeviceError(error); this._logger.error(`promptDeviceAccess: error requesting device`, { @@ -259,7 +257,7 @@ export class WebUsbHidTransport implements Transport { .run(); } - startDiscovering(): Observable { + startDiscovering(): Observable { this._logger.debug("startDiscovering"); return from(this.promptDeviceAccess()).pipe( @@ -276,8 +274,9 @@ export class WebUsbHidTransport implements Transport { this._logger.info(`Got access to ${hidDevices.length} HID devices`); const discoveredDevices = hidDevices.map((hidDevice) => { - return this.mapHIDDeviceToInternalDiscoveredDevice(hidDevice); + return this.mapHIDDeviceToTransportDiscoveredDevice(hidDevice); }); + return from(discoveredDevices); }, }); @@ -304,9 +303,7 @@ export class WebUsbHidTransport implements Transport { hidApi.addEventListener( "disconnect", - (event) => { - this.handleDeviceDisconnectionEvent(event); - }, + (event) => this.handleDeviceDisconnectionEvent(event), { signal: this._connectionListenersAbortController.signal }, ); }); @@ -326,10 +323,10 @@ export class WebUsbHidTransport implements Transport { }: { deviceId: DeviceId; onDisconnect: DisconnectHandler; - }): Promise> { + }): Promise> { this._logger.debug("connect", { data: { deviceId } }); - const matchingInternalDevice = this._internalDiscoveredDevices + const matchingInternalDevice = this._transportDiscoveredDevices .getValue() .find((internalDevice) => internalDevice.id === deviceId); @@ -360,9 +357,19 @@ export class WebUsbHidTransport implements Transport { const { deviceModel } = matchingInternalDevice; - const deviceConnection = this._usbHidDeviceConnectionFactory.create( - matchingInternalDevice.hidDevice, + const channel = Maybe.of( + FramerUtils.numberToByteArray(Math.floor(Math.random() * 0xffff), 2), + ); + const deviceConnection = new WebHidDeviceConnection( { + device: matchingInternalDevice.hidDevice, + deviceId, + apduSender: this._apduSenderFactory({ + frameSize: FRAME_SIZE, + channel, + padding: true, + }), + apduReceiver: this._apduReceiverFactory({ channel }), onConnectionTerminated: () => { onDisconnect(deviceId); this._deviceConnectionsPendingReconnection.delete(deviceConnection); @@ -371,15 +378,16 @@ export class WebUsbHidTransport implements Transport { ); deviceConnection.device.close(); }, - deviceId, }, + this._loggerServiceFactory, ); this._deviceConnectionsByHidDevice.set( matchingInternalDevice.hidDevice, deviceConnection, ); - const connectedDevice = new InternalConnectedDevice({ + + const connectedDevice = new TransportConnectedDevice({ sendApdu: (apdu, triggersDisconnection) => deviceConnection.sendApdu(apdu, triggersDisconnection), deviceModel, @@ -387,12 +395,13 @@ export class WebUsbHidTransport implements Transport { type: this.connectionType, transport: this.identifier, }); + return Right(connectedDevice); } - private getDeviceModel(hidDevice: HIDDevice): Maybe { + private getDeviceModel(hidDevice: HIDDevice): Maybe { const { productId } = hidDevice; - const matchingModel = this.deviceModelDataSource.getAllDeviceModels().find( + const matchingModel = this._deviceModelDataSource.getAllDeviceModels().find( (deviceModel) => // outside of bootloader mode, the value that we need to identify a device model is the first byte of the actual hidDevice.productId deviceModel.usbProductId === productId >> 8 || @@ -412,7 +421,7 @@ export class WebUsbHidTransport implements Transport { * Disconnect from a HID USB device */ async disconnect(params: { - connectedDevice: InternalConnectedDevice; + connectedDevice: TransportConnectedDevice; }): Promise> { this._logger.debug("disconnect", { data: { connectedDevice: params } }); @@ -467,7 +476,7 @@ export class WebUsbHidTransport implements Transport { data: { event }, }); - this.updateInternalDiscoveredDevices(); + this.updateTransportDiscoveredDevices(); try { await event.device.close(); @@ -489,12 +498,15 @@ export class WebUsbHidTransport implements Transport { } private handleDeviceReconnection( - deviceConnection: UsbHidDeviceConnection, + deviceConnection: WebHidDeviceConnection, hidDevice: HIDDevice, ) { this._deviceConnectionsPendingReconnection.delete(deviceConnection); this._deviceConnectionsByHidDevice.set(hidDevice, deviceConnection); + console.log(`🦖 Reconnecting device 🎉`); + console.log(deviceConnection); + try { deviceConnection.reconnectHidDevice(hidDevice); } catch (error) { @@ -536,7 +548,7 @@ export class WebUsbHidTransport implements Transport { * discovered device to keep the same DeviceId as the previous one in case * of a reconnection. */ - this.updateInternalDiscoveredDevices(); + this.updateTransportDiscoveredDevices(); } public destroy() { diff --git a/packages/transport/web-hid/src/index.ts b/packages/transport/web-hid/src/index.ts new file mode 100644 index 000000000..d158c5764 --- /dev/null +++ b/packages/transport/web-hid/src/index.ts @@ -0,0 +1 @@ +export * from "./api"; diff --git a/packages/transport/web-hid/tsconfig.json b/packages/transport/web-hid/tsconfig.json new file mode 100644 index 000000000..3df06a391 --- /dev/null +++ b/packages/transport/web-hid/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "@ledgerhq/tsconfig-dsdk/tsconfig.sdk", + "compilerOptions": { + "baseUrl": ".", + "outDir": "./lib/types", + "module": "esnext", + "target": "esnext", + "moduleResolution": "bundler", + "emitDeclarationOnly": true, + "paths": { + "@api/*": ["./src/api/*"] + }, + "resolveJsonModule": true + }, + "include": ["src", "jest.*.ts"] +} diff --git a/packages/transport/web-hid/tsconfig.prod.json b/packages/transport/web-hid/tsconfig.prod.json new file mode 100644 index 000000000..b90fc83e0 --- /dev/null +++ b/packages/transport/web-hid/tsconfig.prod.json @@ -0,0 +1,4 @@ +{ + "extends": "./tsconfig.json", + "include": ["src"] +} diff --git a/packages/trusted-apps/eslint.config.mjs b/packages/trusted-apps/eslint.config.mjs index efdcd9509..39852fc4b 100644 --- a/packages/trusted-apps/eslint.config.mjs +++ b/packages/trusted-apps/eslint.config.mjs @@ -3,7 +3,7 @@ import config from "@ledgerhq/eslint-config-dsdk"; export default [ ...config, { - ignores: ["eslint.config.mjs"], + ignores: ["eslint.config.mjs", "lib/*"], languageOptions: { parserOptions: { project: "./tsconfig.json", diff --git a/packages/ui/eslint.config.mjs b/packages/ui/eslint.config.mjs index efdcd9509..39852fc4b 100644 --- a/packages/ui/eslint.config.mjs +++ b/packages/ui/eslint.config.mjs @@ -3,7 +3,7 @@ import config from "@ledgerhq/eslint-config-dsdk"; export default [ ...config, { - ignores: ["eslint.config.mjs"], + ignores: ["eslint.config.mjs", "lib/*"], languageOptions: { parserOptions: { project: "./tsconfig.json", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6d262b582..1af530f5e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -57,8 +57,8 @@ importers: specifier: ^1.8.10 version: 1.8.10 turbo: - specifier: ^2.2.1 - version: 2.2.1 + specifier: ^2.2.3 + version: 2.2.3 typescript: specifier: ^5.6.3 version: 5.6.3 @@ -86,6 +86,12 @@ importers: '@ledgerhq/device-transport-kit-mock-client': specifier: workspace:* version: link:../../packages/transport-mock + '@ledgerhq/device-transport-kit-web-ble': + specifier: workspace:* + version: link:../../packages/transport/web-ble + '@ledgerhq/device-transport-kit-web-hid': + specifier: workspace:* + version: link:../../packages/transport/web-hid '@ledgerhq/react-ui': specifier: ^0.16.2 version: 0.16.2(@emotion/is-prop-valid@1.2.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react-native-svg@15.3.0(react-native@0.74.3(@babel/core@7.24.7)(@babel/preset-env@7.24.7(@babel/core@7.24.7))(@types/react@18.3.11)(react@18.3.1))(react@18.3.1))(react@18.3.1)(styled-components@5.3.11(@babel/core@7.24.7)(react-dom@18.3.1(react@18.3.1))(react-is@18.3.1)(react@18.3.1)) @@ -231,9 +237,6 @@ importers: reflect-metadata: specifier: ^0.2.2 version: 0.2.2 - rxjs: - specifier: ^7.8.1 - version: 7.8.1 semver: specifier: ^7.6.3 version: 7.6.3 @@ -265,12 +268,9 @@ importers: '@types/uuid': specifier: ^10.0.0 version: 10.0.0 - '@types/w3c-web-hid': - specifier: ^1.0.6 - version: 1.0.6 - '@types/web-bluetooth': - specifier: ^0.0.20 - version: 0.0.20 + rxjs: + specifier: ^7.8.1 + version: 7.8.1 ts-node: specifier: ^10.9.2 version: 10.9.2(@types/node@22.9.0)(typescript@5.6.3) @@ -562,6 +562,92 @@ importers: specifier: workspace:* version: link:../config/typescript + packages/transport/web-ble: + dependencies: + '@sentry/minimal': + specifier: ^6.19.7 + version: 6.19.7 + purify-ts: + specifier: ^2.1.0 + version: 2.1.0 + uuid: + specifier: ^10.0.0 + version: 10.0.0 + devDependencies: + '@ledgerhq/device-management-kit': + specifier: workspace:* + version: link:../../device-management-kit + '@ledgerhq/esbuild-tools': + specifier: workspace:* + version: link:../../tools/esbuild-tools + '@ledgerhq/eslint-config-dsdk': + specifier: workspace:* + version: link:../../config/eslint + '@ledgerhq/jest-config-dsdk': + specifier: workspace:* + version: link:../../config/jest + '@ledgerhq/prettier-config-dsdk': + specifier: workspace:* + version: link:../../config/prettier + '@ledgerhq/tsconfig-dsdk': + specifier: workspace:* + version: link:../../config/typescript + '@types/uuid': + specifier: ^10.0.0 + version: 10.0.0 + '@types/web-bluetooth': + specifier: ^0.0.20 + version: 0.0.20 + rxjs: + specifier: ^7.8.1 + version: 7.8.1 + ts-node: + specifier: ^10.9.2 + version: 10.9.2(@types/node@22.9.0)(typescript@5.6.3) + + packages/transport/web-hid: + dependencies: + '@sentry/minimal': + specifier: ^6.19.7 + version: 6.19.7 + purify-ts: + specifier: ^2.1.0 + version: 2.1.0 + uuid: + specifier: ^10.0.0 + version: 10.0.0 + devDependencies: + '@ledgerhq/device-management-kit': + specifier: workspace:* + version: link:../../device-management-kit + '@ledgerhq/esbuild-tools': + specifier: workspace:* + version: link:../../tools/esbuild-tools + '@ledgerhq/eslint-config-dsdk': + specifier: workspace:* + version: link:../../config/eslint + '@ledgerhq/jest-config-dsdk': + specifier: workspace:* + version: link:../../config/jest + '@ledgerhq/prettier-config-dsdk': + specifier: workspace:* + version: link:../../config/prettier + '@ledgerhq/tsconfig-dsdk': + specifier: workspace:* + version: link:../../config/typescript + '@types/uuid': + specifier: ^10.0.0 + version: 10.0.0 + '@types/w3c-web-hid': + specifier: ^1.0.6 + version: 1.0.6 + rxjs: + specifier: ^7.8.1 + version: 7.8.1 + ts-node: + specifier: ^10.9.2 + version: 10.9.2(@types/node@22.9.0)(typescript@5.6.3) + packages/trusted-apps: dependencies: inversify: @@ -3145,11 +3231,6 @@ packages: resolution: {integrity: sha512-TgUZgYvqZprrl7YldZNoa9OciCAyZR+Ejm9eXzKCmjsF5IKp/wgQ7Z/ZpjpGTIUPwrHQIcYeI8qDh4PsEwxMbw==} engines: {node: '>=0.4.0'} - acorn@8.13.0: - resolution: {integrity: sha512-8zSiw54Oxrdym50NlZ9sUusyO1Z1ZchgRLWRaK6c86XJFClyCgFKetdowBg5bKxyp/u+CDBJG4Mpp0m3HLZl9w==} - engines: {node: '>=0.4.0'} - hasBin: true - acorn@8.14.0: resolution: {integrity: sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==} engines: {node: '>=0.4.0'} @@ -6925,44 +7006,41 @@ packages: tslib@2.7.0: resolution: {integrity: sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==} - tslib@2.8.0: - resolution: {integrity: sha512-jWVzBLplnCmoaTr13V9dYbiQ99wvZRd0vNWaDRg+aVYRcjDF3nDksxFDE/+fkXnKhpnUUkmx5pK/v8mCtLVqZA==} - tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} - turbo-darwin-64@2.2.1: - resolution: {integrity: sha512-jltMdSQ+7rQDVaorjW729PCw6fwAn1MgZSdoa0Gil7GZCOF3SnR/ok0uJw6G5mdm6F5XM8ZTlz+mdGzBLuBRaA==} + turbo-darwin-64@2.2.3: + resolution: {integrity: sha512-Rcm10CuMKQGcdIBS3R/9PMeuYnv6beYIHqfZFeKWVYEWH69sauj4INs83zKMTUiZJ3/hWGZ4jet9AOwhsssLyg==} cpu: [x64] os: [darwin] - turbo-darwin-arm64@2.2.1: - resolution: {integrity: sha512-RHW0c1NonsJXXlutlZeunmhLanf0/WbeizFfYgWuTEaJE4MbbhyD/RG4Fm/7iob5kxQ4Es2TzfDPqyMqpIO0GA==} + turbo-darwin-arm64@2.2.3: + resolution: {integrity: sha512-+EIMHkuLFqUdJYsA3roj66t9+9IciCajgj+DVek+QezEdOJKcRxlvDOS2BUaeN8kEzVSsNiAGnoysFWYw4K0HA==} cpu: [arm64] os: [darwin] - turbo-linux-64@2.2.1: - resolution: {integrity: sha512-RasrjV+i2B90hoR8r6B2Btf2/ebNT5MJbhkpY0G1EN06E1IkjCKfAXj/1Dwmjy9+Zo0NC2r69L3HxRrtpar8jQ==} + turbo-linux-64@2.2.3: + resolution: {integrity: sha512-UBhJCYnqtaeOBQLmLo8BAisWbc9v9daL9G8upLR+XGj6vuN/Nz6qUAhverN4Pyej1g4Nt1BhROnj6GLOPYyqxQ==} cpu: [x64] os: [linux] - turbo-linux-arm64@2.2.1: - resolution: {integrity: sha512-LNkUUJuu1gNkhlo7Ky/zilXEiajLoGlWLiKT1XV5neEf+x1s+aU9Hzd/+HhSVMiyI8l7z6zLbrM1a6+v4co/SQ==} + turbo-linux-arm64@2.2.3: + resolution: {integrity: sha512-hJYT9dN06XCQ3jBka/EWvvAETnHRs3xuO/rb5bESmDfG+d9yQjeTMlhRXKrr4eyIMt6cLDt1LBfyi+6CQ+VAwQ==} cpu: [arm64] os: [linux] - turbo-windows-64@2.2.1: - resolution: {integrity: sha512-Mn5tlFrLzlQ6tW6wTWNlyT1osXuDUg0VT1VAjRpmRXlK2Zi3oKVVG0rs0nkkq4rmuheryD1xyuGPN9nFKbAn/A==} + turbo-windows-64@2.2.3: + resolution: {integrity: sha512-NPrjacrZypMBF31b4HE4ROg4P3nhMBPHKS5WTpMwf7wydZ8uvdEHpESVNMOtqhlp857zbnKYgP+yJF30H3N2dQ==} cpu: [x64] os: [win32] - turbo-windows-arm64@2.2.1: - resolution: {integrity: sha512-bvYOJ3SMN00yiem+uAqwRMbUMau/KiMzJYxnD0YkFo6INc08z8gZi5g0GLZAR7g/L3JegktX3UQW2cJvryjvLg==} + turbo-windows-arm64@2.2.3: + resolution: {integrity: sha512-fnNrYBCqn6zgKPKLHu4sOkihBI/+0oYFr075duRxqUZ+1aLWTAGfHZLgjVeLh3zR37CVzuerGIPWAEkNhkWEIw==} cpu: [arm64] os: [win32] - turbo@2.2.1: - resolution: {integrity: sha512-clZFkh6U6NpsLKBVZYRjlZjRTfju1Z5STqvFVaOGu5443uM75alJe1nCYH9pQ9YJoiOvXAqA2rDHWN5kLS9JMg==} + turbo@2.2.3: + resolution: {integrity: sha512-5lDvSqIxCYJ/BAd6rQGK/AzFRhBkbu4JHVMLmGh/hCb7U3CqSnr5Tjwfy9vc+/5wG2DJ6wttgAaA7MoCgvBKZQ==} hasBin: true type-check@0.4.0: @@ -10625,7 +10703,7 @@ snapshots: '@swc/helpers@0.5.5': dependencies: '@swc/counter': 0.1.3 - tslib: 2.8.0 + tslib: 2.8.1 '@tippyjs/react@4.2.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: @@ -10996,8 +11074,6 @@ snapshots: acorn-walk@8.3.1: {} - acorn@8.13.0: {} - acorn@8.14.0: {} aes-js@3.0.0: {} @@ -11155,7 +11231,7 @@ snapshots: ast-types@0.13.4: dependencies: - tslib: 2.8.0 + tslib: 2.8.1 ast-types@0.15.2: dependencies: @@ -12558,7 +12634,7 @@ snapshots: framer-motion@11.7.0(@emotion/is-prop-valid@1.2.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - tslib: 2.8.0 + tslib: 2.8.1 optionalDependencies: '@emotion/is-prop-valid': 1.2.1 react: 18.3.1 @@ -14936,7 +15012,7 @@ snapshots: rxjs@7.8.1: dependencies: - tslib: 2.8.0 + tslib: 2.8.1 safe-array-concat@1.1.2: dependencies: @@ -15334,7 +15410,7 @@ snapshots: synckit@0.9.1: dependencies: '@pkgr/core': 0.1.1 - tslib: 2.8.0 + tslib: 2.8.1 tapable@2.2.1: {} @@ -15438,7 +15514,7 @@ snapshots: '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.4 '@types/node': 22.7.5 - acorn: 8.13.0 + acorn: 8.14.0 acorn-walk: 8.3.1 arg: 4.1.3 create-require: 1.1.1 @@ -15457,7 +15533,7 @@ snapshots: '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.4 '@types/node': 22.9.0 - acorn: 8.13.0 + acorn: 8.14.0 acorn-walk: 8.3.1 arg: 4.1.3 create-require: 1.1.1 @@ -15482,36 +15558,34 @@ snapshots: tslib@2.7.0: {} - tslib@2.8.0: {} - tslib@2.8.1: {} - turbo-darwin-64@2.2.1: + turbo-darwin-64@2.2.3: optional: true - turbo-darwin-arm64@2.2.1: + turbo-darwin-arm64@2.2.3: optional: true - turbo-linux-64@2.2.1: + turbo-linux-64@2.2.3: optional: true - turbo-linux-arm64@2.2.1: + turbo-linux-arm64@2.2.3: optional: true - turbo-windows-64@2.2.1: + turbo-windows-64@2.2.3: optional: true - turbo-windows-arm64@2.2.1: + turbo-windows-arm64@2.2.3: optional: true - turbo@2.2.1: + turbo@2.2.3: optionalDependencies: - turbo-darwin-64: 2.2.1 - turbo-darwin-arm64: 2.2.1 - turbo-linux-64: 2.2.1 - turbo-linux-arm64: 2.2.1 - turbo-windows-64: 2.2.1 - turbo-windows-arm64: 2.2.1 + turbo-darwin-64: 2.2.3 + turbo-darwin-arm64: 2.2.3 + turbo-linux-64: 2.2.3 + turbo-linux-arm64: 2.2.3 + turbo-windows-64: 2.2.3 + turbo-windows-arm64: 2.2.3 type-check@0.4.0: dependencies: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 0d95524f0..67a930d59 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -4,3 +4,4 @@ packages: - "packages/config/*" - "packages/signer/*" - "packages/tools/*" + - "packages/transport/*" diff --git a/turbo.json b/turbo.json index 21d7731e7..74c909b76 100644 --- a/turbo.json +++ b/turbo.json @@ -37,7 +37,7 @@ "dependsOn": ["^build", "^typecheck"] }, "health-check": { - "dependsOn": ["build", "prettier", "lint", "typecheck"] + "dependsOn": ["^build", "^prettier", "^lint", "^typecheck"] } } }