diff --git a/packages/web/src/App.tsx b/packages/web/src/App.tsx index 152bccda..9725a592 100644 --- a/packages/web/src/App.tsx +++ b/packages/web/src/App.tsx @@ -12,8 +12,6 @@ import { Outlet } from "@tanstack/react-router"; import { TanStackRouterDevtools } from "@tanstack/react-router-devtools"; import { ErrorBoundary } from "react-error-boundary"; import { MapProvider } from "react-map-gl/maplibre"; -// Import feature flags and dev overrides -import "@core/services/dev-overrides.ts"; export function App() { const { getDevice } = useDeviceStore(); diff --git a/packages/web/src/core/stores/nodeDBStore/index.ts b/packages/web/src/core/stores/nodeDBStore/index.ts index b5a228e9..c5cdb0d2 100644 --- a/packages/web/src/core/stores/nodeDBStore/index.ts +++ b/packages/web/src/core/stores/nodeDBStore/index.ts @@ -1,13 +1,13 @@ import { create } from "@bufbuild/protobuf"; +import { featureFlags } from "@core/services/featureFlags"; import { createStorage } from "@core/stores/utils/indexDB.ts"; import { Protobuf, type Types } from "@meshtastic/core"; import { produce } from "immer"; -import { create as createStore } from "zustand"; -import { persist } from "zustand/middleware"; +import { create as createStore, type StateCreator } from "zustand"; +import { type PersistOptions, persist } from "zustand/middleware"; import type { NodeError, NodeErrorType, ProcessPacketParams } from "./types"; const CURRENT_STORE_VERSION = 0; - const NODEDB_RETENTION_NUM = 10; export interface NodeDB { @@ -334,95 +334,107 @@ function nodeDBFactory( }; } -export const useNodeDBStore = createStore()( - persist( - (set, get) => ({ - nodeDBs: new Map(), - - addNodeDB: (id) => { - const existing = get().nodeDBs.get(id); - if (existing) { - return existing; +export const nodeDBInitializer: StateCreator = ( + set, + get, +) => ({ + nodeDBs: new Map(), + + addNodeDB: (id) => { + const existing = get().nodeDBs.get(id); + if (existing) { + return existing; + } + + const nodeDB = nodeDBFactory(id, get, set); + set( + produce((draft) => { + draft.nodeDBs.set(id, nodeDB); + + // If over limit, remove oldest inserted. FIFO + if (draft.nodeDBs.size > NODEDB_RETENTION_NUM) { + const firstKey = draft.nodeDBs.keys().next().value; + if (firstKey !== undefined) { + draft.nodeDBs.delete(firstKey); + } } - - const nodeDB = nodeDBFactory(id, get, set); - set( - produce((draft) => { - draft.nodeDBs.set(id, nodeDB); - - // If over limit, remove oldest inserted. FIFO - if (draft.nodeDBs.size > NODEDB_RETENTION_NUM) { - const firstKey = draft.nodeDBs.keys().next().value; - if (firstKey !== undefined) { - draft.nodeDBs.delete(firstKey); - } - } - }), - ); - - return nodeDB; - }, - removeNodeDB: (id) => { - set( - produce((draft) => { - draft.nodeDBs.delete(id); - }), - ); - }, - getNodeDBs: () => Array.from(get().nodeDBs.values()), - getNodeDB: (id) => get().nodeDBs.get(id), - }), - { - name: "meshtastic-nodedb-store", - storage: createStorage(), - version: CURRENT_STORE_VERSION, - partialize: (s): NodeDBPersisted => ({ - nodeDBs: new Map( - Array.from(s.nodeDBs.entries()).map(([id, db]) => [ - id, - { - id: db.id, - myNodeNum: db.myNodeNum, - nodeMap: db.nodeMap, - nodeErrors: db.nodeErrors, - }, - ]), - ), }), - onRehydrateStorage: () => (state) => { - if (!state) { - return; + ); + + return nodeDB; + }, + removeNodeDB: (id) => { + set( + produce((draft) => { + draft.nodeDBs.delete(id); + }), + ); + }, + getNodeDBs: () => Array.from(get().nodeDBs.values()), + getNodeDB: (id) => get().nodeDBs.get(id), +}); + +const persistOptions: PersistOptions = { + name: "meshtastic-nodedb-store", + storage: createStorage(), + version: CURRENT_STORE_VERSION, + partialize: (s): NodeDBPersisted => ({ + nodeDBs: new Map( + Array.from(s.nodeDBs.entries()).map(([id, db]) => [ + id, + { + id: db.id, + myNodeNum: db.myNodeNum, + nodeMap: db.nodeMap, + nodeErrors: db.nodeErrors, + }, + ]), + ), + }), + onRehydrateStorage: () => (state) => { + if (!state) { + return; + } + console.debug( + "NodeDBStore: Rehydrating state with ", + state.nodeDBs.size, + " nodeDBs -", + state.nodeDBs, + ); + + useNodeDBStore.setState( + produce((draft) => { + const rebuilt = new Map(); + for (const [id, data] of ( + draft.nodeDBs as unknown as Map + ).entries()) { + if (data.myNodeNum !== undefined) { + // Only rebuild if there is a nodenum set otherwise orphan dbs will acumulate + rebuilt.set( + id, + nodeDBFactory( + id, + useNodeDBStore.getState, + useNodeDBStore.setState, + data, + ), + ); + } } - console.debug( - "NodeDBStore: Rehydrating state with ", - state.nodeDBs.size, - " nodeDBs -", - state.nodeDBs, - ); - - useNodeDBStore.setState( - produce((draft) => { - const rebuilt = new Map(); - for (const [id, data] of ( - draft.nodeDBs as unknown as Map - ).entries()) { - if (data.myNodeNum !== undefined) { - // Only rebuild if there is a nodenum set otherwise orphan dbs will acumulate - rebuilt.set( - id, - nodeDBFactory( - id, - useNodeDBStore.getState, - useNodeDBStore.setState, - data, - ), - ); - } - } - draft.nodeDBs = rebuilt; - }), - ); - }, - }, - ), + draft.nodeDBs = rebuilt; + }), + ); + }, +}; + +// Add persist middleware on the store if the feature flag is enabled +const persistNodes = featureFlags.get("persistNodeDB"); +console.debug( + `NodeDBStore: Persisting nodes is ${persistNodes ? "enabled" : "disabled"}`, ); + +export const useNodeDBStore = persistNodes + ? createStore( + persist(nodeDBInitializer, persistOptions), + ) + : createStore()(nodeDBInitializer); diff --git a/packages/web/src/index.tsx b/packages/web/src/index.tsx index 40cc7da1..567e2e08 100644 --- a/packages/web/src/index.tsx +++ b/packages/web/src/index.tsx @@ -1,5 +1,8 @@ import React from "react"; import "@app/index.css"; + +// Import feature flags and dev overrides +import "@core/services/dev-overrides.ts"; import { enableMapSet } from "immer"; import "maplibre-gl/dist/maplibre-gl.css"; import { Suspense } from "react";