Skip to content

Commit

Permalink
✨ (core) [DSDK-440]: Connect to Ledger Ble Device (#221)
Browse files Browse the repository at this point in the history
  • Loading branch information
jdabbech-ledger authored Oct 18, 2024
2 parents 760c1ba + dd61a4e commit d017587
Show file tree
Hide file tree
Showing 44 changed files with 1,944 additions and 214 deletions.
5 changes: 5 additions & 0 deletions .changeset/nasty-horses-wash.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@ledgerhq/device-sdk-core": patch
---

Add BLE support
6 changes: 4 additions & 2 deletions apps/sample/next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ const nextConfig = {
styledComponents: true,
},
env: {
MOCK_SERVER_DEFAULT_ENABLED:
process.env.npm_lifecycle_event === "dev:default-mock" ? "true" : "false",
SDK_CONFIG_TRANSPORT:
process.env.npm_lifecycle_event === "dev:default-mock"
? "MOCK_SERVER"
: "",
},
};

Expand Down
6 changes: 3 additions & 3 deletions apps/sample/src/app/client-layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { Header } from "@/components/Header";
import { Sidebar } from "@/components/Sidebar";
import { SdkProvider } from "@/providers/DeviceSdkProvider";
import { DeviceSessionsProvider } from "@/providers/DeviceSessionsProvider";
import { MockServerProvider } from "@/providers/MockServerProvider";
import { SdkConfigProvider } from "@/providers/SdkConfig";
import { GlobalStyle } from "@/styles/globalstyles";

const Root = styled(Flex)`
Expand All @@ -37,7 +37,7 @@ const PageContainer = styled(Flex)`
const ClientRootLayout: React.FC<PropsWithChildren> = ({ children }) => {
return (
<html lang="en">
<MockServerProvider>
<SdkConfigProvider>
<SdkProvider>
<StyleProvider selectedPalette="dark" fontsPath="/fonts">
<GlobalStyle />
Expand All @@ -54,7 +54,7 @@ const ClientRootLayout: React.FC<PropsWithChildren> = ({ children }) => {
</body>
</StyleProvider>
</SdkProvider>
</MockServerProvider>
</SdkConfigProvider>
</html>
);
};
Expand Down
7 changes: 7 additions & 0 deletions apps/sample/src/components/CommandsView/Command.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,13 @@ export function Command<
];
});
})
.catch((error) => {
setLoading(false);
setResponses((prev) => [
...prev.slice(0, -1),
{ args: values, date: new Date(), loading: false, response: error },
]);
})
.finally(() => {
setLoading(false);
});
Expand Down
23 changes: 9 additions & 14 deletions apps/sample/src/components/Device/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ const Root = styled(Flex).attrs({ p: 5, mb: 8, borderRadius: 2 })`
background: ${({ theme }: { theme: DefaultTheme }) =>
theme.colors.neutral.c30};
align-items: center;
border: ${({ active, theme }: { theme: DefaultTheme; active: boolean }) =>
`1px solid ${active ? theme.colors.success.c40 : "transparent"}`};
cursor: ${({ active }: { active: boolean }) =>
active ? "normal" : "pointer"};
`;

const IconContainer = styled(Flex).attrs({ p: 4, mr: 3, borderRadius: 100 })`
Expand All @@ -42,6 +46,7 @@ type DeviceProps = {
sessionId: DeviceSessionId;
model: DeviceModelId;
onDisconnect: () => Promise<void>;
onSelect: () => void;
};

function getIconComponent(model: DeviceModelId) {
Expand All @@ -62,16 +67,17 @@ export const Device: React.FC<DeviceProps> = ({
type,
model,
onDisconnect,
onSelect,
sessionId,
}) => {
const sessionState = useDeviceSessionState(sessionId);
const {
state: { deviceById, selectedId },
dispatch,
state: { selectedId },
} = useDeviceSessionsContext();
const IconComponent = getIconComponent(model);
const isActive = selectedId === sessionId;
return (
<Root>
<Root active={isActive} onClick={isActive ? undefined : onSelect}>
<IconContainer>
<IconComponent size="S" />
</IconContainer>
Expand All @@ -98,17 +104,6 @@ export const Device: React.FC<DeviceProps> = ({
</Box>
<div data-testid="dropdown_device-option">
<DropdownGeneric closeOnClickOutside label="" placement="bottom">
{Object.values(deviceById).length > 1 && selectedId !== sessionId && (
<ActionRow
onClick={() =>
dispatch({ type: "select_session", payload: { sessionId } })
}
>
<Text variant="paragraph" color="neutral.c80">
Select
</Text>
</ActionRow>
)}
<ActionRow data-testid="CTA_disconnect-device" onClick={onDisconnect}>
<Text variant="paragraph" color="neutral.c80">
Disconnect
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,6 @@ export const AllDeviceActions: React.FC<{ sessionId: string }> = ({
const deviceModelId = sdk.getConnectedDevice({
sessionId,
}).modelId;
console.log(
"sdk get connected device::",
sdk.getConnectedDevice({ sessionId }),
);

// eslint-disable-next-line @typescript-eslint/no-explicit-any
const deviceActions: DeviceActionProps<any, any, any, any>[] = useMemo(
Expand Down
29 changes: 20 additions & 9 deletions apps/sample/src/components/Header/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React, { useCallback, useState } from "react";
import { BuiltinTransports } from "@ledgerhq/device-management-kit";
import {
Button,
DropdownGeneric,
Expand All @@ -9,7 +10,7 @@ import {
} from "@ledgerhq/react-ui";
import styled, { DefaultTheme } from "styled-components";

import { useMockServerContext } from "@/providers/MockServerProvider";
import { useSdkConfigContext } from "@/providers/SdkConfig";

const Root = styled(Flex).attrs({ py: 3, px: 10, gridGap: 8 })`
color: ${({ theme }: { theme: DefaultTheme }) => theme.colors.neutral.c90};
Expand All @@ -36,18 +37,28 @@ const UrlInput = styled(Input)`
export const Header = () => {
const {
dispatch,
state: { enabled, url },
} = useMockServerContext();
state: { transport, mockServerUrl },
} = useSdkConfigContext();
const onToggleMockServer = useCallback(() => {
dispatch({ type: enabled ? "disable_mock_server" : "enable_mock_server" });
}, [dispatch, enabled]);
const [mockServerStateUrl, setMockServerStateUrl] = useState<string>(url);
dispatch({
type: "set_transport",
payload: {
transport:
transport === BuiltinTransports.MOCK_SERVER
? BuiltinTransports.USB
: BuiltinTransports.MOCK_SERVER,
},
});
}, [transport]);
const [mockServerStateUrl, setMockServerStateUrl] =
useState<string>(mockServerUrl);
const mockServerEnabled = transport === BuiltinTransports.MOCK_SERVER;

const validateServerUrl = useCallback(
() =>
dispatch({
type: "set_mock_server_url",
payload: { url: mockServerStateUrl },
payload: { mockServerUrl: mockServerStateUrl },
}),
[mockServerStateUrl],
);
Expand All @@ -67,13 +78,13 @@ export const Header = () => {
<div data-testid="switch_mock-server">
<Switch
onChange={onToggleMockServer}
checked={enabled}
checked={mockServerEnabled}
name="switch-mock-server"
label="Enable Mock server"
/>
</div>
</Flex>
{enabled && (
{mockServerEnabled && (
<UrlInput
value={mockServerStateUrl}
onChange={(url: string) => setMockServerStateUrl(url)}
Expand Down
92 changes: 92 additions & 0 deletions apps/sample/src/components/MainView/ConnectDeviceActions.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import React, { useCallback } from "react";
import { BuiltinTransports, SdkError } from "@ledgerhq/device-management-kit";
import { Button, Flex } from "@ledgerhq/react-ui";
import styled from "styled-components";

import { useSdk } from "@/providers/DeviceSdkProvider";
import { useDeviceSessionsContext } from "@/providers/DeviceSessionsProvider";
import { useSdkConfigContext } from "@/providers/SdkConfig";

type ConnectDeviceActionsProps = {
onError: (error: SdkError | null) => void;
};

const ConnectButton = styled(Button).attrs({ mx: 3 })``;

export const ConnectDeviceActions = ({
onError,
}: ConnectDeviceActionsProps) => {
const {
dispatch: dispatchSdkConfig,
state: { transport },
} = useSdkConfigContext();
const { dispatch: dispatchDeviceSession } = useDeviceSessionsContext();
const sdk = useSdk();

const onSelectDeviceClicked = useCallback(
(selectedTransport: BuiltinTransports) => {
onError(null);
dispatchSdkConfig({
type: "set_transport",
payload: { transport: selectedTransport },
});
sdk.startDiscovering({ transport: selectedTransport }).subscribe({
next: (device) => {
sdk
.connect({ device })
.then((sessionId) => {
console.log(
`🦖 Response from connect: ${JSON.stringify(sessionId)} 🎉`,
);
dispatchDeviceSession({
type: "add_session",
payload: {
sessionId,
connectedDevice: sdk.getConnectedDevice({ sessionId }),
},
});
})
.catch((error) => {
onError(error);
console.error(`Error from connection or get-version`, error);
});
},
error: (error) => {
console.error(error);
},
});
},
[sdk, transport],
);

return transport === BuiltinTransports.MOCK_SERVER ? (
<ConnectButton
onClick={() => onSelectDeviceClicked(BuiltinTransports.MOCK_SERVER)}
variant="main"
backgroundColor="main"
size="large"
data-testid="CTA_select-device"
>
Select a device
</ConnectButton>
) : (
<Flex>
<ConnectButton
onClick={() => onSelectDeviceClicked(BuiltinTransports.USB)}
variant="main"
backgroundColor="main"
size="large"
>
Select a USB device
</ConnectButton>
<ConnectButton
onClick={() => onSelectDeviceClicked(BuiltinTransports.BLE)}
variant="main"
backgroundColor="main"
size="large"
>
Select a BLE device
</ConnectButton>
</Flex>
);
};
Loading

0 comments on commit d017587

Please sign in to comment.