mirror of
https://github.com/meshtastic/web.git
synced 2026-07-31 06:56:28 -04:00
feat(sdk,web): SDK NodesClient owns user/position/lastHeard/snr +
favourite-flag flips; legacy mirror retired
The SDK NodesClient now subscribes to onUserPacket / onPositionPacket /
onMeshPacket so user-record updates, GPS updates, and lastHeard / snr
refreshes flow into the signal-backed store + repository directly. The
favourite / ignored toggles also flip the local flag once the admin
message resolves successfully, so the UI no longer needs to mirror the
state into a parallel Zustand store.
packages/sdk
- NodesClient: new private patch(num, partial) helper that shallow-
merges into the existing entry (or seeds a placeholder Node) and
upserts to the repository in one shot. Used by:
- onUserPacket → patch user
- onPositionPacket → patch position
- onMeshPacket → patch lastHeard / snr (replaces the legacy nodeDB
processPacket flow)
- favorite / unfavorite / ignore / unignore — flag flip on Result.ok
- NodesClient.reset({ keepMyNode? }) — preserves the local node entry
when requested. Mirrors the previous removeAllNodes(true) semantics
the ResetNodeDb dialog relied on.
packages/web
- core/subscriptions.ts: drops the onUserPacket / onPositionPacket /
onNodeInfoPacket / onMeshPacket → legacy nodeDB write paths. The
unused nodeDB parameter is renamed `_nodeDB` for callsite stability;
it will disappear when the store itself is deleted in a follow-up.
- core/hooks/useFavoriteNode + useIgnoreNode: drop the legacy
updateFavorite / updateIgnore mirror — the SDK flips the flag on
success.
- components/Dialog/RemoveNodeDialog: now calls meshClient.nodes.remove
exclusively; no legacy fall-through.
- components/Dialog/ResetNodeDbDialog: calls
meshClient.nodes.reset({ keepMyNode: true }) and meshClient.chat.clearAll().
No legacy nodeDB calls.
- Test mocks for useFavoriteNode / useIgnoreNode / ResetNodeDbDialog
pruned to match.
Test counts: sdk 44 (unchanged), sdk-react 8, sdk-storage-sqlocal 24,
web 290. Production Vite build clean.
Remaining surface on the legacy nodeDB: addNode / addUser / addPosition /
processPacket / setNodeError-equivalent are now unused; the store retains
only updateFavorite / updateIgnore (no callers) and the per-device
plumbing required by the deviceContext hooks. Full deletion of
nodeDBStore queued in the plan-file follow-up.
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import { create } from "@bufbuild/protobuf";
|
||||
import * as Protobuf from "@meshtastic/protobufs";
|
||||
import type { ResultType } from "better-result";
|
||||
import type { MeshClient } from "../../core/client/MeshClient.ts";
|
||||
@@ -41,16 +42,31 @@ export class NodesClient {
|
||||
this.list = this.store.read;
|
||||
this.errors = this.errorsStore.read;
|
||||
|
||||
client.events.onNodeInfoPacket.subscribe((info) => {
|
||||
this.handleIncoming(info);
|
||||
client.events.onNodeInfoPacket.subscribe((info) => this.handleIncoming(info));
|
||||
|
||||
client.events.onUserPacket.subscribe((packet) => {
|
||||
this.patch(packet.from, { user: packet.data });
|
||||
});
|
||||
|
||||
client.events.onPositionPacket.subscribe((packet) => {
|
||||
this.patch(packet.from, { position: packet.data });
|
||||
});
|
||||
|
||||
client.events.onMeshPacket.subscribe((packet) => {
|
||||
// Every inbound mesh packet refreshes lastHeard / snr for the
|
||||
// originating node — same semantics as the legacy nodeDB
|
||||
// processPacket but routed through the SDK signal layer.
|
||||
const nowSec = Math.floor(Date.now() / 1000);
|
||||
this.patch(packet.from, {
|
||||
lastHeard: packet.rxTime > 0 ? packet.rxTime : nowSec,
|
||||
snr: packet.rxSnr,
|
||||
});
|
||||
});
|
||||
|
||||
client.events.onRoutingPacket.subscribe((packet) => {
|
||||
if (packet.data.variant.case !== "errorReason") return;
|
||||
const reason = packet.data.variant.value;
|
||||
if (reason === Protobuf.Mesh.Routing_Error.NONE) return;
|
||||
// Reasons that indicate a node-level fault. Anything else is just a
|
||||
// transient packet failure and not worth surfacing per-node.
|
||||
if (
|
||||
reason === Protobuf.Mesh.Routing_Error.PKI_UNKNOWN_PUBKEY ||
|
||||
reason === Protobuf.Mesh.Routing_Error.PKI_FAILED ||
|
||||
@@ -76,11 +92,31 @@ export class NodesClient {
|
||||
|
||||
const node = NodeMapper.fromProto(verdict.accepted);
|
||||
this.store.set(node.num, node);
|
||||
// A successful update implicitly clears any prior PKI/no-channel error.
|
||||
if (this.errorsStore.has(node.num)) this.errorsStore.delete(node.num);
|
||||
void this.repository.upsert(node).catch(() => {});
|
||||
}
|
||||
|
||||
/**
|
||||
* Shallow-merges a partial Node update over the existing entry. Creates a
|
||||
* stub Node if none exists yet — matches the legacy nodeDB behaviour
|
||||
* where a stray UserPacket / PositionPacket / mesh packet would seed a
|
||||
* placeholder NodeInfo.
|
||||
*/
|
||||
private patch(nodeNum: number, partial: Partial<Node>): void {
|
||||
if (nodeNum === 0) return;
|
||||
const existing = this.store.get(nodeNum);
|
||||
const next: Node = existing
|
||||
? { ...existing, ...partial }
|
||||
: {
|
||||
num: nodeNum,
|
||||
isFavorite: false,
|
||||
isIgnored: false,
|
||||
...partial,
|
||||
};
|
||||
this.store.set(nodeNum, next);
|
||||
void this.repository.upsert(next).catch(() => {});
|
||||
}
|
||||
|
||||
private async hydrate(): Promise<void> {
|
||||
if (this.hydrated) return;
|
||||
this.hydrated = true;
|
||||
@@ -118,20 +154,28 @@ export class NodesClient {
|
||||
this.errorsStore.clear();
|
||||
}
|
||||
|
||||
public favorite(nodeNum: number): Promise<ResultType<number, Error>> {
|
||||
return favoriteNode(this.client, nodeNum);
|
||||
public async favorite(nodeNum: number): Promise<ResultType<number, Error>> {
|
||||
const result = await favoriteNode(this.client, nodeNum);
|
||||
if (result.status === "ok") this.patch(nodeNum, { isFavorite: true });
|
||||
return result;
|
||||
}
|
||||
|
||||
public unfavorite(nodeNum: number): Promise<ResultType<number, Error>> {
|
||||
return removeFavoriteNode(this.client, nodeNum);
|
||||
public async unfavorite(nodeNum: number): Promise<ResultType<number, Error>> {
|
||||
const result = await removeFavoriteNode(this.client, nodeNum);
|
||||
if (result.status === "ok") this.patch(nodeNum, { isFavorite: false });
|
||||
return result;
|
||||
}
|
||||
|
||||
public ignore(nodeNum: number): Promise<ResultType<number, Error>> {
|
||||
return ignoreNode(this.client, nodeNum);
|
||||
public async ignore(nodeNum: number): Promise<ResultType<number, Error>> {
|
||||
const result = await ignoreNode(this.client, nodeNum);
|
||||
if (result.status === "ok") this.patch(nodeNum, { isIgnored: true });
|
||||
return result;
|
||||
}
|
||||
|
||||
public unignore(nodeNum: number): Promise<ResultType<number, Error>> {
|
||||
return removeIgnoredNode(this.client, nodeNum);
|
||||
public async unignore(nodeNum: number): Promise<ResultType<number, Error>> {
|
||||
const result = await removeIgnoredNode(this.client, nodeNum);
|
||||
if (result.status === "ok") this.patch(nodeNum, { isIgnored: false });
|
||||
return result;
|
||||
}
|
||||
|
||||
public remove(nodeNum: number): Promise<ResultType<number, Error>> {
|
||||
@@ -141,10 +185,40 @@ export class NodesClient {
|
||||
return removeNodeByNum(this.client, nodeNum);
|
||||
}
|
||||
|
||||
public reset(): Promise<ResultType<number, Error>> {
|
||||
void this.repository.clear().catch(() => {});
|
||||
this.store.clear();
|
||||
this.errorsStore.clear();
|
||||
/**
|
||||
* Wipes every node except (optionally) the local "my node" entry, then
|
||||
* sends the device-side reset admin message. Mirrors the legacy
|
||||
* removeAllNodes(true) + resetNodes flow that the ResetNodeDb dialog
|
||||
* relied on.
|
||||
*/
|
||||
public async reset(options: { keepMyNode?: boolean } = {}): Promise<ResultType<number, Error>> {
|
||||
const myNodeNum = this.client.device.myNodeNum.value;
|
||||
if (options.keepMyNode && myNodeNum !== undefined) {
|
||||
const me = this.store.get(myNodeNum);
|
||||
this.store.clear();
|
||||
this.errorsStore.clear();
|
||||
if (me) this.store.set(myNodeNum, me);
|
||||
try {
|
||||
await this.repository.clear();
|
||||
if (me) await this.repository.upsert(me);
|
||||
} catch {
|
||||
// ok
|
||||
}
|
||||
} else {
|
||||
this.store.clear();
|
||||
this.errorsStore.clear();
|
||||
try {
|
||||
await this.repository.clear();
|
||||
} catch {
|
||||
// ok
|
||||
}
|
||||
}
|
||||
return resetNodes(this.client);
|
||||
}
|
||||
|
||||
/** Drives the legacy `Protobuf.Mesh.NodeInfoSchema` create flow if a
|
||||
* consumer needs a placeholder for a not-yet-known node. */
|
||||
public createPlaceholder(nodeNum: number): Protobuf.Mesh.NodeInfo {
|
||||
return create(Protobuf.Mesh.NodeInfoSchema, { num: nodeNum });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Label } from "@components/UI/Label.tsx";
|
||||
import { useNodeAsProto } from "@core/hooks/useNodesAsProto.ts";
|
||||
import { useAppStore, useDevice, useNodeDB } from "@core/stores";
|
||||
import { useAppStore } from "@core/stores";
|
||||
import { useActiveClient } from "@meshtastic/sdk-react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { DialogWrapper } from "./DialogWrapper.tsx";
|
||||
|
||||
@@ -11,14 +12,12 @@ export interface RemoveNodeDialogProps {
|
||||
|
||||
export const RemoveNodeDialog = ({ open, onOpenChange }: RemoveNodeDialogProps) => {
|
||||
const { t } = useTranslation("dialog");
|
||||
const { connection } = useDevice();
|
||||
const { removeNode } = useNodeDB();
|
||||
const meshClient = useActiveClient();
|
||||
const { nodeNumToBeRemoved } = useAppStore();
|
||||
const node = useNodeAsProto(nodeNumToBeRemoved);
|
||||
|
||||
const handleConfirm = () => {
|
||||
connection?.removeNodeByNum(nodeNumToBeRemoved);
|
||||
removeNode(nodeNumToBeRemoved);
|
||||
void meshClient?.nodes.remove(nodeNumToBeRemoved);
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -4,8 +4,6 @@ import { ResetNodeDbDialog } from "./ResetNodeDbDialog.tsx";
|
||||
|
||||
const mockResetNodes = vi.fn();
|
||||
const mockClearAll = vi.fn();
|
||||
const mockClearAllErrors = vi.fn();
|
||||
const mockRemoveAllNodes = vi.fn();
|
||||
|
||||
const { mockUseActiveClient } = vi.hoisted(() => ({
|
||||
mockUseActiveClient: vi.fn(),
|
||||
@@ -15,27 +13,18 @@ vi.mock("@meshtastic/sdk-react", () => ({
|
||||
useActiveClient: mockUseActiveClient,
|
||||
}));
|
||||
|
||||
vi.mock("@core/stores", () => ({
|
||||
CurrentDeviceContext: {
|
||||
_currentValue: { deviceId: 1234 },
|
||||
},
|
||||
useNodeDB: () => ({
|
||||
removeAllNodes: mockRemoveAllNodes,
|
||||
}),
|
||||
}));
|
||||
|
||||
describe("ResetNodeDbDialog", () => {
|
||||
const mockOnOpenChange = vi.fn();
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
mockUseActiveClient.mockReturnValue({
|
||||
nodes: { reset: mockResetNodes, clearAllErrors: mockClearAllErrors },
|
||||
nodes: { reset: mockResetNodes },
|
||||
chat: { clearAll: mockClearAll },
|
||||
});
|
||||
});
|
||||
|
||||
it("calls reset(), clears SDK errors + chat + legacy nodes after resolve", async () => {
|
||||
it("calls reset({ keepMyNode: true }) then clears chat after resolve", async () => {
|
||||
let resolveReset: ((value: { status: "ok"; value: number }) => void) | undefined;
|
||||
mockResetNodes.mockImplementation(
|
||||
() =>
|
||||
@@ -48,22 +37,18 @@ describe("ResetNodeDbDialog", () => {
|
||||
render(<ResetNodeDbDialog open onOpenChange={mockOnOpenChange} />);
|
||||
fireEvent.click(screen.getByRole("button", { name: "Reset Node Database" }));
|
||||
|
||||
expect(mockResetNodes).toHaveBeenCalledTimes(1);
|
||||
expect(mockResetNodes).toHaveBeenCalledWith({ keepMyNode: true });
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockOnOpenChange).toHaveBeenCalledWith(false);
|
||||
});
|
||||
|
||||
expect(mockClearAll).not.toHaveBeenCalled();
|
||||
expect(mockClearAllErrors).not.toHaveBeenCalled();
|
||||
expect(mockRemoveAllNodes).not.toHaveBeenCalled();
|
||||
|
||||
resolveReset?.({ status: "ok", value: 1 });
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockClearAllErrors).toHaveBeenCalledTimes(1);
|
||||
expect(mockClearAll).toHaveBeenCalledTimes(1);
|
||||
expect(mockRemoveAllNodes).toHaveBeenCalledWith(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -77,7 +62,5 @@ describe("ResetNodeDbDialog", () => {
|
||||
|
||||
expect(mockResetNodes).not.toHaveBeenCalled();
|
||||
expect(mockClearAll).not.toHaveBeenCalled();
|
||||
expect(mockClearAllErrors).not.toHaveBeenCalled();
|
||||
expect(mockRemoveAllNodes).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { toast } from "@core/hooks/useToast.ts";
|
||||
import { useNodeDB } from "@core/stores";
|
||||
import { useActiveClient } from "@meshtastic/sdk-react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { DialogWrapper } from "../DialogWrapper.tsx";
|
||||
@@ -12,22 +11,15 @@ export interface ResetNodeDbDialogProps {
|
||||
export const ResetNodeDbDialog = ({ open, onOpenChange }: ResetNodeDbDialogProps) => {
|
||||
const { t } = useTranslation("dialog");
|
||||
const meshClient = useActiveClient();
|
||||
// The legacy nodeDB still holds an in-memory snapshot for unmigrated
|
||||
// consumers; sweep it alongside the SDK clear.
|
||||
const { removeAllNodes } = useNodeDB();
|
||||
|
||||
const handleResetNodeDb = () => {
|
||||
if (!meshClient) return;
|
||||
meshClient.nodes
|
||||
.reset()
|
||||
.reset({ keepMyNode: true })
|
||||
.then((result) => {
|
||||
if (result.status === "error") throw result.error;
|
||||
meshClient.nodes.clearAllErrors();
|
||||
return meshClient.chat.clearAll();
|
||||
})
|
||||
.then(() => {
|
||||
removeAllNodes(true);
|
||||
})
|
||||
.catch((error) => {
|
||||
toast({ title: t("resetNodeDb.failedTitle") });
|
||||
console.error("Failed to reset Node DB:", error);
|
||||
|
||||
@@ -2,7 +2,6 @@ import { act, renderHook } from "@testing-library/react";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { useFavoriteNode } from "./useFavoriteNode.ts";
|
||||
|
||||
const mockUpdateFavorite = vi.fn();
|
||||
const mockToast = vi.fn();
|
||||
const mockSdkFavorite = vi.fn();
|
||||
const mockSdkUnfavorite = vi.fn();
|
||||
@@ -15,15 +14,6 @@ vi.mock("@meshtastic/sdk-react", () => ({
|
||||
useActiveClient: mockUseActiveClient,
|
||||
}));
|
||||
|
||||
vi.mock("@core/stores", () => ({
|
||||
CurrentDeviceContext: {
|
||||
_currentValue: { deviceId: 1234 },
|
||||
},
|
||||
useNodeDB: () => ({
|
||||
updateFavorite: mockUpdateFavorite,
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock("@core/hooks/useToast.ts", () => ({
|
||||
useToast: () => ({
|
||||
toast: mockToast,
|
||||
@@ -47,7 +37,7 @@ describe("useFavoriteNode hook", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("calls updateFavorite and shows correct toast", () => {
|
||||
it("calls SDK favorite and shows correct toast", () => {
|
||||
const { result } = renderHook(() => useFavoriteNode());
|
||||
|
||||
act(() => {
|
||||
@@ -55,7 +45,6 @@ describe("useFavoriteNode hook", () => {
|
||||
});
|
||||
|
||||
expect(mockSdkFavorite).toHaveBeenCalledWith(1234);
|
||||
expect(mockUpdateFavorite).toHaveBeenCalledWith(1234, true);
|
||||
expect(mockToast).toHaveBeenCalledWith({
|
||||
title: "Added Test Node to favorites.",
|
||||
});
|
||||
@@ -69,7 +58,6 @@ describe("useFavoriteNode hook", () => {
|
||||
});
|
||||
|
||||
expect(mockSdkUnfavorite).toHaveBeenCalledWith(1234);
|
||||
expect(mockUpdateFavorite).toHaveBeenCalledWith(1234, false);
|
||||
expect(mockToast).toHaveBeenCalledWith({
|
||||
title: "Removed Test Node from favorites.",
|
||||
});
|
||||
@@ -98,7 +86,8 @@ describe("useFavoriteNode hook", () => {
|
||||
result.current.updateFavorite({ nodeNum: 9999, isFavorite: false });
|
||||
});
|
||||
|
||||
expect(mockUpdateFavorite).not.toHaveBeenCalled();
|
||||
expect(mockSdkFavorite).not.toHaveBeenCalled();
|
||||
expect(mockSdkUnfavorite).not.toHaveBeenCalled();
|
||||
expect(mockToast).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { useToast } from "@core/hooks/useToast.ts";
|
||||
import { useNodeDB } from "@core/stores";
|
||||
import { useActiveClient } from "@meshtastic/sdk-react";
|
||||
import { useCallback } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
@@ -9,11 +8,12 @@ interface FavoriteNodeOptions {
|
||||
isFavorite: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles the favorite flag on a node. Drives the SDK NodesClient which
|
||||
* sends the matching admin message and flips the local flag on success.
|
||||
*/
|
||||
export function useFavoriteNode() {
|
||||
const meshClient = useActiveClient();
|
||||
// Mirror to the legacy nodeDB until the favorite/ignore-flag projection on
|
||||
// the SDK Node entity drives the UI directly.
|
||||
const { updateFavorite } = useNodeDB();
|
||||
const { t } = useTranslation();
|
||||
const { toast } = useToast();
|
||||
|
||||
@@ -25,9 +25,6 @@ export function useFavoriteNode() {
|
||||
|
||||
void (isFavorite ? meshClient.nodes.favorite(nodeNum) : meshClient.nodes.unfavorite(nodeNum));
|
||||
|
||||
// TODO: drive store mutation off the admin-message ack instead of optimistic.
|
||||
updateFavorite(nodeNum, isFavorite);
|
||||
|
||||
toast({
|
||||
title: t("toast.favoriteNode.title", {
|
||||
action: isFavorite
|
||||
@@ -40,7 +37,7 @@ export function useFavoriteNode() {
|
||||
}),
|
||||
});
|
||||
},
|
||||
[meshClient, updateFavorite, t, toast],
|
||||
[meshClient, t, toast],
|
||||
);
|
||||
|
||||
return { updateFavorite: updateFavoriteCB };
|
||||
|
||||
@@ -2,7 +2,6 @@ import { act, renderHook } from "@testing-library/react";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { useIgnoreNode } from "./useIgnoreNode.ts";
|
||||
|
||||
const mockUpdateIgnore = vi.fn();
|
||||
const mockToast = vi.fn();
|
||||
const mockSdkIgnore = vi.fn();
|
||||
const mockSdkUnignore = vi.fn();
|
||||
@@ -15,15 +14,6 @@ vi.mock("@meshtastic/sdk-react", () => ({
|
||||
useActiveClient: mockUseActiveClient,
|
||||
}));
|
||||
|
||||
vi.mock("@core/stores", () => ({
|
||||
CurrentDeviceContext: {
|
||||
_currentValue: { deviceId: 1234 },
|
||||
},
|
||||
useNodeDB: () => ({
|
||||
updateIgnore: mockUpdateIgnore,
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock("@core/hooks/useToast.ts", () => ({
|
||||
useToast: () => ({
|
||||
toast: mockToast,
|
||||
@@ -47,7 +37,7 @@ describe("useIgnoreNode hook", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("calls updateIgnored and shows correct toast", () => {
|
||||
it("calls SDK ignore and shows correct toast", () => {
|
||||
const { result } = renderHook(() => useIgnoreNode());
|
||||
|
||||
act(() => {
|
||||
@@ -55,7 +45,6 @@ describe("useIgnoreNode hook", () => {
|
||||
});
|
||||
|
||||
expect(mockSdkIgnore).toHaveBeenCalledWith(1234);
|
||||
expect(mockUpdateIgnore).toHaveBeenCalledWith(1234, true);
|
||||
expect(mockToast).toHaveBeenCalledWith({
|
||||
title: "Added Test Node to ignore list",
|
||||
});
|
||||
@@ -69,7 +58,6 @@ describe("useIgnoreNode hook", () => {
|
||||
});
|
||||
|
||||
expect(mockSdkUnignore).toHaveBeenCalledWith(1234);
|
||||
expect(mockUpdateIgnore).toHaveBeenCalledWith(1234, false);
|
||||
expect(mockToast).toHaveBeenCalledWith({
|
||||
title: "Removed Test Node from ignore list",
|
||||
});
|
||||
@@ -98,7 +86,8 @@ describe("useIgnoreNode hook", () => {
|
||||
result.current.updateIgnored({ nodeNum: 9999, isIgnored: false });
|
||||
});
|
||||
|
||||
expect(mockUpdateIgnore).not.toHaveBeenCalled();
|
||||
expect(mockSdkIgnore).not.toHaveBeenCalled();
|
||||
expect(mockSdkUnignore).not.toHaveBeenCalled();
|
||||
expect(mockToast).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { useToast } from "@core/hooks/useToast.ts";
|
||||
import { useNodeDB } from "@core/stores";
|
||||
import { useActiveClient } from "@meshtastic/sdk-react";
|
||||
import { useCallback } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
@@ -9,9 +8,12 @@ interface IgnoreNodeOptions {
|
||||
isIgnored: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles the ignored flag on a node. Drives the SDK NodesClient which
|
||||
* sends the matching admin message and flips the local flag on success.
|
||||
*/
|
||||
export function useIgnoreNode() {
|
||||
const meshClient = useActiveClient();
|
||||
const { updateIgnore } = useNodeDB();
|
||||
const { t } = useTranslation();
|
||||
const { toast } = useToast();
|
||||
|
||||
@@ -23,8 +25,6 @@ export function useIgnoreNode() {
|
||||
|
||||
void (isIgnored ? meshClient.nodes.ignore(nodeNum) : meshClient.nodes.unignore(nodeNum));
|
||||
|
||||
updateIgnore(nodeNum, isIgnored);
|
||||
|
||||
toast({
|
||||
title: t("toast.ignoreNode.title", {
|
||||
nodeName: node.user?.longName ?? "node",
|
||||
@@ -37,7 +37,7 @@ export function useIgnoreNode() {
|
||||
}),
|
||||
});
|
||||
},
|
||||
[meshClient, updateIgnore, t, toast],
|
||||
[meshClient, t, toast],
|
||||
);
|
||||
|
||||
return { updateIgnored: updateIgnoredCB };
|
||||
|
||||
@@ -5,18 +5,20 @@ import { type MeshDevice, Protobuf } from "@meshtastic/sdk";
|
||||
/**
|
||||
* Wires up the legacy MeshDevice event stream into the web's Zustand stores.
|
||||
*
|
||||
* Note: the SDK chat slice already persists messages via the configured
|
||||
* SqlocalMessageRepository, so this function no longer copies messages into
|
||||
* the legacy messageStore. Unread-count increments stay here because that
|
||||
* logic still lives on the device store; it migrates to the SDK in a
|
||||
* follow-up "unread" cross-cutting commit.
|
||||
* Note: the SDK now owns chat persistence (via SqlocalMessageRepository) and
|
||||
* the entire NodesClient surface — node info, user, position, lastHeard /
|
||||
* snr, favourite / ignored flags, and PKI-error tracking. This handler no
|
||||
* longer mirrors any of that into the legacy stores; what remains is
|
||||
* device-store-only state (waypoints, traceroutes, neighbour info, dialog
|
||||
* open triggers, unread counts).
|
||||
*/
|
||||
export const subscribeAll = (
|
||||
device: Device,
|
||||
connection: MeshDevice,
|
||||
// biome-ignore lint/correctness/noUnusedFunctionParameters: kept for callsite stability while messageStore is being retired
|
||||
_messageStore: MessageStore,
|
||||
nodeDB: NodeDB,
|
||||
// biome-ignore lint/correctness/noUnusedFunctionParameters: kept for callsite stability while nodeDB is being retired
|
||||
_nodeDB: NodeDB,
|
||||
) => {
|
||||
let myNodeNum = 0;
|
||||
|
||||
@@ -62,20 +64,8 @@ export const subscribeAll = (
|
||||
myNodeNum = nodeInfo.myNodeNum;
|
||||
});
|
||||
|
||||
connection.events.onUserPacket.subscribe((user) => {
|
||||
nodeDB.addUser(user);
|
||||
});
|
||||
|
||||
connection.events.onPositionPacket.subscribe((position) => {
|
||||
nodeDB.addPosition(position);
|
||||
});
|
||||
|
||||
// NOTE: Node handling is managed by the nodeDB
|
||||
// Nodes are added via subscriptions.ts and stored in nodeDB
|
||||
// Configuration is handled directly by meshDevice.configure() in useConnections
|
||||
connection.events.onNodeInfoPacket.subscribe((nodeInfo) => {
|
||||
nodeDB.addNode(nodeInfo);
|
||||
});
|
||||
// onUserPacket / onPositionPacket / onNodeInfoPacket are handled by the
|
||||
// SDK NodesClient (see packages/sdk/src/features/nodes/NodesClient.ts).
|
||||
|
||||
connection.events.onChannelPacket.subscribe((channel) => {
|
||||
device.addChannel(channel);
|
||||
@@ -111,13 +101,8 @@ export const subscribeAll = (
|
||||
device.setPendingSettingsChanges(state);
|
||||
});
|
||||
|
||||
connection.events.onMeshPacket.subscribe((meshPacket) => {
|
||||
nodeDB.processPacket({
|
||||
from: meshPacket.from,
|
||||
snr: meshPacket.rxSnr,
|
||||
time: meshPacket.rxTime,
|
||||
});
|
||||
});
|
||||
// onMeshPacket → lastHeard / snr per-node updates are handled by the SDK
|
||||
// NodesClient.
|
||||
|
||||
connection.events.onClientNotificationPacket.subscribe((clientNotificationPacket) => {
|
||||
device.addClientNotification(clientNotificationPacket);
|
||||
|
||||
Reference in New Issue
Block a user