Skip to content

Commit

Permalink
💄 (smpl) [NO-ISSUE]: Display ApduResponse raw at the end of the form (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
jdabbech-ledger authored Apr 12, 2024
2 parents 6a7d979 + f74d77a commit e728a6c
Show file tree
Hide file tree
Showing 18 changed files with 170 additions and 49 deletions.
34 changes: 31 additions & 3 deletions apps/sample/src/components/ApduView/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React, { useCallback, useState } from "react";
import { ApduResponse } from "@ledgerhq/device-sdk-core";
import { Button, Divider, Flex, Grid, Input, Text } from "@ledgerhq/react-ui";
import styled, { DefaultTheme } from "styled-components";

Expand Down Expand Up @@ -56,21 +57,31 @@ const InputContainer = styled(Flex).attrs({ mx: 8, mb: 4 })`

const inputContainerProps = { style: { borderRadius: 4 } };

const ResultDescText = styled(Text).attrs({ variant: "body", mt: 4, px: 8 })`
min-width: 150px;
display: inline-block;
`;

export const ApduView: React.FC = () => {
const { apduFormValues, setApduFormValue, getRawApdu } = useApduForm();
const { apduFormValues, setApduFormValue, getRawApdu, getHexString } =
useApduForm();
const [loading, setLoading] = useState(false);
const [apduResponse, setApduResponse] = useState<ApduResponse>();
const sdk = useSdk();
const {
state: { selectedId: selectedSessionId },
} = useSessionContext();
const onSubmit = useCallback(
async (values: typeof apduFormValues) => {
setLoading(true);
let rawApduResponse;
try {
await sdk.sendApdu({
rawApduResponse = await sdk.sendApdu({
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
sessionId: selectedSessionId!,
apdu: getRawApdu(values),
});
setApduResponse(rawApduResponse);
setLoading(false);
} catch (error) {
setLoading(false);
Expand Down Expand Up @@ -161,7 +172,24 @@ export const ApduView: React.FC = () => {
</InputContainer>
</Grid>
</Form>
<Divider my={4} />
<Divider mt={4} />
{apduResponse && (
<>
<span>
<ResultDescText>Raw APDU:</ResultDescText>
<Text>{getHexString(getRawApdu(apduFormValues))}</Text>
</span>
<span>
<ResultDescText>Response status:</ResultDescText>
<Text>{getHexString(apduResponse.statusCode)}</Text>
</span>
<span>
<ResultDescText>Response raw data:</ResultDescText>
<Text>{getHexString(apduResponse.data)}</Text>
</span>
<Divider my={4} />
</>
)}
<FormFooter my={8}>
<FormFooterButton
onClick={() => onSubmit(apduFormValues)}
Expand Down
6 changes: 5 additions & 1 deletion apps/sample/src/components/Device/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,11 @@ export const Device: React.FC<DeviceProps> = ({
return (
<Root>
<IconContainer>
{model === "stax" ? <Icons.Stax size="S" /> : <Icons.Nano size="S" />}
{model === DeviceModelId.STAX ? (
<Icons.Stax size="S" />
) : (
<Icons.Nano size="S" />
)}
</IconContainer>
<Box flex={1}>
<Text variant="body">{name}</Text>
Expand Down
7 changes: 7 additions & 0 deletions apps/sample/src/hooks/useApduForm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,17 @@ export function useApduForm() {
[],
);

const getHexString = useCallback((raw: Uint8Array): string => {
return raw
.reduce((acc, curr) => acc + " " + curr.toString(16).padStart(2, "0"), "")
.toUpperCase();
}, []);

return {
apduFormValues: values,
setApduFormValue: setValue,
getRawApdu,
getHexString,
};
}

Expand Down
2 changes: 1 addition & 1 deletion apps/sample/src/providers/SessionsProvider/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ type SessionContextType = {
const SessionContext: Context<SessionContextType> =
createContext<SessionContextType>({
state: SessionsInitialState,
dispatch: (_value: AddSessionAction) => {},
dispatch: (_value: AddSessionAction) => null,
});

export const SessionProvider: React.FC<React.PropsWithChildren> = ({
Expand Down
43 changes: 37 additions & 6 deletions packages/core/src/api/DeviceSdk.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { LocalConfigDataSource } from "@internal/config/data/ConfigDataSource";
import { StubLocalConfigDataSource } from "@internal/config/data/LocalConfigDataSource.stub";
import { configTypes } from "@internal/config/di/configTypes";
import { discoveryTypes } from "@internal/discovery/di/discoveryTypes";
import { sendTypes } from "@internal/send/di/sendTypes";
import { usbDiTypes } from "@internal/usb/di/usbDiTypes";
import pkg from "@root/package.json";
import { StubUseCase } from "@root/src/di.stub";

import { ConsoleLogger } from "./logger-subscriber/service/ConsoleLogger";
import { DeviceSdk } from "./DeviceSdk";
Expand All @@ -26,12 +30,24 @@ describe("DeviceSdk", () => {
expect(await sdk.getVersion()).toBe(pkg.version);
});

it("startScan should ....", () => {
expect(sdk.startScan()).toBeFalsy();
it("should have startDiscovery method", () => {
expect(sdk.startDiscovering).toBeDefined();
});

it("stopScan should ....", () => {
expect(sdk.stopScan()).toBeFalsy();
it("should have stopDiscovery method", () => {
expect(sdk.stopDiscovering).toBeDefined();
});

it("should have connect method", () => {
expect(sdk.connect).toBeDefined();
});

it("should have sendApdu method", () => {
expect(sdk.sendApdu).toBeDefined();
});

it("should have getConnectedDevice method", () => {
expect(sdk.getConnectedDevice).toBeDefined();
});
});

Expand All @@ -40,19 +56,34 @@ describe("DeviceSdk", () => {
sdk = new DeviceSdk({ stub: true, loggers: [] });
});

it("should create a stubbed version", () => {
it("should create a stubbed sdk", () => {
expect(sdk).toBeDefined();
expect(sdk).toBeInstanceOf(DeviceSdk);
});

it("should return a stubbed config", () => {
expect(
sdk.container.get<LocalConfigDataSource>(
configTypes.LocalConfigDataSource,
),
).toBeInstanceOf(StubLocalConfigDataSource);
});

it("should return a stubbed `version`", async () => {
it("should return a stubbed version", async () => {
expect(await sdk.getVersion()).toBe("0.0.0-stub.1");
});

it.each([
[discoveryTypes.StartDiscoveringUseCase],
[discoveryTypes.StopDiscoveringUseCase],
[discoveryTypes.ConnectUseCase],
[sendTypes.SendApduUseCase],
[usbDiTypes.GetConnectedDeviceUseCase],
])("should have %p use case", (diSymbol) => {
const uc = sdk.container.get<StubUseCase>(diSymbol);
expect(uc).toBeInstanceOf(StubUseCase);
expect(uc.execute()).toBe("stub");
});
});

describe("without args", () => {
Expand Down
8 changes: 0 additions & 8 deletions packages/core/src/api/DeviceSdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,6 @@ export class DeviceSdk {
this.container = makeContainer({ stub, loggers });
}

startScan() {
return;
}

stopScan() {
return;
}

getVersion(): Promise<string> {
return this.container
.get<GetSdkVersionUseCase>(configTypes.GetSdkVersionUseCase)
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ export { LogLevel } from "./logger-subscriber/model/LogLevel";
export { ConsoleLogger } from "./logger-subscriber/service/ConsoleLogger";
export * from "./types";
export { ConnectedDevice } from "./usb/model/ConnectedDevice";
export { ApduResponse } from "@internal/device-session/model/ApduResponse";
2 changes: 1 addition & 1 deletion packages/core/src/api/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ export type { LogSubscriberOptions } from "./logger-subscriber/model/LogSubscrib
export type {
DeviceId,
DeviceModel,
DeviceModelId,
} from "@internal/device-model/model/DeviceModel";
export { DeviceModelId } from "@internal/device-model/model/DeviceModel";
export type { SessionId } from "@internal/device-session/model/Session";
export type { ConnectionType } from "@internal/discovery/model/ConnectionType";
export type { DiscoveredDevice } from "@internal/usb/model/DiscoveredDevice";
6 changes: 6 additions & 0 deletions packages/core/src/di.stub.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { injectable } from "inversify";

@injectable()
export class StubUseCase {
execute = jest.fn(() => "stub");
}
2 changes: 1 addition & 1 deletion packages/core/src/di.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export const makeContainer = ({
discoveryModuleFactory({ stub }),
loggerModuleFactory({ subscribers: loggers }),
deviceSessionModuleFactory(),
sendModuleFactory(),
sendModuleFactory({ stub }),
// modules go here
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ export type ApduResponseConstructorArgs = {
};

export class ApduResponse {
protected _statusCode: Uint8Array;
protected _data: Uint8Array;
public statusCode: Uint8Array;
public data: Uint8Array;

constructor({ statusCode, data }: ApduResponseConstructorArgs) {
this._statusCode = statusCode;
this._data = data;
this.statusCode = statusCode;
this.data = data;
}
}
2 changes: 1 addition & 1 deletion packages/core/src/internal/device-session/model/Session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { v4 as uuidv4 } from "uuid";

import { InternalConnectedDevice } from "@internal/usb/model/InternalConnectedDevice";

export type SessionId = ReturnType<typeof uuidv4>;
export type SessionId = string;

export type SessionConstructorArgs = {
connectedDevice: InternalConnectedDevice;
Expand Down
8 changes: 5 additions & 3 deletions packages/core/src/internal/discovery/di/discoveryModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { ContainerModule } from "inversify";
import { ConnectUseCase } from "@internal/discovery/use-case/ConnectUseCase";
import { StartDiscoveringUseCase } from "@internal/discovery/use-case/StartDiscoveringUseCase";
import { StopDiscoveringUseCase } from "@internal/discovery/use-case/StopDiscoveringUseCase";
import { StubUseCase } from "@root/src/di.stub";

import { discoveryTypes } from "./discoveryTypes";

Expand All @@ -13,13 +14,14 @@ type FactoryProps = {
export const discoveryModuleFactory = ({
stub = false,
}: Partial<FactoryProps> = {}) =>
new ContainerModule((bind, _unbind, _isBound, _rebind) => {
new ContainerModule((bind, _unbind, _isBound, rebind) => {
bind(discoveryTypes.StartDiscoveringUseCase).to(StartDiscoveringUseCase);
bind(discoveryTypes.StopDiscoveringUseCase).to(StopDiscoveringUseCase);
bind(discoveryTypes.ConnectUseCase).to(ConnectUseCase);

if (stub) {
// We can rebind our interfaces to their mock implementations
// rebind(...).to(....);
rebind(discoveryTypes.StartDiscoveringUseCase).to(StubUseCase);
rebind(discoveryTypes.StopDiscoveringUseCase).to(StubUseCase);
rebind(discoveryTypes.ConnectUseCase).to(StubUseCase);
}
});
10 changes: 8 additions & 2 deletions packages/core/src/internal/send/di/sendModule.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,30 @@
import { ContainerModule } from "inversify";

import { SendApduUseCase } from "@internal/send/use-case/SendApduUseCase";
import { StubUseCase } from "@root/src/di.stub";

import { sendTypes } from "./sendTypes";

type FactoryProps = {
stub: boolean;
};

export const sendModuleFactory = (_args: Partial<FactoryProps> = {}) =>
export const sendModuleFactory = ({
stub = false,
}: Partial<FactoryProps> = {}) =>
new ContainerModule(
(
bind,
_unbind,
_isBound,
_rebind,
rebind,
_unbindAsync,
_onActivation,
_onDeactivation,
) => {
bind(sendTypes.SendApduUseCase).to(SendApduUseCase);
if (stub) {
rebind(sendTypes.SendApduUseCase).to(StubUseCase);
}
},
);
17 changes: 11 additions & 6 deletions packages/core/src/internal/send/use-case/SendApduUseCase.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { DeviceSessionNotFound } from "@internal/device-session/model/Errors";
import { Left } from "purify-ts";

import {
DeviceSessionNotFound,
ReceiverApduError,
} from "@internal/device-session/model/Errors";
import { sessionStubBuilder } from "@internal/device-session/model/Session.stub";
import { DefaultSessionService } from "@internal/device-session/service/DefaultSessionService";
import { SessionService } from "@internal/device-session/service/SessionService";
Expand Down Expand Up @@ -48,12 +53,12 @@ describe("SendApduUseCase", () => {
await expect(response).rejects.toBeInstanceOf(DeviceSessionNotFound);
});

it("should throw an error if the apdu receiver failed", () => {
it("should throw an error if the apdu receiver failed", async () => {
// given
const connectedDevice = connectedDeviceStubBuilder({
sendApdu: jest.fn(async () => {
throw new Error("apdu receiver failed");
}),
sendApdu: jest.fn(async () =>
Promise.resolve(Left(new ReceiverApduError())),
),
});
const session = sessionStubBuilder({ connectedDevice });
sessionService.addSession(session);
Expand All @@ -66,6 +71,6 @@ describe("SendApduUseCase", () => {
});

// then
expect(response).rejects.toThrow("apdu receiver failed");
await expect(response).rejects.toBeInstanceOf(ReceiverApduError);
});
});
6 changes: 3 additions & 3 deletions packages/core/src/internal/usb/di/usbModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { ContainerModule } from "inversify";
import { UsbHidDeviceConnectionFactory } from "@internal/usb/service/UsbHidDeviceConnectionFactory";
import { WebUsbHidTransport } from "@internal/usb/transport/WebUsbHidTransport";
import { GetConnectedDeviceUseCase } from "@internal/usb/use-case/GetConnectedDeviceUseCase";
import { StubUseCase } from "@root/src/di.stub";

import { usbDiTypes } from "./usbDiTypes";

Expand All @@ -13,7 +14,7 @@ type FactoryProps = {
export const usbModuleFactory = ({
stub = false,
}: Partial<FactoryProps> = {}) =>
new ContainerModule((bind, _unbind, _isBound, _rebind) => {
new ContainerModule((bind, _unbind, _isBound, rebind) => {
// The transport needs to be a singleton to keep the internal states of the devices
bind(usbDiTypes.UsbHidTransport).to(WebUsbHidTransport).inSingletonScope();

Expand All @@ -26,7 +27,6 @@ export const usbModuleFactory = ({
bind(usbDiTypes.GetConnectedDeviceUseCase).to(GetConnectedDeviceUseCase);

if (stub) {
// We can rebind our interfaces to their mock implementations
// rebind(...).to(....);
rebind(usbDiTypes.GetConnectedDeviceUseCase).to(StubUseCase);
}
});
Loading

0 comments on commit e728a6c

Please sign in to comment.