diff --git a/.github/workflows/release-packages.yml b/.github/workflows/release-packages.yml index 55453e4ab..b58b738f3 100644 --- a/.github/workflows/release-packages.yml +++ b/.github/workflows/release-packages.yml @@ -139,7 +139,7 @@ jobs: echo "Publishing $dir to npm…" ( cd "$dir" [ -d dist ] || pnpm run build - npm publish --access public + pnpm publish --access public --no-git-checks ) fi done diff --git a/apps/web/src/components/Dialog/ClientNotificationDialog/ClientNotificationDialog.tsx b/apps/web/src/components/Dialog/ClientNotificationDialog/ClientNotificationDialog.tsx index 20896a02c..021d48604 100644 --- a/apps/web/src/components/Dialog/ClientNotificationDialog/ClientNotificationDialog.tsx +++ b/apps/web/src/components/Dialog/ClientNotificationDialog/ClientNotificationDialog.tsx @@ -10,7 +10,7 @@ import { } from "@components/UI/Dialog.tsx"; import { Input } from "@components/UI/Input.tsx"; import { useDevice } from "@core/stores"; -import { Protobuf } from "@meshtastic/core"; +import { Protobuf } from "@meshtastic/sdk"; import { useState } from "react"; import { useTranslation } from "react-i18next"; diff --git a/apps/web/src/components/PageComponents/ModuleConfig/RemoteHardware.tsx b/apps/web/src/components/PageComponents/ModuleConfig/RemoteHardware.tsx index d1924efaa..82a3d3db9 100644 --- a/apps/web/src/components/PageComponents/ModuleConfig/RemoteHardware.tsx +++ b/apps/web/src/components/PageComponents/ModuleConfig/RemoteHardware.tsx @@ -8,32 +8,46 @@ import { type DynamicFormFormInit, } from "@components/Form/DynamicForm.tsx"; import { useDevice } from "@core/stores"; -import { deepCompareConfig } from "@core/utils/deepCompareConfig.ts"; +import type { Protobuf } from "@meshtastic/sdk"; +import { useConfigEditor, useSignal } from "@meshtastic/sdk-react"; import { useTranslation } from "react-i18next"; interface RemoteHardwareModuleConfigProps { onFormInit: DynamicFormFormInit; } +const EMPTY_MODULES_SIGNAL = { + value: {} as { + remoteHardware?: Protobuf.ModuleConfig.ModuleConfig_RemoteHardwareConfig; + }, + peek: () => + ({}) as { + remoteHardware?: Protobuf.ModuleConfig.ModuleConfig_RemoteHardwareConfig; + }, + subscribe: () => () => {}, +} as const; + export const RemoteHardware = ({ onFormInit, }: RemoteHardwareModuleConfigProps) => { useWaitForConfig({ moduleConfigCase: "remoteHardware" }); - const { moduleConfig, setChange, getEffectiveModuleConfig, removeChange } = - useDevice(); + const { moduleConfig, getEffectiveModuleConfig } = useDevice(); + const editor = useConfigEditor(); + const modules = useSignal(editor?.modules ?? EMPTY_MODULES_SIGNAL); + const effective = + modules.remoteHardware ?? + (getEffectiveModuleConfig("remoteHardware") as + | Protobuf.ModuleConfig.ModuleConfig_RemoteHardwareConfig + | undefined); + const { t } = useTranslation("moduleConfig"); const onSubmit = (data: RemoteHardwareValidation) => { - if (deepCompareConfig(moduleConfig.remoteHardware, data, true)) { - removeChange({ type: "moduleConfig", variant: "remoteHardware" }); - return; - } - - setChange( - { type: "moduleConfig", variant: "remoteHardware" }, - data, - moduleConfig.remoteHardware, + if (!editor) return; + editor.setModuleSection( + "remoteHardware", + data as unknown as Protobuf.ModuleConfig.ModuleConfig_RemoteHardwareConfig, ); }; @@ -43,7 +57,7 @@ export const RemoteHardware = ({ onFormInit={onFormInit} validationSchema={RemoteHardwareValidationSchema} defaultValues={moduleConfig.remoteHardware} - values={getEffectiveModuleConfig("remoteHardware")} + values={effective} fieldGroups={[ { label: t("remoteHardware.title"), diff --git a/apps/web/src/components/PageComponents/ModuleConfig/StatusMessage.tsx b/apps/web/src/components/PageComponents/ModuleConfig/StatusMessage.tsx index 25e8fe3c5..cd46cb0b1 100644 --- a/apps/web/src/components/PageComponents/ModuleConfig/StatusMessage.tsx +++ b/apps/web/src/components/PageComponents/ModuleConfig/StatusMessage.tsx @@ -8,32 +8,46 @@ import { type DynamicFormFormInit, } from "@components/Form/DynamicForm.tsx"; import { useDevice } from "@core/stores"; -import { deepCompareConfig } from "@core/utils/deepCompareConfig.ts"; +import type { Protobuf } from "@meshtastic/sdk"; +import { useConfigEditor, useSignal } from "@meshtastic/sdk-react"; import { useTranslation } from "react-i18next"; interface StatusMessageModuleConfigProps { onFormInit: DynamicFormFormInit; } +const EMPTY_MODULES_SIGNAL = { + value: {} as { + statusmessage?: Protobuf.ModuleConfig.ModuleConfig_StatusMessageConfig; + }, + peek: () => + ({}) as { + statusmessage?: Protobuf.ModuleConfig.ModuleConfig_StatusMessageConfig; + }, + subscribe: () => () => {}, +} as const; + export const StatusMessage = ({ onFormInit, }: StatusMessageModuleConfigProps) => { useWaitForConfig({ moduleConfigCase: "statusmessage" }); - const { moduleConfig, setChange, getEffectiveModuleConfig, removeChange } = - useDevice(); + const { moduleConfig, getEffectiveModuleConfig } = useDevice(); + const editor = useConfigEditor(); + const modules = useSignal(editor?.modules ?? EMPTY_MODULES_SIGNAL); + const effective = + modules.statusmessage ?? + (getEffectiveModuleConfig("statusmessage") as + | Protobuf.ModuleConfig.ModuleConfig_StatusMessageConfig + | undefined); + const { t } = useTranslation("moduleConfig"); const onSubmit = (data: StatusMessageValidation) => { - if (deepCompareConfig(moduleConfig.statusmessage, data, true)) { - removeChange({ type: "moduleConfig", variant: "statusmessage" }); - return; - } - - setChange( - { type: "moduleConfig", variant: "statusmessage" }, - data, - moduleConfig.statusmessage, + if (!editor) return; + editor.setModuleSection( + "statusmessage", + data as unknown as Protobuf.ModuleConfig.ModuleConfig_StatusMessageConfig, ); }; @@ -43,7 +57,7 @@ export const StatusMessage = ({ onFormInit={onFormInit} validationSchema={StatusMessageValidationSchema} defaultValues={moduleConfig.statusmessage} - values={getEffectiveModuleConfig("statusmessage")} + values={effective} fieldGroups={[ { label: t("statusMessage.title"), diff --git a/apps/web/src/components/PageComponents/ModuleConfig/Tak.tsx b/apps/web/src/components/PageComponents/ModuleConfig/Tak.tsx index a6d33a480..557d0df6d 100644 --- a/apps/web/src/components/PageComponents/ModuleConfig/Tak.tsx +++ b/apps/web/src/components/PageComponents/ModuleConfig/Tak.tsx @@ -8,28 +8,40 @@ import { type DynamicFormFormInit, } from "@components/Form/DynamicForm.tsx"; import { useDevice } from "@core/stores"; -import { deepCompareConfig } from "@core/utils/deepCompareConfig.ts"; -import { Protobuf } from "@meshtastic/core"; +import { Protobuf } from "@meshtastic/sdk"; +import { useConfigEditor, useSignal } from "@meshtastic/sdk-react"; import { useTranslation } from "react-i18next"; interface TakModuleConfigProps { onFormInit: DynamicFormFormInit; } +const EMPTY_MODULES_SIGNAL = { + value: {} as { tak?: Protobuf.ModuleConfig.ModuleConfig_TAKConfig }, + peek: () => ({}) as { tak?: Protobuf.ModuleConfig.ModuleConfig_TAKConfig }, + subscribe: () => () => {}, +} as const; + export const Tak = ({ onFormInit }: TakModuleConfigProps) => { useWaitForConfig({ moduleConfigCase: "tak" }); - const { moduleConfig, setChange, getEffectiveModuleConfig, removeChange } = - useDevice(); + const { moduleConfig, getEffectiveModuleConfig } = useDevice(); + const editor = useConfigEditor(); + const modules = useSignal(editor?.modules ?? EMPTY_MODULES_SIGNAL); + const effective = + modules.tak ?? + (getEffectiveModuleConfig("tak") as + | Protobuf.ModuleConfig.ModuleConfig_TAKConfig + | undefined); + const { t } = useTranslation("moduleConfig"); const onSubmit = (data: TakValidation) => { - if (deepCompareConfig(moduleConfig.tak, data, true)) { - removeChange({ type: "moduleConfig", variant: "tak" }); - return; - } - - setChange({ type: "moduleConfig", variant: "tak" }, data, moduleConfig.tak); + if (!editor) return; + editor.setModuleSection( + "tak", + data as unknown as Protobuf.ModuleConfig.ModuleConfig_TAKConfig, + ); }; return ( @@ -38,7 +50,7 @@ export const Tak = ({ onFormInit }: TakModuleConfigProps) => { onFormInit={onFormInit} validationSchema={TakValidationSchema} defaultValues={moduleConfig.tak} - values={getEffectiveModuleConfig("tak")} + values={effective} fieldGroups={[ { label: t("tak.title"), diff --git a/apps/web/src/components/PageComponents/ModuleConfig/TrafficManagement.tsx b/apps/web/src/components/PageComponents/ModuleConfig/TrafficManagement.tsx index cf6ac54c1..def93cf18 100644 --- a/apps/web/src/components/PageComponents/ModuleConfig/TrafficManagement.tsx +++ b/apps/web/src/components/PageComponents/ModuleConfig/TrafficManagement.tsx @@ -8,32 +8,46 @@ import { type DynamicFormFormInit, } from "@components/Form/DynamicForm.tsx"; import { useDevice } from "@core/stores"; -import { deepCompareConfig } from "@core/utils/deepCompareConfig.ts"; +import type { Protobuf } from "@meshtastic/sdk"; +import { useConfigEditor, useSignal } from "@meshtastic/sdk-react"; import { useTranslation } from "react-i18next"; interface TrafficManagementModuleConfigProps { onFormInit: DynamicFormFormInit; } +const EMPTY_MODULES_SIGNAL = { + value: {} as { + trafficManagement?: Protobuf.ModuleConfig.ModuleConfig_TrafficManagementConfig; + }, + peek: () => + ({}) as { + trafficManagement?: Protobuf.ModuleConfig.ModuleConfig_TrafficManagementConfig; + }, + subscribe: () => () => {}, +} as const; + export const TrafficManagement = ({ onFormInit, }: TrafficManagementModuleConfigProps) => { useWaitForConfig({ moduleConfigCase: "trafficManagement" }); - const { moduleConfig, setChange, getEffectiveModuleConfig, removeChange } = - useDevice(); + const { moduleConfig, getEffectiveModuleConfig } = useDevice(); + const editor = useConfigEditor(); + const modules = useSignal(editor?.modules ?? EMPTY_MODULES_SIGNAL); + const effective = + modules.trafficManagement ?? + (getEffectiveModuleConfig("trafficManagement") as + | Protobuf.ModuleConfig.ModuleConfig_TrafficManagementConfig + | undefined); + const { t } = useTranslation("moduleConfig"); const onSubmit = (data: TrafficManagementValidation) => { - if (deepCompareConfig(moduleConfig.trafficManagement, data, true)) { - removeChange({ type: "moduleConfig", variant: "trafficManagement" }); - return; - } - - setChange( - { type: "moduleConfig", variant: "trafficManagement" }, - data, - moduleConfig.trafficManagement, + if (!editor) return; + editor.setModuleSection( + "trafficManagement", + data as unknown as Protobuf.ModuleConfig.ModuleConfig_TrafficManagementConfig, ); }; @@ -43,7 +57,7 @@ export const TrafficManagement = ({ onFormInit={onFormInit} validationSchema={TrafficManagementValidationSchema} defaultValues={moduleConfig.trafficManagement} - values={getEffectiveModuleConfig("trafficManagement")} + values={effective} fieldGroups={[ { label: t("trafficManagement.title"), diff --git a/apps/web/src/components/UI/ErrorPage.tsx b/apps/web/src/components/UI/ErrorPage.tsx index c35a74df8..037d62756 100644 --- a/apps/web/src/components/UI/ErrorPage.tsx +++ b/apps/web/src/components/UI/ErrorPage.tsx @@ -5,12 +5,15 @@ import newGithubIssueUrl from "@core/utils/github.ts"; import { ExternalLink } from "lucide-react"; import { Trans, useTranslation } from "react-i18next"; -export function ErrorPage({ error }: { error: Error }) { +// react-error-boundary types the caught value as `unknown` — anything can be +// thrown, so normalize non-Error values before rendering. +export function ErrorPage({ error: caught }: { error: unknown }) { const { t } = useTranslation(); - if (!error) { + if (!caught) { return null; } + const error = caught instanceof Error ? caught : new Error(String(caught)); return (
diff --git a/apps/web/src/components/generic/Filter/useFilterNode.test.ts b/apps/web/src/components/generic/Filter/useFilterNode.test.ts index 1ed8fa00d..d199683ff 100644 --- a/apps/web/src/components/generic/Filter/useFilterNode.test.ts +++ b/apps/web/src/components/generic/Filter/useFilterNode.test.ts @@ -16,6 +16,7 @@ function createMockNode(): Protobuf.Mesh.NodeInfo { isMuted: false, hopsAway: 2, isKeyManuallyVerified: false, + hasXeddsaSigned: false, user: { $typeName: "meshtastic.User", id: "!12345678", diff --git a/apps/web/src/pages/Connections/useConnections.ts b/apps/web/src/pages/Connections/useConnections.ts index 4c7ae1930..d8cf7d0c5 100644 --- a/apps/web/src/pages/Connections/useConnections.ts +++ b/apps/web/src/pages/Connections/useConnections.ts @@ -249,7 +249,9 @@ export function useConnections() { // Read from the live store, not the memoized `connections` closure: callers // such as addConnectionAndConnect() add a connection and connect to it in the // same tick, before this hook re-renders, so the closure would be stale. - const conn = useDeviceStore.getState().savedConnections.find((c) => c.id === id); + const conn = useDeviceStore + .getState() + .savedConnections.find((c) => c.id === id); if (!conn) { log.warn("connect: unknown connection id", { id }); return false; diff --git a/apps/web/src/sdk-preview/adapters/fromMeshDevice.ts b/apps/web/src/sdk-preview/adapters/fromMeshDevice.ts index 45231214b..7bebf88fc 100644 --- a/apps/web/src/sdk-preview/adapters/fromMeshDevice.ts +++ b/apps/web/src/sdk-preview/adapters/fromMeshDevice.ts @@ -1,4 +1,4 @@ -import type { MeshDevice } from "@meshtastic/core"; +import type { MeshDevice } from "@meshtastic/sdk"; import type { MeshClientPort } from "../core/index.ts"; import { ConfigEditor } from "../features/config/index.ts"; diff --git a/apps/web/src/sdk-preview/core/client/MeshClientPort.ts b/apps/web/src/sdk-preview/core/client/MeshClientPort.ts index 0ac582f2e..a78e9d1aa 100644 --- a/apps/web/src/sdk-preview/core/client/MeshClientPort.ts +++ b/apps/web/src/sdk-preview/core/client/MeshClientPort.ts @@ -1,4 +1,4 @@ -import type { Protobuf, Types } from "@meshtastic/core"; +import type { Protobuf, Types } from "@meshtastic/sdk"; /** * Minimal subscribe-only view of an event source. `ste-simple-events`' diff --git a/apps/web/src/sdk-preview/features/config/__tests__/ConfigEditor.test.ts b/apps/web/src/sdk-preview/features/config/__tests__/ConfigEditor.test.ts index 3919e5ea9..e1a0ea719 100644 --- a/apps/web/src/sdk-preview/features/config/__tests__/ConfigEditor.test.ts +++ b/apps/web/src/sdk-preview/features/config/__tests__/ConfigEditor.test.ts @@ -1,5 +1,5 @@ import { create } from "@bufbuild/protobuf"; -import { Protobuf, Types } from "@meshtastic/core"; +import { Protobuf, Types } from "@meshtastic/sdk"; import { Result } from "better-result"; import { describe, expect, it } from "vitest"; import type { MeshClientPort, Subscribable } from "../../../core/index.ts"; diff --git a/apps/web/src/sdk-preview/features/config/application/ConfigUseCases.ts b/apps/web/src/sdk-preview/features/config/application/ConfigUseCases.ts index f5cca125c..465d48fef 100644 --- a/apps/web/src/sdk-preview/features/config/application/ConfigUseCases.ts +++ b/apps/web/src/sdk-preview/features/config/application/ConfigUseCases.ts @@ -1,6 +1,5 @@ -import type { Protobuf } from "@meshtastic/core"; +import type { Protobuf } from "@meshtastic/sdk"; import { Result } from "better-result"; -import type { ResultType } from "better-result"; import type { MeshClientPort } from "../../../core/client/MeshClientPort.ts"; import { type MeshError, toMeshError } from "../../../core/errors/MeshError.ts"; @@ -12,7 +11,7 @@ import { type MeshError, toMeshError } from "../../../core/errors/MeshError.ts"; export async function beginEditSettings( client: MeshClientPort, -): Promise> { +): Promise> { try { return Result.ok(await client.beginEditSettings()); } catch (error) { @@ -22,7 +21,7 @@ export async function beginEditSettings( export async function commitEditSettings( client: MeshClientPort, -): Promise> { +): Promise> { try { return Result.ok(await client.commitEditSettings()); } catch (error) { @@ -33,7 +32,7 @@ export async function commitEditSettings( export async function setConfig( client: MeshClientPort, config: Protobuf.Config.Config, -): Promise> { +): Promise> { try { return Result.ok(await client.setConfig(config)); } catch (error) { @@ -44,7 +43,7 @@ export async function setConfig( export async function setModuleConfig( client: MeshClientPort, moduleConfig: Protobuf.ModuleConfig.ModuleConfig, -): Promise> { +): Promise> { try { return Result.ok(await client.setModuleConfig(moduleConfig)); } catch (error) { diff --git a/apps/web/src/sdk-preview/features/config/domain/ConfigEditor.ts b/apps/web/src/sdk-preview/features/config/domain/ConfigEditor.ts index 3c8a9f94a..c80fe071b 100644 --- a/apps/web/src/sdk-preview/features/config/domain/ConfigEditor.ts +++ b/apps/web/src/sdk-preview/features/config/domain/ConfigEditor.ts @@ -1,7 +1,6 @@ -import { Types } from "@meshtastic/core"; +import { Types } from "@meshtastic/sdk"; import { signal } from "@preact/signals-core"; import { Result } from "better-result"; -import type { ResultType } from "better-result"; import type { MeshClientPort } from "../../../core/client/MeshClientPort.ts"; import type { MeshError } from "../../../core/errors/MeshError.ts"; import { @@ -144,7 +143,7 @@ export class ConfigEditor { * inbound config packets after commit reconcile. Any failure aborts and * returns the error — the baseline is left untouched. */ - public async commit(): Promise> { + public async commit(): Promise> { if (!this._isDirty.peek()) { return Result.ok(undefined); } diff --git a/apps/web/src/sdk-preview/features/config/domain/ModuleConfig.ts b/apps/web/src/sdk-preview/features/config/domain/ModuleConfig.ts index e47215d62..7ca6bf583 100644 --- a/apps/web/src/sdk-preview/features/config/domain/ModuleConfig.ts +++ b/apps/web/src/sdk-preview/features/config/domain/ModuleConfig.ts @@ -1,4 +1,4 @@ -import type { Protobuf } from "@meshtastic/core"; +import type { Protobuf } from "@meshtastic/sdk"; /** * The module-config sections keyed by their protobuf variant case. Because this diff --git a/apps/web/src/sdk-preview/features/config/domain/RadioConfig.ts b/apps/web/src/sdk-preview/features/config/domain/RadioConfig.ts index 416918f5a..4d6d21ac6 100644 --- a/apps/web/src/sdk-preview/features/config/domain/RadioConfig.ts +++ b/apps/web/src/sdk-preview/features/config/domain/RadioConfig.ts @@ -1,4 +1,4 @@ -import type { Protobuf } from "@meshtastic/core"; +import type { Protobuf } from "@meshtastic/sdk"; /** * The radio-config sections keyed by their protobuf variant case (excluding the diff --git a/apps/web/src/sdk-preview/features/config/infrastructure/ConfigMapper.ts b/apps/web/src/sdk-preview/features/config/infrastructure/ConfigMapper.ts index 5d4875dd9..6937ba4fb 100644 --- a/apps/web/src/sdk-preview/features/config/infrastructure/ConfigMapper.ts +++ b/apps/web/src/sdk-preview/features/config/infrastructure/ConfigMapper.ts @@ -1,4 +1,4 @@ -import type { Protobuf } from "@meshtastic/core"; +import type { Protobuf } from "@meshtastic/sdk"; import type { ModuleConfig } from "../domain/ModuleConfig.ts"; import type { RadioConfig } from "../domain/RadioConfig.ts"; diff --git a/apps/web/src/sdk-preview/features/config/infrastructure/configBuilders.ts b/apps/web/src/sdk-preview/features/config/infrastructure/configBuilders.ts index e240d2a81..412a898e3 100644 --- a/apps/web/src/sdk-preview/features/config/infrastructure/configBuilders.ts +++ b/apps/web/src/sdk-preview/features/config/infrastructure/configBuilders.ts @@ -1,5 +1,5 @@ import { create } from "@bufbuild/protobuf"; -import { Protobuf } from "@meshtastic/core"; +import { Protobuf } from "@meshtastic/sdk"; import type { ModuleConfig, ModuleConfigSection, diff --git a/apps/web/src/validation/moduleConfig/tak.ts b/apps/web/src/validation/moduleConfig/tak.ts index 5ffb055f1..248598dc5 100644 --- a/apps/web/src/validation/moduleConfig/tak.ts +++ b/apps/web/src/validation/moduleConfig/tak.ts @@ -1,4 +1,4 @@ -import { Protobuf } from "@meshtastic/core"; +import { Protobuf } from "@meshtastic/sdk"; import { z } from "zod/v4"; const TeamEnum = z.enum(Protobuf.ATAK.Team); diff --git a/e2e/fixtures/peer.ts b/e2e/fixtures/peer.ts index ca512bc3b..a0600f82b 100644 --- a/e2e/fixtures/peer.ts +++ b/e2e/fixtures/peer.ts @@ -6,7 +6,8 @@ import path from "node:path"; * that talks to the non-browser node over the TCP phone API. It sends text, * blocks until a specific text is received, or reports the node's number. */ -const PYTHON = process.env.E2E_PEER_PYTHON ?? path.resolve("e2e/peer/.venv/bin/python"); +const PYTHON = + process.env.E2E_PEER_PYTHON ?? path.resolve("e2e/peer/.venv/bin/python"); const SCRIPT = path.resolve("e2e/peer/peer.py"); const HOST = process.env.E2E_PEER_HOST ?? "127.0.0.1"; const PORT = process.env.E2E_PEER_PORT ?? "14404"; @@ -16,7 +17,10 @@ function spawnPeer(args: string[]): ChildProcess { } /** Invoke `onLine` for each complete stdout line. */ -function onStdoutLines(child: ChildProcess, onLine: (line: string) => void): void { +function onStdoutLines( + child: ChildProcess, + onLine: (line: string) => void, +): void { let buf = ""; child.stdout?.on("data", (chunk: Buffer) => { buf += chunk.toString(); @@ -44,7 +48,9 @@ export function peerSend( }); return new Promise((resolve, reject) => { child.on("exit", (code) => - code === 0 ? resolve() : reject(new Error(`peer send exited ${code}: ${stderr.trim()}`)), + code === 0 + ? resolve() + : reject(new Error(`peer send exited ${code}: ${stderr.trim()}`)), ); child.on("error", reject); }); diff --git a/e2e/global-setup.ts b/e2e/global-setup.ts index 7fe642486..51ac0a786 100644 --- a/e2e/global-setup.ts +++ b/e2e/global-setup.ts @@ -44,11 +44,17 @@ async function waitForHttps(url: string, timeoutMs: number): Promise { if (ok) return; await sleep(1000); } - throw new Error(`device webserver not ready at ${url} within ${timeoutMs}ms (last: ${lastErr})`); + throw new Error( + `device webserver not ready at ${url} within ${timeoutMs}ms (last: ${lastErr})`, + ); } /** Poll a TCP port until it accepts a connection. */ -async function waitForTcp(host: string, port: number, timeoutMs: number): Promise { +async function waitForTcp( + host: string, + port: number, + timeoutMs: number, +): Promise { const deadline = Date.now() + timeoutMs; let lastErr = "connection refused"; while (Date.now() < deadline) { @@ -83,7 +89,9 @@ export default async function globalSetup(): Promise { console.log("[e2e] bringing up meshtasticd two-node mesh ..."); execSync(`docker compose -f ${COMPOSE_FILE} up -d`, { stdio: "inherit" }); } else { - console.log(`[e2e] hardware mode: device=${NODE_A_URL} peer=${PEER_HOST}:${PEER_PORT}`); + console.log( + `[e2e] hardware mode: device=${NODE_A_URL} peer=${PEER_HOST}:${PEER_PORT}`, + ); } console.log(`[e2e] waiting for device webserver ${NODE_A_URL} ...`); diff --git a/e2e/global-teardown.ts b/e2e/global-teardown.ts index 5a91feff4..8fd272bef 100644 --- a/e2e/global-teardown.ts +++ b/e2e/global-teardown.ts @@ -17,6 +17,8 @@ export default async function globalTeardown(): Promise { console.log("[e2e] tearing down meshtasticd mesh ..."); execSync(`docker compose -f ${COMPOSE_FILE} down -v`, { stdio: "inherit" }); } else { - console.log("[e2e] leaving meshtasticd mesh running (set E2E_DOCKER_DOWN=1 to stop)."); + console.log( + "[e2e] leaving meshtasticd mesh running (set E2E_DOCKER_DOWN=1 to stop).", + ); } } diff --git a/e2e/pages/ConnectionPage.ts b/e2e/pages/ConnectionPage.ts index 95ba9d317..a39e194e5 100644 --- a/e2e/pages/ConnectionPage.ts +++ b/e2e/pages/ConnectionPage.ts @@ -9,7 +9,11 @@ import { expect, type Page } from "@playwright/test"; export class ConnectionPage { constructor(private readonly page: Page) {} - async connectHttp(opts: { host: string; tls: boolean; name?: string }): Promise { + async connectHttp(opts: { + host: string; + tls: boolean; + name?: string; + }): Promise { const { host, tls, name = "E2E Device" } = opts; const page = this.page; @@ -25,7 +29,8 @@ export class ConnectionPage { await dialog.locator("#url").fill(host); const httpsSwitch = dialog.getByRole("switch"); - const isChecked = (await httpsSwitch.getAttribute("aria-checked")) === "true"; + const isChecked = + (await httpsSwitch.getAttribute("aria-checked")) === "true"; if (tls !== isChecked) { await httpsSwitch.click(); } @@ -33,11 +38,16 @@ export class ConnectionPage { await dialog.getByRole("button", { name: "Test connection" }).click(); const save = dialog.getByRole("button", { name: "Save connection" }); - await expect(save, "Save enables only after the device is reachable").toBeEnabled({ + await expect( + save, + "Save enables only after the device is reachable", + ).toBeEnabled({ timeout: 20_000, }); await save.click(); - await expect(page).toHaveURL(/\/messages\/broadcast\/0/, { timeout: 60_000 }); + await expect(page).toHaveURL(/\/messages\/broadcast\/0/, { + timeout: 60_000, + }); } } diff --git a/e2e/pages/MessagesPage.ts b/e2e/pages/MessagesPage.ts index 25626e52a..bf878304e 100644 --- a/e2e/pages/MessagesPage.ts +++ b/e2e/pages/MessagesPage.ts @@ -23,7 +23,9 @@ export class MessagesPage { await expect(this.input()).toBeEnabled(); // The SDK chat client lags the composer (device handshake + sqlocal init), so // an immediate send is silently dropped. Gate on the "Connected" status. - await expect(this.page.getByText("Connected", { exact: true }).first()).toBeVisible({ + await expect( + this.page.getByText("Connected", { exact: true }).first(), + ).toBeVisible({ timeout: 30_000, }); } diff --git a/e2e/tests/messaging.broadcast.spec.ts b/e2e/tests/messaging.broadcast.spec.ts index f17724e64..04f8f1785 100644 --- a/e2e/tests/messaging.broadcast.spec.ts +++ b/e2e/tests/messaging.broadcast.spec.ts @@ -13,13 +13,17 @@ test.describe("broadcast messaging over a real two-node mesh", () => { await messagesPage.waitReady(); }); - test("renders a broadcast received from a mesh peer (mesh -> web)", async ({ messagesPage }) => { + test("renders a broadcast received from a mesh peer (mesh -> web)", async ({ + messagesPage, + }) => { const nonce = `pong-${Date.now()}`; await peerSend(nonce); await messagesPage.expectMessage(nonce); }); - test("delivers a typed broadcast to the mesh (web -> mesh)", async ({ messagesPage }) => { + test("delivers a typed broadcast to the mesh (web -> mesh)", async ({ + messagesPage, + }) => { const nonce = `ping-${Date.now()}`; // Listen on the peer node before sending, then type+send from the browser. const recv = await startPeerRecv(nonce, { timeout: 60 }); diff --git a/e2e/tests/messaging.direct.spec.ts b/e2e/tests/messaging.direct.spec.ts index af3e9aa70..696a00837 100644 --- a/e2e/tests/messaging.direct.spec.ts +++ b/e2e/tests/messaging.direct.spec.ts @@ -28,16 +28,15 @@ test.describe("direct messaging over a real two-node mesh", () => { await messagesPage.waitReady(); }); - test.fixme( - "delivers a direct message from the browser to the peer node (web -> mesh)", - async ({ messagesPage }) => { - const peerNum = await peerNodeNum(); - const nonce = `dm-${Date.now()}`; - const recv = await startPeerRecv(nonce, { timeout: 60 }); - await messagesPage.openDirectMessageByNodeNum(peerNum); - await messagesPage.send(nonce); - const from = await recv.received; - expect(from).toBeGreaterThan(0); - }, - ); + test.fixme("delivers a direct message from the browser to the peer node (web -> mesh)", async ({ + messagesPage, + }) => { + const peerNum = await peerNodeNum(); + const nonce = `dm-${Date.now()}`; + const recv = await startPeerRecv(nonce, { timeout: 60 }); + await messagesPage.openDirectMessageByNodeNum(peerNum); + await messagesPage.send(nonce); + const from = await recv.received; + expect(from).toBeGreaterThan(0); + }); }); diff --git a/packages/sdk-react/src/hooks/useChat.ts b/packages/sdk-react/src/hooks/useChat.ts index f4b2f9461..65b30ca5d 100644 --- a/packages/sdk-react/src/hooks/useChat.ts +++ b/packages/sdk-react/src/hooks/useChat.ts @@ -5,14 +5,14 @@ import type { SendTextError, SendTextInput, } from "@meshtastic/sdk"; -import type { ResultType } from "better-result"; +import type { Result } from "better-result"; import { useCallback, useMemo } from "react"; import { useClient } from "../adapters/useClient.ts"; import { useSignal } from "../adapters/useSignal.ts"; export interface UseChatResult { messages: Message[]; - send(input: SendTextInput): Promise>; + send(input: SendTextInput): Promise>; loadOlder(before: Date, limit?: number): Promise; } diff --git a/packages/sdk-react/src/hooks/useDirectChat.ts b/packages/sdk-react/src/hooks/useDirectChat.ts index 780026fd3..49b194837 100644 --- a/packages/sdk-react/src/hooks/useDirectChat.ts +++ b/packages/sdk-react/src/hooks/useDirectChat.ts @@ -4,14 +4,14 @@ import type { SendTextError, SendTextInput, } from "@meshtastic/sdk"; -import type { ResultType } from "better-result"; +import type { Result } from "better-result"; import { useCallback, useMemo } from "react"; import { useClient } from "../adapters/useClient.ts"; import { useSignal } from "../adapters/useSignal.ts"; export interface UseDirectChatResult { messages: Message[]; - send(input: SendTextInput): Promise>; + send(input: SendTextInput): Promise>; loadOlder(before: Date, limit?: number): Promise; } diff --git a/packages/sdk-react/src/hooks/useFavoriteNode.ts b/packages/sdk-react/src/hooks/useFavoriteNode.ts index 588ed8fd5..96068c8a2 100644 --- a/packages/sdk-react/src/hooks/useFavoriteNode.ts +++ b/packages/sdk-react/src/hooks/useFavoriteNode.ts @@ -1,16 +1,16 @@ -import type { ResultType } from "better-result"; +import type { Result } from "better-result"; import { useCallback } from "react"; import { useClient } from "../adapters/useClient.ts"; export function useFavoriteNode() { const client = useClient(); const favorite = useCallback( - (nodeNum: number): Promise> => + (nodeNum: number): Promise> => client.nodes.favorite(nodeNum), [client], ); const unfavorite = useCallback( - (nodeNum: number): Promise> => + (nodeNum: number): Promise> => client.nodes.unfavorite(nodeNum), [client], ); diff --git a/packages/sdk-react/src/hooks/useIgnoreNode.ts b/packages/sdk-react/src/hooks/useIgnoreNode.ts index 5f72ab85c..88a0ff0c0 100644 --- a/packages/sdk-react/src/hooks/useIgnoreNode.ts +++ b/packages/sdk-react/src/hooks/useIgnoreNode.ts @@ -1,16 +1,16 @@ -import type { ResultType } from "better-result"; +import type { Result } from "better-result"; import { useCallback } from "react"; import { useClient } from "../adapters/useClient.ts"; export function useIgnoreNode() { const client = useClient(); const ignore = useCallback( - (nodeNum: number): Promise> => + (nodeNum: number): Promise> => client.nodes.ignore(nodeNum), [client], ); const unignore = useCallback( - (nodeNum: number): Promise> => + (nodeNum: number): Promise> => client.nodes.unignore(nodeNum), [client], ); diff --git a/packages/sdk/package.json b/packages/sdk/package.json index f098be44c..54784694c 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -48,7 +48,7 @@ }, "dependencies": { "@bufbuild/protobuf": "^2.12.1", - "@meshtastic/protobufs": "jsr:^2.7.18", + "@meshtastic/protobufs": "workspace:*", "@preact/signals-core": "^1.14.3", "better-result": "^2.9.2", "crc": "npm:crc@^4.3.2", diff --git a/packages/sdk/src/features/channels/ChannelsClient.ts b/packages/sdk/src/features/channels/ChannelsClient.ts index 74f237bcc..33f01970e 100644 --- a/packages/sdk/src/features/channels/ChannelsClient.ts +++ b/packages/sdk/src/features/channels/ChannelsClient.ts @@ -1,5 +1,5 @@ import type * as Protobuf from "@meshtastic/protobufs"; -import type { ResultType } from "better-result"; +import type { Result } from "better-result"; import type { MeshClient } from "../../core/client/MeshClient.ts"; import type { ReadonlySignal } from "../../core/signals/createStore.ts"; import { @@ -32,15 +32,15 @@ export class ChannelsClient { public set( channel: Protobuf.Channel.Channel, - ): Promise> { + ): Promise> { return setChannel(this.client, channel); } - public requestChannel(index: number): Promise> { + public requestChannel(index: number): Promise> { return getChannel(this.client, index); } - public clear(index: number): Promise> { + public clear(index: number): Promise> { return clearChannel(this.client, index); } } diff --git a/packages/sdk/src/features/channels/application/ChannelUseCases.ts b/packages/sdk/src/features/channels/application/ChannelUseCases.ts index 3836dbc87..4bf9812a8 100644 --- a/packages/sdk/src/features/channels/application/ChannelUseCases.ts +++ b/packages/sdk/src/features/channels/application/ChannelUseCases.ts @@ -1,14 +1,13 @@ import * as Protobuf from "@meshtastic/protobufs"; import { create } from "@bufbuild/protobuf"; import { Result } from "better-result"; -import type { ResultType } from "better-result"; import type { MeshClient } from "../../../core/client/MeshClient.ts"; import { sendAdminMessage } from "../../device/infrastructure/AdminMessageSender.ts"; export async function setChannel( client: MeshClient, channel: Protobuf.Channel.Channel, -): Promise> { +): Promise> { try { const id = await sendAdminMessage(client, { case: "setChannel", @@ -23,7 +22,7 @@ export async function setChannel( export async function getChannel( client: MeshClient, index: number, -): Promise> { +): Promise> { try { const id = await sendAdminMessage(client, { case: "getChannelRequest", @@ -38,7 +37,7 @@ export async function getChannel( export async function clearChannel( client: MeshClient, index: number, -): Promise> { +): Promise> { const channel = create(Protobuf.Channel.ChannelSchema, { index, role: Protobuf.Channel.Channel_Role.DISABLED, diff --git a/packages/sdk/src/features/chat/ChatClient.send.test.ts b/packages/sdk/src/features/chat/ChatClient.send.test.ts index dd6564de2..8c19cf7e6 100644 --- a/packages/sdk/src/features/chat/ChatClient.send.test.ts +++ b/packages/sdk/src/features/chat/ChatClient.send.test.ts @@ -1,4 +1,4 @@ -import type { ResultType } from "better-result"; +import type { Result } from "better-result"; import { describe, expect, it } from "vitest"; import { MeshClient } from "../../core/client/MeshClient.ts"; import { createFakeTransport } from "../../core/testing/createFakeTransport.ts"; @@ -6,7 +6,7 @@ import { ChannelNumber } from "../../core/types.ts"; import type { SendTextError } from "./application/SendTextUseCase.ts"; import { MessageState } from "./domain/MessageState.ts"; -type SendResult = ResultType; +type SendResult = Result; /** * `client.chat.send` resolves only after the packet is ack'd via diff --git a/packages/sdk/src/features/chat/ChatClient.ts b/packages/sdk/src/features/chat/ChatClient.ts index 5116cc2ad..cc3914410 100644 --- a/packages/sdk/src/features/chat/ChatClient.ts +++ b/packages/sdk/src/features/chat/ChatClient.ts @@ -1,5 +1,5 @@ import { signal } from "@preact/signals-core"; -import type { ResultType } from "better-result"; +import type { Result } from "better-result"; import type { MeshClient } from "../../core/client/MeshClient.ts"; import { Constants } from "../../core/constants/index.ts"; import { generatePacketId } from "../../core/identifiers/PacketId.ts"; @@ -195,7 +195,7 @@ export class ChatClient { public async send( input: SendTextInput, - ): Promise> { + ): Promise> { const conv: ConversationKey = typeof input.destination === "number" ? { kind: "direct", peer: input.destination } diff --git a/packages/sdk/src/features/chat/application/SendTextUseCase.ts b/packages/sdk/src/features/chat/application/SendTextUseCase.ts index 15709d459..7e93d1dc4 100644 --- a/packages/sdk/src/features/chat/application/SendTextUseCase.ts +++ b/packages/sdk/src/features/chat/application/SendTextUseCase.ts @@ -1,6 +1,5 @@ import * as Protobuf from "@meshtastic/protobufs"; import { Result } from "better-result"; -import type { ResultType } from "better-result"; import type { MeshClient } from "../../../core/client/MeshClient.ts"; import { ChannelNumber, @@ -53,7 +52,7 @@ const MAX_TEXT_BYTES = 228; export async function sendText( client: MeshClient, input: SendTextInput, -): Promise> { +): Promise> { if (input.text.length === 0) { return Result.err(new EmptyMessageError()); } diff --git a/packages/sdk/src/features/chat/application/SendWaypointUseCase.ts b/packages/sdk/src/features/chat/application/SendWaypointUseCase.ts index e274bba3d..e0725dd94 100644 --- a/packages/sdk/src/features/chat/application/SendWaypointUseCase.ts +++ b/packages/sdk/src/features/chat/application/SendWaypointUseCase.ts @@ -1,7 +1,6 @@ import { toBinary } from "@bufbuild/protobuf"; import * as Protobuf from "@meshtastic/protobufs"; import { Result } from "better-result"; -import type { ResultType } from "better-result"; import type { MeshClient } from "../../../core/client/MeshClient.ts"; import { generatePacketId } from "../../../core/identifiers/PacketId.ts"; import { @@ -15,7 +14,7 @@ export async function sendWaypoint( waypoint: Protobuf.Mesh.Waypoint, destination: Destination, channel: ChannelNumber = ChannelNumber.Primary, -): Promise> { +): Promise> { client.log.debug( Emitter[Emitter.SendWaypoint], `📤 Sending waypoint to ${destination} on channel ${channel}`, diff --git a/packages/sdk/src/features/config/ConfigClient.ts b/packages/sdk/src/features/config/ConfigClient.ts index 09502bbb2..37ea6572c 100644 --- a/packages/sdk/src/features/config/ConfigClient.ts +++ b/packages/sdk/src/features/config/ConfigClient.ts @@ -1,6 +1,6 @@ import * as Protobuf from "@meshtastic/protobufs"; import { computed } from "@preact/signals-core"; -import type { ResultType } from "better-result"; +import type { Result } from "better-result"; import type { MeshClient } from "../../core/client/MeshClient.ts"; import { type ReadonlySignal, @@ -68,35 +68,35 @@ export class ConfigClient { }); } - public beginEdit(): Promise> { + public beginEdit(): Promise> { return beginEditSettings(this.client); } - public commitEdit(): Promise> { + public commitEdit(): Promise> { return commitEditSettings(this.client); } public setRadio( config: Protobuf.Config.Config, - ): Promise> { + ): Promise> { return setConfig(this.client, config); } public getRadio( type: Protobuf.Admin.AdminMessage_ConfigType, - ): Promise> { + ): Promise> { return getConfig(this.client, type); } public setModule( moduleConfig: Protobuf.ModuleConfig.ModuleConfig, - ): Promise> { + ): Promise> { return setModuleConfig(this.client, moduleConfig); } public getModule( type: Protobuf.Admin.AdminMessage_ModuleConfigType, - ): Promise> { + ): Promise> { return getModuleConfig(this.client, type); } } diff --git a/packages/sdk/src/features/config/application/ConfigUseCases.ts b/packages/sdk/src/features/config/application/ConfigUseCases.ts index 9521ea29b..06968c307 100644 --- a/packages/sdk/src/features/config/application/ConfigUseCases.ts +++ b/packages/sdk/src/features/config/application/ConfigUseCases.ts @@ -1,12 +1,11 @@ import type * as Protobuf from "@meshtastic/protobufs"; import { Result } from "better-result"; -import type { ResultType } from "better-result"; import type { MeshClient } from "../../../core/client/MeshClient.ts"; import { sendAdminMessage } from "../../device/infrastructure/AdminMessageSender.ts"; export async function beginEditSettings( client: MeshClient, -): Promise> { +): Promise> { client.events.onPendingSettingsChange.dispatch(true); try { const id = await sendAdminMessage(client, { @@ -21,7 +20,7 @@ export async function beginEditSettings( export async function commitEditSettings( client: MeshClient, -): Promise> { +): Promise> { client.events.onPendingSettingsChange.dispatch(false); try { const id = await sendAdminMessage(client, { @@ -37,7 +36,7 @@ export async function commitEditSettings( export async function setConfig( client: MeshClient, config: Protobuf.Config.Config, -): Promise> { +): Promise> { try { const id = await sendAdminMessage(client, { case: "setConfig", @@ -52,7 +51,7 @@ export async function setConfig( export async function getConfig( client: MeshClient, type: Protobuf.Admin.AdminMessage_ConfigType, -): Promise> { +): Promise> { try { const id = await sendAdminMessage(client, { case: "getConfigRequest", @@ -67,7 +66,7 @@ export async function getConfig( export async function setModuleConfig( client: MeshClient, moduleConfig: Protobuf.ModuleConfig.ModuleConfig, -): Promise> { +): Promise> { try { const id = await sendAdminMessage(client, { case: "setModuleConfig", @@ -82,7 +81,7 @@ export async function setModuleConfig( export async function getModuleConfig( client: MeshClient, type: Protobuf.Admin.AdminMessage_ModuleConfigType, -): Promise> { +): Promise> { try { const id = await sendAdminMessage(client, { case: "getModuleConfigRequest", diff --git a/packages/sdk/src/features/config/domain/ConfigEditor.ts b/packages/sdk/src/features/config/domain/ConfigEditor.ts index e41f7bea1..f6b9b9815 100644 --- a/packages/sdk/src/features/config/domain/ConfigEditor.ts +++ b/packages/sdk/src/features/config/domain/ConfigEditor.ts @@ -2,7 +2,6 @@ import { create } from "@bufbuild/protobuf"; import * as Protobuf from "@meshtastic/protobufs"; import { signal } from "@preact/signals-core"; import { Result } from "better-result"; -import type { ResultType } from "better-result"; import type { MeshClient } from "../../../core/client/MeshClient.ts"; import { type ReadonlySignal, @@ -236,7 +235,7 @@ export class ConfigEditor { * pair. On success the baseline is replaced with the working copy * (optimistic update); inbound config packets after commit will reconcile. */ - public async commit(): Promise> { + public async commit(): Promise> { if (!this._isDirty.peek()) return Result.ok(undefined); const beginResult = await beginEditSettings(this.client); diff --git a/packages/sdk/src/features/files/FilesClient.ts b/packages/sdk/src/features/files/FilesClient.ts index bf1c31b5d..490d7b211 100644 --- a/packages/sdk/src/features/files/FilesClient.ts +++ b/packages/sdk/src/features/files/FilesClient.ts @@ -1,5 +1,4 @@ import { Result } from "better-result"; -import type { ResultType } from "better-result"; import type { MeshClient } from "../../core/client/MeshClient.ts"; import { generatePacketId } from "../../core/identifiers/PacketId.ts"; import type { ReadonlySignal } from "../../core/signals/createStore.ts"; @@ -20,7 +19,7 @@ export class FilesClient { public async upload( filename: string, data: Uint8Array, - ): Promise> { + ): Promise> { const id = generatePacketId(); const transfer: FileTransfer = { id, @@ -44,7 +43,7 @@ export class FilesClient { public async download( filename: string, - ): Promise> { + ): Promise> { const id = generatePacketId(); const transfer: FileTransfer = { id, diff --git a/packages/sdk/src/features/nodes/NodesClient.test.ts b/packages/sdk/src/features/nodes/NodesClient.test.ts index 8d7b8170d..3edb9d9a3 100644 --- a/packages/sdk/src/features/nodes/NodesClient.test.ts +++ b/packages/sdk/src/features/nodes/NodesClient.test.ts @@ -33,7 +33,9 @@ describe("NodesClient", () => { const { transport } = createFakeTransport(); const client = new MeshClient({ transport }); - client.events.onNodeInfoPacket.dispatch(create(Protobuf.Mesh.NodeInfoSchema, { num: 100 })); + client.events.onNodeInfoPacket.dispatch( + create(Protobuf.Mesh.NodeInfoSchema, { num: 100 }), + ); client.events.onTelemetryPacket.dispatch({ id: 1, from: 100, diff --git a/packages/sdk/src/features/nodes/NodesClient.ts b/packages/sdk/src/features/nodes/NodesClient.ts index 8c59dc3bf..acff1606b 100644 --- a/packages/sdk/src/features/nodes/NodesClient.ts +++ b/packages/sdk/src/features/nodes/NodesClient.ts @@ -1,6 +1,6 @@ import { create } from "@bufbuild/protobuf"; import * as Protobuf from "@meshtastic/protobufs"; -import type { ResultType } from "better-result"; +import type { Result } from "better-result"; import type { MeshClient } from "../../core/client/MeshClient.ts"; import type { ReadonlySignal } from "../../core/signals/createStore.ts"; import type { Node } from "./domain/Node.ts"; @@ -173,31 +173,31 @@ export class NodesClient { this.errorsStore.clear(); } - public async favorite(nodeNum: number): Promise> { + public async favorite(nodeNum: number): Promise> { const result = await favoriteNode(this.client, nodeNum); if (result.status === "ok") this.patch(nodeNum, { isFavorite: true }); return result; } - public async unfavorite(nodeNum: number): Promise> { + public async unfavorite(nodeNum: number): Promise> { const result = await removeFavoriteNode(this.client, nodeNum); if (result.status === "ok") this.patch(nodeNum, { isFavorite: false }); return result; } - public async ignore(nodeNum: number): Promise> { + public async ignore(nodeNum: number): Promise> { const result = await ignoreNode(this.client, nodeNum); if (result.status === "ok") this.patch(nodeNum, { isIgnored: true }); return result; } - public async unignore(nodeNum: number): Promise> { + public async unignore(nodeNum: number): Promise> { const result = await removeIgnoredNode(this.client, nodeNum); if (result.status === "ok") this.patch(nodeNum, { isIgnored: false }); return result; } - public remove(nodeNum: number): Promise> { + public remove(nodeNum: number): Promise> { void this.repository.remove(nodeNum).catch(() => {}); this.store.delete(nodeNum); this.errorsStore.delete(nodeNum); @@ -212,7 +212,7 @@ export class NodesClient { */ public async reset( options: { keepMyNode?: boolean } = {}, - ): Promise> { + ): Promise> { const myNodeNum = this.client.device.myNodeNum.value; if (options.keepMyNode && myNodeNum !== undefined) { const me = this.store.get(myNodeNum); diff --git a/packages/sdk/src/features/nodes/application/FavoriteNodeUseCase.ts b/packages/sdk/src/features/nodes/application/FavoriteNodeUseCase.ts index 3ef4f44fa..271c92272 100644 --- a/packages/sdk/src/features/nodes/application/FavoriteNodeUseCase.ts +++ b/packages/sdk/src/features/nodes/application/FavoriteNodeUseCase.ts @@ -1,12 +1,11 @@ import { Result } from "better-result"; -import type { ResultType } from "better-result"; import type { MeshClient } from "../../../core/client/MeshClient.ts"; import { sendAdminMessage } from "../../device/infrastructure/AdminMessageSender.ts"; export async function favoriteNode( client: MeshClient, nodeNum: number, -): Promise> { +): Promise> { try { const id = await sendAdminMessage(client, { case: "setFavoriteNode", @@ -21,7 +20,7 @@ export async function favoriteNode( export async function removeFavoriteNode( client: MeshClient, nodeNum: number, -): Promise> { +): Promise> { try { const id = await sendAdminMessage(client, { case: "removeFavoriteNode", diff --git a/packages/sdk/src/features/nodes/application/IgnoreNodeUseCase.ts b/packages/sdk/src/features/nodes/application/IgnoreNodeUseCase.ts index fbe234a1c..f809cd73b 100644 --- a/packages/sdk/src/features/nodes/application/IgnoreNodeUseCase.ts +++ b/packages/sdk/src/features/nodes/application/IgnoreNodeUseCase.ts @@ -1,12 +1,11 @@ import { Result } from "better-result"; -import type { ResultType } from "better-result"; import type { MeshClient } from "../../../core/client/MeshClient.ts"; import { sendAdminMessage } from "../../device/infrastructure/AdminMessageSender.ts"; export async function ignoreNode( client: MeshClient, nodeNum: number, -): Promise> { +): Promise> { try { const id = await sendAdminMessage(client, { case: "setIgnoredNode", @@ -21,7 +20,7 @@ export async function ignoreNode( export async function removeIgnoredNode( client: MeshClient, nodeNum: number, -): Promise> { +): Promise> { try { const id = await sendAdminMessage(client, { case: "removeIgnoredNode", diff --git a/packages/sdk/src/features/nodes/application/RemoveNodeUseCase.ts b/packages/sdk/src/features/nodes/application/RemoveNodeUseCase.ts index 27dc16150..cb7d300de 100644 --- a/packages/sdk/src/features/nodes/application/RemoveNodeUseCase.ts +++ b/packages/sdk/src/features/nodes/application/RemoveNodeUseCase.ts @@ -1,12 +1,11 @@ import { Result } from "better-result"; -import type { ResultType } from "better-result"; import type { MeshClient } from "../../../core/client/MeshClient.ts"; import { sendAdminMessage } from "../../device/infrastructure/AdminMessageSender.ts"; export async function removeNodeByNum( client: MeshClient, nodeNum: number, -): Promise> { +): Promise> { try { const id = await sendAdminMessage(client, { case: "removeByNodenum", @@ -20,7 +19,7 @@ export async function removeNodeByNum( export async function resetNodes( client: MeshClient, -): Promise> { +): Promise> { try { const id = await sendAdminMessage(client, { case: "nodedbReset", diff --git a/packages/sdk/src/features/nodes/application/SetOwnerUseCase.ts b/packages/sdk/src/features/nodes/application/SetOwnerUseCase.ts index c0d2d91f5..79e051d57 100644 --- a/packages/sdk/src/features/nodes/application/SetOwnerUseCase.ts +++ b/packages/sdk/src/features/nodes/application/SetOwnerUseCase.ts @@ -1,13 +1,12 @@ import type * as Protobuf from "@meshtastic/protobufs"; import { Result } from "better-result"; -import type { ResultType } from "better-result"; import type { MeshClient } from "../../../core/client/MeshClient.ts"; import { sendAdminMessage } from "../../device/infrastructure/AdminMessageSender.ts"; export async function setOwner( client: MeshClient, owner: Protobuf.Mesh.User, -): Promise> { +): Promise> { try { const id = await sendAdminMessage(client, { case: "setOwner", diff --git a/packages/sdk/src/features/position/PositionClient.ts b/packages/sdk/src/features/position/PositionClient.ts index 197a323aa..239355660 100644 --- a/packages/sdk/src/features/position/PositionClient.ts +++ b/packages/sdk/src/features/position/PositionClient.ts @@ -1,5 +1,5 @@ import type * as Protobuf from "@meshtastic/protobufs"; -import type { ResultType } from "better-result"; +import type { Result } from "better-result"; import type { MeshClient } from "../../core/client/MeshClient.ts"; import type { ReadonlySignal } from "../../core/signals/createStore.ts"; import type { Position } from "./domain/Position.ts"; @@ -34,21 +34,19 @@ export class PositionClient { public setFixed( latitude: number, longitude: number, - ): Promise> { + ): Promise> { return setFixedPosition(this.client, latitude, longitude); } - public removeFixed(): Promise> { + public removeFixed(): Promise> { return removeFixedPosition(this.client); } - public set( - position: Protobuf.Mesh.Position, - ): Promise> { + public set(position: Protobuf.Mesh.Position): Promise> { return setPosition(this.client, position); } - public request(destination: number): Promise> { + public request(destination: number): Promise> { return requestPosition(this.client, destination); } } diff --git a/packages/sdk/src/features/position/application/PositionUseCases.ts b/packages/sdk/src/features/position/application/PositionUseCases.ts index 15f1beda1..a09aec520 100644 --- a/packages/sdk/src/features/position/application/PositionUseCases.ts +++ b/packages/sdk/src/features/position/application/PositionUseCases.ts @@ -1,7 +1,6 @@ import { create, toBinary } from "@bufbuild/protobuf"; import * as Protobuf from "@meshtastic/protobufs"; import { Result } from "better-result"; -import type { ResultType } from "better-result"; import type { MeshClient } from "../../../core/client/MeshClient.ts"; import { sendAdminMessage } from "../../device/infrastructure/AdminMessageSender.ts"; @@ -9,7 +8,7 @@ export async function setFixedPosition( client: MeshClient, latitude: number, longitude: number, -): Promise> { +): Promise> { try { const position = create(Protobuf.Mesh.PositionSchema, { latitudeI: Math.floor(latitude / 1e-7), @@ -31,7 +30,7 @@ export async function setFixedPosition( export async function removeFixedPosition( client: MeshClient, -): Promise> { +): Promise> { try { const id = await sendAdminMessage( client, @@ -50,7 +49,7 @@ export async function removeFixedPosition( export async function setPosition( client: MeshClient, position: Protobuf.Mesh.Position, -): Promise> { +): Promise> { try { const id = await client.sendPacket( toBinary(Protobuf.Mesh.PositionSchema, position), @@ -66,7 +65,7 @@ export async function setPosition( export async function requestPosition( client: MeshClient, destination: number, -): Promise> { +): Promise> { try { const id = await client.sendPacket( new Uint8Array(), diff --git a/packages/sdk/src/features/traceroute/TraceRouteClient.ts b/packages/sdk/src/features/traceroute/TraceRouteClient.ts index 8fa83afb0..dbf4dad49 100644 --- a/packages/sdk/src/features/traceroute/TraceRouteClient.ts +++ b/packages/sdk/src/features/traceroute/TraceRouteClient.ts @@ -1,4 +1,4 @@ -import type { ResultType } from "better-result"; +import type { Result } from "better-result"; import type { MeshClient } from "../../core/client/MeshClient.ts"; import type { ReadonlySignal } from "../../core/signals/createStore.ts"; import type { TraceRoute } from "./domain/TraceRoute.ts"; @@ -29,7 +29,7 @@ export class TraceRouteClient { return this.store.get(destination); } - public run(destination: number): Promise> { + public run(destination: number): Promise> { return runTraceRoute(this.client, destination); } } diff --git a/packages/sdk/src/features/traceroute/application/TraceRouteUseCase.ts b/packages/sdk/src/features/traceroute/application/TraceRouteUseCase.ts index 3703304e4..0a5d471ec 100644 --- a/packages/sdk/src/features/traceroute/application/TraceRouteUseCase.ts +++ b/packages/sdk/src/features/traceroute/application/TraceRouteUseCase.ts @@ -1,13 +1,12 @@ import { create, toBinary } from "@bufbuild/protobuf"; import * as Protobuf from "@meshtastic/protobufs"; import { Result } from "better-result"; -import type { ResultType } from "better-result"; import type { MeshClient } from "../../../core/client/MeshClient.ts"; export async function runTraceRoute( client: MeshClient, destination: number, -): Promise> { +): Promise> { try { const routeDiscovery = create(Protobuf.Mesh.RouteDiscoverySchema, { route: [], diff --git a/packages/sdk/src/shim/legacyMeshDevice.ts b/packages/sdk/src/shim/legacyMeshDevice.ts index 05ce5335f..8e1bab6c0 100644 --- a/packages/sdk/src/shim/legacyMeshDevice.ts +++ b/packages/sdk/src/shim/legacyMeshDevice.ts @@ -310,6 +310,24 @@ export class MeshDevice { }); } + public sendKeyVerification( + messageType: Protobuf.Admin.KeyVerificationAdmin_MessageType, + remoteNodenum: number, + nonce: bigint, + securityNumber?: number, + ): Promise { + const keyVerification = create(Protobuf.Admin.KeyVerificationAdminSchema, { + messageType, + remoteNodenum, + nonce, + securityNumber, + }); + return sendAdminMessage(this.client, { + case: "keyVerification", + value: keyVerification, + }); + } + public resetNodes(): Promise { return sendAdminMessage(this.client, { case: "nodedbReset", value: true }); } diff --git a/packages/transport-web-serial/src/transport.ts b/packages/transport-web-serial/src/transport.ts index 581715821..39f1e0998 100644 --- a/packages/transport-web-serial/src/transport.ts +++ b/packages/transport-web-serial/src/transport.ts @@ -6,7 +6,7 @@ import { toDeviceStream, type Transport, } from "@meshtastic/sdk"; -import { Result, type ResultType } from "better-result"; +import { Result } from "better-result"; const log = createLogger("TransportWebSerial"); @@ -70,7 +70,7 @@ export class TransportWebSerial implements Transport { */ public static async create( baudRate?: number, - ): Promise> { + ): Promise> { let port: SerialPort; try { port = await navigator.serial.requestPort(); @@ -101,7 +101,7 @@ export class TransportWebSerial implements Transport { public static async createFromPort( port: SerialPort, baudRate?: number, - ): Promise> { + ): Promise> { const prep = await TransportWebSerial.preparePort(port, baudRate ?? 115200); if (Result.isError(prep)) return Result.err(prep.error); try { @@ -120,7 +120,7 @@ export class TransportWebSerial implements Transport { private static async preparePort( port: SerialPort, baudRate: number, - ): Promise> { + ): Promise> { log.debug("preparePort: enter", { readable: !!port.readable, writable: !!port.writable, diff --git a/playwright.config.ts b/playwright.config.ts index 10a6fa90a..43b3ed80a 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -24,7 +24,10 @@ export default defineConfig({ timeout: 90_000, expect: { timeout: 15_000 }, - reporter: [["list"], ["html", { outputFolder: "e2e/.report", open: "never" }]], + reporter: [ + ["list"], + ["html", { outputFolder: "e2e/.report", open: "never" }], + ], use: { baseURL: `http://localhost:${WEB_PORT}`, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 58fc7bbbe..edbe03550 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -339,8 +339,8 @@ importers: specifier: ^2.12.1 version: 2.12.1 '@meshtastic/protobufs': - specifier: jsr:^2.7.18 - version: '@jsr/meshtastic__protobufs@2.7.20' + specifier: workspace:* + version: link:../protobufs '@preact/signals-core': specifier: ^1.14.3 version: 1.14.3