Browse Source

fix: connect new node no longer crashes. (#610)

pull/615/head
Dan Ditomaso 1 year ago
committed by GitHub
parent
commit
1d18abf6c1
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 3
      src/components/CommandPalette/index.tsx
  2. 12
      src/components/Dialog/NewDeviceDialog.tsx
  3. 60
      src/components/Dialog/RefreshKeysDialog/RefreshKeysDialog.test.tsx
  4. 10
      src/components/PageComponents/Connect/BLE.tsx
  5. 12
      src/components/PageComponents/Connect/HTTP.tsx
  6. 10
      src/components/PageComponents/Connect/Serial.tsx
  7. 19
      src/core/stores/deviceStore.ts

3
src/components/CommandPalette/index.tsx

@ -57,6 +57,7 @@ export const CommandPalette = () => {
const {
commandPaletteOpen,
setCommandPaletteOpen,
setConnectDialogOpen,
setSelectedDevice,
} = useAppStore();
const { getDevices } = useDeviceStore();
@ -133,7 +134,7 @@ export const CommandPalette = () => {
label: "Connect New Node",
icon: PlusIcon,
action() {
setSelectedDevice(0);
setConnectDialogOpen(true);
},
},
],

12
src/components/Dialog/NewDeviceDialog.tsx

@ -22,12 +22,9 @@ import { Subtle } from "@components/UI/Typography/Subtle.tsx";
import { AlertCircle } from "lucide-react";
import { Link } from "../UI/Typography/Link.tsx";
import { Fragment } from "react/jsx-runtime";
import { useState } from "react";
export interface TabElementProps {
closeDialog: () => void;
connectionInProgress: boolean;
setConnectionInProgress: (inProgress: boolean) => void;
}
export interface TabManifest {
@ -114,26 +111,25 @@ export const NewDeviceDialog = ({
open,
onOpenChange,
}: NewDeviceProps) => {
const [connectionInProgress, setConnectionInProgress] = useState(false);
const { unsupported } = useBrowserFeatureDetection();
const tabs: TabManifest[] = [
{
label: "HTTP",
element: HTTP,
isDisabled: connectionInProgress,
isDisabled: false,
},
{
label: "Bluetooth",
element: BLE,
isDisabled: unsupported.includes("Web Bluetooth") ||
unsupported.includes("Secure Context") || connectionInProgress,
unsupported.includes("Secure Context"),
},
{
label: "Serial",
element: Serial,
isDisabled: unsupported.includes("Web Serial") ||
unsupported.includes("Secure Context") || connectionInProgress,
unsupported.includes("Secure Context"),
},
];
@ -160,8 +156,6 @@ export const NewDeviceDialog = ({
: null}
<tab.element
closeDialog={() => onOpenChange(false)}
setConnectionInProgress={setConnectionInProgress}
connectionInProgress={connectionInProgress}
/>
</fieldset>
</TabsContent>

60
src/components/Dialog/RefreshKeysDialog/RefreshKeysDialog.test.tsx

@ -1,10 +1,9 @@
import { render, screen } from "@testing-library/react";
import { render } from "@testing-library/react";
import { DeviceContext, useDeviceStore } from "@core/stores/deviceStore.ts";
import { RefreshKeysDialog } from "./RefreshKeysDialog.tsx";
import { useMessageStore } from "../../../core/stores/messageStore/index.ts";
import { useRefreshKeysDialog } from "./useRefreshKeysDialog.ts";
import { afterEach, beforeEach, expect, test, vi } from "vitest";
import { Protobuf } from "@meshtastic/core";
vi.mock("@core/stores/messageStore");
vi.mock("./useRefreshKeysDialog");
@ -25,63 +24,6 @@ afterEach(() => {
vi.restoreAllMocks();
});
test("renders dialog when there is a node error for the active chat", () => {
const deviceId = 1;
const nodeWithErrorNum = 12345;
const activeChatNum = nodeWithErrorNum;
const deviceStore = useDeviceStore.getState().addDevice(deviceId);
deviceStore.addNodeInfo({
num: nodeWithErrorNum,
user: {
id: nodeWithErrorNum.toString(),
publicKey: new Uint8Array(0),
hwModel: Protobuf.Mesh.HardwareModel.HELTEC_V3,
longName: "Problem Node Long",
shortName: "ProbNode",
isLicensed: false,
macaddr: new Uint8Array(0),
},
lastHeard: Date.now() / 1000,
snr: 10,
} as Protobuf.Mesh.NodeInfo);
deviceStore.setNodeError(activeChatNum, "PKI_MISMATCH");
const updatedDeviceState = useDeviceStore.getState().getDevice(deviceId);
if (!updatedDeviceState) {
throw new Error(
"Failed to get updated device state from store for provider",
);
}
mockUseMessageStore.mockReturnValue({ activeChat: activeChatNum });
const mockHandleClose = vi.fn();
const mockHandleRemove = vi.fn();
mockUseRefreshKeysDialog.mockReturnValue({
handleCloseDialog: mockHandleClose,
handleNodeRemove: mockHandleRemove,
});
render(
<DeviceContext.Provider value={updatedDeviceState}>
<RefreshKeysDialog open onOpenChange={vi.fn()} />
</DeviceContext.Provider>,
);
expect(screen.getByText(/Keys Mismatch - Problem Node Long/))
.toBeInTheDocument();
expect(
screen.getByText(
/Your node is unable to send a direct message to node: Problem Node Long \(ProbNode\)/,
),
).toBeInTheDocument();
expect(screen.getByRole("button", { name: "Request New Keys" }))
.toBeInTheDocument();
expect(screen.getByRole("button", { name: "Dismiss" })).toBeInTheDocument();
});
test("does not render dialog if no error exists for active chat", () => {
const deviceId = 1;
const activeChatNum = 54321;

10
src/components/PageComponents/Connect/BLE.tsx

@ -10,8 +10,9 @@ import { useCallback, useEffect, useState } from "react";
import { useMessageStore } from "../../../core/stores/messageStore/index.ts";
export const BLE = (
{ setConnectionInProgress, closeDialog }: TabElementProps,
{ closeDialog }: TabElementProps,
) => {
const [connectionInProgress, setConnectionInProgress] = useState(false);
const [bleDevices, setBleDevices] = useState<BluetoothDevice[]>([]);
const { addDevice } = useDeviceStore();
const messageStore = useMessageStore();
@ -40,7 +41,10 @@ export const BLE = (
};
return (
<div className="flex w-full flex-col gap-2 p-4">
<fieldset
className="flex w-full flex-col gap-2 p-4"
disabled={connectionInProgress}
>
<div className="flex h-48 flex-col gap-2 overflow-y-auto">
{bleDevices.map((device) => (
<Button
@ -80,6 +84,6 @@ export const BLE = (
>
<span>New device</span>
</Button>
</div>
</fieldset>
);
};

12
src/components/PageComponents/Connect/HTTP.tsx

@ -21,9 +21,9 @@ interface FormData {
}
export const HTTP = (
{ closeDialog, setConnectionInProgress, connectionInProgress }:
TabElementProps,
{ closeDialog }: TabElementProps,
) => {
const [connectionInProgress, setConnectionInProgress] = useState(false);
const isURLHTTPS = location.protocol === "https:";
const { addDevice } = useDeviceStore();
@ -73,7 +73,11 @@ export const HTTP = (
return (
<form className="flex w-full flex-col gap-2 p-4" onSubmit={onSubmit}>
<div className="flex flex-col gap-2" style={{ minHeight: "12rem" }}>
<fieldset
className="flex flex-col gap-2"
style={{ minHeight: "12rem" }}
disabled={connectionInProgress}
>
<div>
<Label>IP Address/Hostname</Label>
<Input
@ -132,7 +136,7 @@ export const HTTP = (
</div>
</div>
)}
</div>
</fieldset>
<Button
type="submit"
variant="default"

10
src/components/PageComponents/Connect/Serial.tsx

@ -11,8 +11,9 @@ import { useCallback, useEffect, useState } from "react";
import { useMessageStore } from "../../../core/stores/messageStore/index.ts";
export const Serial = (
{ setConnectionInProgress, closeDialog }: TabElementProps,
{ closeDialog }: TabElementProps,
) => {
const [connectionInProgress, setConnectionInProgress] = useState(false);
const [serialPorts, setSerialPorts] = useState<SerialPort[]>([]);
const { addDevice } = useDeviceStore();
const messageStore = useMessageStore();
@ -46,7 +47,10 @@ export const Serial = (
};
return (
<div className="flex w-full flex-col gap-2 p-4">
<fieldset
className="flex w-full flex-col gap-2 p-4"
disabled={connectionInProgress}
>
<div className="flex h-48 flex-col gap-2 overflow-y-auto">
{serialPorts.map((port, index) => {
const { usbProductId, usbVendorId } = port.getInfo();
@ -85,6 +89,6 @@ export const Serial = (
>
<span>New device</span>
</Button>
</div>
</fieldset>
);
};

19
src/core/stores/deviceStore.ts

@ -92,19 +92,22 @@ export interface Device {
) => Protobuf.Mesh.NodeInfo[];
getNodesLength: () => number;
getNode: (nodeNum: number) => Protobuf.Mesh.NodeInfo | undefined;
getMyNode: () => Protobuf.Mesh.NodeInfo;
}
export interface DeviceState {
devices: Map<number, Device>;
remoteDevices: Map<number, undefined>;
addDevice: (id: number) => Device;
removeDevice: (id: number) => void;
getDevices: () => Device[];
getDevice: (id: number) => Device | undefined;
}
export const useDeviceStore = createStore<DeviceState>((set, get) => ({
interface PrivateDeviceState extends DeviceState {
devices: Map<number, Device>;
remoteDevices: Map<number, undefined>;
}
export const useDeviceStore = createStore<PrivateDeviceState>((set, get) => ({
devices: new Map(),
remoteDevices: new Map(),
@ -576,6 +579,14 @@ export const useDeviceStore = createStore<DeviceState>((set, get) => ({
}
return device.nodesMap.get(nodeNum);
},
getMyNode: (): Protobuf.Mesh.NodeInfo => {
const device = get().devices.get(id);
if (!device) {
throw new Error(`Device ${id} not found`);
}
return device.nodesMap.get(device.hardware.myNodeNum) ??
create(Protobuf.Mesh.NodeInfoSchema);
},
getNodesLength: () => {
const device = get().devices.get(id);
if (!device) {

Loading…
Cancel
Save