Skip to content

feat: telemetry parsing, direct messages, danger zone, and UX improvements#6

Merged
Yeraze merged 3 commits into
mainfrom
feat/telemetry-dm-fixes-and-danger-zone
Sep 27, 2025
Merged

feat: telemetry parsing, direct messages, danger zone, and UX improvements#6
Yeraze merged 3 commits into
mainfrom
feat/telemetry-dm-fixes-and-danger-zone

Conversation

@Yeraze

@Yeraze Yeraze commented Sep 27, 2025

Copy link
Copy Markdown
Owner

Summary

  • Fix environment telemetry parsing to properly capture temperature, humidity, and pressure data
  • Prevent telemetry data pollution from NodeInfo packets during refresh
  • Isolate direct messages from channels with proper channel=-1 handling
  • Preserve node names across non-NODEINFO packet updates
  • Add Settings danger zone for data purge operations
  • Improve UI notifications and reduce console spam

Key Changes

Telemetry Fixes

  • Fix environment telemetry parsing: Access deviceMetrics, environmentMetrics, powerMetrics fields directly instead of using incorrect variant.case structure
  • Stop telemetry pollution from NodeInfo: Only save telemetry from TELEMETRY_APP packets, not from node refresh packets
  • Preserve current node states: Battery/voltage displayed in UI while historical data is properly tracked

Direct Message Isolation

  • Channel -1 for DMs: Direct messages (non-broadcast) now use channel=-1 to separate them from channel messages
  • Filter channel list: Channel -1 no longer appears in the Channels tab
  • Proper DM retrieval: getDMMessages now explicitly filters for channel=-1
  • Better logging: Distinguish between direct and channel messages in logs

Node Name Preservation

  • Prevent name overwrites: Only update technical fields (SNR/RSSI/lastHeard) from MeshPackets
  • Names from NODEINFO only: Node names like "Yeraze Mobile" only updated from NODEINFO packets
  • Traceroute handling: Check if node exists before updating to avoid overwriting names

Danger Zone (Settings)

  • Erase Node List: Purges all nodes + traceroutes, triggers refresh
  • Purge Telemetry: Removes all historical telemetry data
  • Purge Messages: Deletes all channel and DM messages
  • Each action requires confirmation with impact explanation
  • Styled with red borders and warning indicators

UX Improvements

  • Remove console spam: Eliminated constant "Map render" and "Node X hops" logs
  • DM notifications: Messages tab now shows unread badge for direct messages (channel -1)
  • Channels tab badge fix: Excludes DM unread count from Channels tab badge
  • Auto-clear DM badge: Switching to Messages tab clears the DM unread count

Test Plan

  • ✅ Verify environment telemetry appears in graphs for nodes like "KE4JQ G2 Base"
  • ✅ Confirm direct messages only appear in DM conversations, not Primary channel
  • ✅ Check node names persist correctly (e.g., "Yeraze Mobile" doesn't revert to ID)
  • ✅ Test danger zone buttons with confirmation dialogs
  • ✅ Verify console is clean without constant logging
  • ✅ Confirm DM notification badge works correctly

🤖 Generated with Claude Code

Yeraze and others added 3 commits September 27, 2025 17:34
- Fix environment telemetry parsing by accessing fields directly (deviceMetrics, environmentMetrics, powerMetrics) instead of variant.case structure
- Prevent telemetry data pollution from NodeInfo packets during node refresh - only save telemetry from TELEMETRY_APP packets
- Fix direct messages appearing in Primary channel by using channel=-1 for non-broadcast messages
- Preserve node names by only updating technical fields (SNR/RSSI/lastHeard) from MeshPackets, names only updated from NODEINFO packets

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
Add a "Danger Zone" section to the Settings tab with three destructive operations:
- Erase Node List: Purges all nodes and traceroutes, triggers refresh
- Purge Telemetry: Removes all historical telemetry data
- Purge Messages: Deletes all channel and direct messages

Each operation:
- Shows confirmation dialog with impact explanation
- Requires explicit user confirmation
- Displays success/error feedback
- Refreshes page when needed (nodes/messages)

Also fix channel -1 filtering:
- Direct messages (channel -1) no longer appear in Channels tab
- getDMMessages now explicitly filters for channel -1

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Remove constant "Map render" console logs
- Remove "Node X hops" console logs
- Fix unread count badge on Channels tab to exclude DM channel (-1)
- Add unread count badge to Messages tab for direct messages
- Clear DM unread count when switching to Messages tab

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
@claude

claude Bot commented Sep 27, 2025

Copy link
Copy Markdown
Contributor

Claude finished @Yeraze's task —— View job


PR Code Review ✅

I've completed my comprehensive review of this PR. Here's my analysis:

Overall Assessment

This is a well-structured PR that addresses several important issues in the Meshtastic mesh monitoring system. The changes are focused and implement proper isolation between different types of data and messages.

Code Quality & Best Practices ⭐⭐⭐⭐⭐

Strengths:

  • Excellent separation of concerns: Telemetry data is now properly isolated from NodeInfo packets
  • Proper channel handling: Direct messages are correctly isolated using channel -1
  • Good error handling: Comprehensive try-catch blocks with descriptive logging
  • Clear documentation: Comments explain the reasoning behind changes
  • Consistent naming: Variables and functions follow established patterns

Areas for improvement:

  • Console logging: Could be reduced in production (though noted in PR description)
  • TypeScript types: Some any types could be more specific
  • Function length: Some functions in meshtasticManager.ts are quite long and could benefit from extraction

Security Assessment 🔒

Danger Zone Implementation:

The danger zone operations in App.tsx and server.ts are properly implemented:

  • Confirmation dialogs with clear impact descriptions
  • Server-side validation with explicit endpoints
  • No client-side only operations - all dangerous operations go through the server
  • Clear user feedback about what will be deleted
  • Appropriate styling with red borders and warning indicators

Security Concerns Found:

Environment variable exposure: In server.ts:494-496, the server exposes the Meshtastic node IP through /api/config. While this may be intentional for configuration, consider if this should be restricted.

// Potentially sensitive endpoint
app.get('/api/config', (_req, res) => {
  res.json({
    meshtasticNodeIp: process.env.MESHTASTIC_NODE_IP || '192.168.1.100', // Exposes internal network info
    meshtasticUseTls: process.env.MESHTASTIC_USE_TLS === 'true'
  });
});

Potential Bugs 🐛

Critical Issues:

  1. Missing validation in purge endpoints (server.ts:585-615): The purge endpoints don't validate request body or require additional confirmation.

  2. Race condition potential in App.tsx:484-520: The unread count logic could have race conditions when messages arrive during channel switching.

  3. Database transaction safety (database.ts:895-909): Purge operations should use transactions to ensure atomicity.

Minor Issues:

  1. BigInt handling: The JSON replacer override in server.ts:22-29 globally modifies JSON.stringify which could affect other parts of the application.

  2. Hardcoded timeout values: Several magic numbers for timeouts that should be configurable.

Performance Considerations ⚡

Positive aspects:

  • ✅ Proper database indexing
  • ✅ Efficient message filtering
  • ✅ Limited query results with proper pagination

Potential improvements:

  1. Database queries: Some queries in database.ts could benefit from prepared statements being cached
  2. Memory usage: Large message arrays could grow unbounded in the frontend
  3. Polling frequency: 5-second intervals might be excessive for large networks

Technical Implementation Analysis 🔧

Telemetry Parsing Fix (meshtasticManager.ts:832-936):

Excellent fix - The change from variant.case structure to direct field access is correct:

// Before (incorrect):
if (telemetry.variant?.case === 'deviceMetrics') 

// After (correct):
if (telemetry.deviceMetrics)

Direct Message Isolation (meshtasticManager.ts:693-695):

Well implemented - Using channel -1 for DMs is a clean solution:

const isDirectMessage = toNum !== 4294967295;
const channelIndex = isDirectMessage ? -1 : (meshPacket.channel !== undefined ? meshPacket.channel : 0);

Node Name Preservation:

Good approach - Only updating technical fields from MeshPackets while preserving names from NODEINFO packets prevents data corruption.

Test Coverage 📋

Missing test coverage:

  • ❌ No automated tests for telemetry parsing changes
  • ❌ No tests for DM isolation logic
  • ❌ No tests for danger zone operations
  • ❌ No integration tests for the complete message flow

Recommended tests:

  1. Unit tests for telemetry parsing with different protobuf structures
  2. Integration tests for message channel routing
  3. Security tests for purge operations
  4. Performance tests for large node networks

Recommendations 📝

Immediate fixes needed:

  1. Add request validation to purge endpoints
  2. Wrap purge operations in database transactions
  3. Consider restricting the /api/config endpoint

Future improvements:

  1. Add comprehensive test suite
  2. Implement configurable timeouts
  3. Add rate limiting to prevent API abuse
  4. Consider using environment-specific logging levels

Verdict ✅

This PR addresses critical functionality issues and is ready for merge with the immediate security fixes noted above. The telemetry parsing and DM isolation improvements are solid and will significantly improve the application's reliability.

Recommendation: Merge after addressing the purge endpoint validation concern.

@Yeraze Yeraze merged commit 18c3fcf into main Sep 27, 2025
1 check passed
@Yeraze Yeraze deleted the feat/telemetry-dm-fixes-and-danger-zone branch September 27, 2025 21:57
TheWISPRer pushed a commit to TheWISPRer/meshmonitor that referenced this pull request Jun 20, 2026
- Mark messages played from the delivery-success callback, not at enqueue:
  handleCommand returns { responses, playOnDelivery }; a dropped body DM now
  leaves the message pending instead of losing it. (Yeraze#1)
- Wire purgeExpired into databaseMaintenanceService so expired rows are
  reclaimed daily (purgeExpired returns a count). (Yeraze#2)
- Count the per-recipient cap across the recipient's identity forms via an
  injected node resolver, so it can't be bypassed by addressing one node by
  several name forms. (Yeraze#3)
- Mailbox bypasses the per-node cooldown (interactive flow). (Yeraze#4)
- inbox play <sender> filter matches !hex/node-num forms too. (Yeraze#5)
- Non-DM commands return [] (no unsolicited DM). (Yeraze#6)
- inbox delete returns the same response for not-yours vs non-existent ids
  (no id enumeration). (Yeraze#7)
- Wrap the mailbox dispatch in try/catch like the script branch. (Yeraze#8)
- Remove the command-prefix tolerance: canonical msg/inbox only. (Yeraze#9)
- Use shared nodeIdHex (unsigned coerce) for the mailbox log target. (Yeraze#10)

Docs + tests updated to match.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Yeraze added a commit that referenced this pull request Jun 20, 2026
…gaps (#3578)

Addresses Claude Code Review findings on PR #3578:
- Sanitize device-controlled message before logging/forwarding: strip control
  chars (log-injection defense) and bound length to 500 (#9, #10).
- Widen protected-cap regex to {1,8} hex digits so a short node id still
  reconciles (#4).
- Exclude clientNotification from the unknown-FromRadio debug JSON.stringify
  dump (#3).
- Drop the redundant `&& this.sourceId` guard (always set) and comment why the
  toast still fires when the DB revert fails (#2, #5).
- Frontend: name the level magic numbers and note they mirror the backend
  NOTIFICATION_LEVEL (#1).
- Tests: sanitizer (control chars/whitespace/truncation/empty), short-hex-id
  parse, and the DB-revert-failure path (#6).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011JEaCGwY9Wz8jeV4e22GW4
Yeraze added a commit that referenced this pull request Jun 20, 2026
…8 favorite/ignore cap (#3548) (#3578)

* feat(notifications): surface device ClientNotifications + handle firmware 2.8 favorite/ignore cap (#3548)

MeshMonitor decoded FromRadio.ClientNotification (mesh.proto field 16) and
dropped it. Surface these device-originated warnings/errors as toasts, and
handle the firmware 2.8 protected-node-cap refusal for Set Favorite / Ignore.

Backend:
- clientNotificationPolicy.ts (pure/testable): suppression patterns (recurring
  power-save "sleeping for N interval" INFO + key-verification variants + empty
  messages), the 2.8 protected-node-cap refusal parser, level->severity mapping,
  and a per-source ToastThrottle that dedupes identical messages within a window.
- meshtasticProtobufService.ts: add the clientNotification dispatch branch (was
  falling through to the generic catch-all).
- dataEventEmitter.ts: client-notification event type + emitClientNotification().
- meshtasticManager.ts: handleClientNotification() reverts the optimistic
  favorite/ignore flag and re-broadcasts the node when the device refuses at the
  protected-node cap, then applies the suppression/throttle policy and emits the
  toast event. Source-scoped throughout.

Frontend:
- DeviceNotificationToaster.tsx: listens for client-notification inside
  ToastProvider, maps level->severity, shows the toast. Wired into App.tsx.
- WS forwarding + per-source room filtering needed no changes.

Scope: the 2.8 NodeDB warm-tier restructure and the on-disk snr_q4 field do NOT
affect the over-the-air wire MeshMonitor reads. OTA NodeInfo.snr stays a float in
dB; no protobuf/decode change. A regression test guards this. The cap-refusal
warning is only emitted by firmware for the locally-connected node (from == 0).

Tests (20 new, full suite green): policy unit tests, manager handler tests
(reconciliation/suppression/throttle, per-source), and protobuf dispatch + SNR
float guard.

Docs: FAQ (node count + blocked-node 2.8 behavior; device-notification toasts),
IgnoredNodesSection inline help, CHANGELOG, and the support plan dev-note.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011JEaCGwY9Wz8jeV4e22GW4

* address review: sanitize device notification text, robustness + test gaps (#3578)

Addresses Claude Code Review findings on PR #3578:
- Sanitize device-controlled message before logging/forwarding: strip control
  chars (log-injection defense) and bound length to 500 (#9, #10).
- Widen protected-cap regex to {1,8} hex digits so a short node id still
  reconciles (#4).
- Exclude clientNotification from the unknown-FromRadio debug JSON.stringify
  dump (#3).
- Drop the redundant `&& this.sourceId` guard (always set) and comment why the
  toast still fires when the DB revert fails (#2, #5).
- Frontend: name the level magic numbers and note they mirror the backend
  NOTIFICATION_LEVEL (#1).
- Tests: sanitizer (control chars/whitespace/truncation/empty), short-hex-id
  parse, and the DB-revert-failure path (#6).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011JEaCGwY9Wz8jeV4e22GW4

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
TheWISPRer pushed a commit to TheWISPRer/meshmonitor that referenced this pull request Jun 21, 2026
- Mark messages played from the delivery-success callback, not at enqueue:
  handleCommand returns { responses, playOnDelivery }; a dropped body DM now
  leaves the message pending instead of losing it. (Yeraze#1)
- Wire purgeExpired into databaseMaintenanceService so expired rows are
  reclaimed daily (purgeExpired returns a count). (Yeraze#2)
- Count the per-recipient cap across the recipient's identity forms via an
  injected node resolver, so it can't be bypassed by addressing one node by
  several name forms. (Yeraze#3)
- Mailbox bypasses the per-node cooldown (interactive flow). (Yeraze#4)
- inbox play <sender> filter matches !hex/node-num forms too. (Yeraze#5)
- Non-DM commands return [] (no unsolicited DM). (Yeraze#6)
- inbox delete returns the same response for not-yours vs non-existent ids
  (no id enumeration). (Yeraze#7)
- Wrap the mailbox dispatch in try/catch like the script branch. (Yeraze#8)
- Remove the command-prefix tolerance: canonical msg/inbox only. (Yeraze#9)
- Use shared nodeIdHex (unsigned coerce) for the mailbox log target. (Yeraze#10)

Docs + tests updated to match.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Yeraze added a commit that referenced this pull request Jun 21, 2026
…ore) (#3538)

* feat: add Dead Drop / Mailbox auto-responder (async message store)

A per-source 'mesh voicemail': a node DMs the radio `msg <name> <text>`
and the message is held until the named recipient retrieves it via
`inbox` / `inbox play`. Implemented as a new auto-responder
responseType ('mailbox'), reusing the existing DM-gating, per-node
cooldown, param extraction, and per-source scoping.

- DB: dead_drop_messages table (SQLite/PostgreSQL/MySQL) + migration 092
- Repository (Drizzle-only) + DatabaseService.deadDrop accessor
- DeadDropService: command brain (store/inbox/play[/sender]/delete/clear,
  180-byte cap, per-recipient & per-sender caps, 7-day expiry, batch play)
- meshtasticManager: 'mailbox' responseType dispatch branch
- UI: 'Mailbox' response type option + guidance in the auto-responder editor
- Tests: 34 (migration registry, repository perSource, service)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(dead-drop): add Mailbox option to the Add-Trigger form select

The Mailbox response type was only added to the per-trigger edit view
(TriggerItem); the separate Add-Trigger form in AutoResponderSection had
its own type <select> (Text/HTTP/Script) that was missed, so new mailbox
triggers couldn't be created from the UI.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(dead-drop): don't require response text to add a Mailbox trigger

The Add button's disabled gate required a non-empty response field for
all types; Mailbox has no response, so the button stayed greyed out.
Exempt mailbox from the response-required check (matches validateResponse).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(dead-drop): accept mailbox responseType in settings save validation

The settings-save validator required a non-empty response for every
trigger and only allowed text/http/script responseTypes, so saving a
Mailbox trigger failed with a generic 400. Exempt mailbox from the
response-required check and add it to the responseType allowlist.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* test(dead-drop): settings-save accepts mailbox triggers without response text

Regression coverage for the mailbox responseType in the autoResponderTriggers
validator: a mailbox trigger with empty response now saves (200), while
non-mailbox empty responses and unknown responseTypes still 400.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(dead-drop): tolerate optional command keyword prefix

The mailbox service parsed hardcoded msg/inbox, but a trigger configured with
prefixed keywords (e.g. betamsg/betainbox, to coexist with another responder
already using msg/inbox) would fire the handler yet fall through to help. Strip
an optional non-space prefix from the leading verb so prefixed keywords parse
correctly; no-op for plain msg/inbox. Caught by live over-the-air testing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs(dead-drop): add Mailbox feature docs + live testing brief

- automation.md: Mailbox response type + 'Mailbox (Dead Drop)' section
  (commands, recipient matching, delivery format, limits, command-prefix
  coexistence, configuration).
- dev-notes/DEAD_DROP_TESTING.md: architecture, automated coverage, and the
  over-the-air validation results (ALTO MF / ALTO LF / ZN Office).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(dead-drop): register dead_drop_messages in migrate-db table lists

The cross-database migrate-db CLI tracks every schema table in TABLE_ORDER /
SKIP_TABLES; migrationTables.test.ts fails if a new schema table isn't listed.
Add dead_drop_messages to TABLE_ORDER and SOURCE_SCOPED_TABLES (it carries a
sourceId, like auto_favorite_targets). Caught by the full Vitest suite.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(dead-drop): address PR review feedback

- Mark messages played from the delivery-success callback, not at enqueue:
  handleCommand returns { responses, playOnDelivery }; a dropped body DM now
  leaves the message pending instead of losing it. (#1)
- Wire purgeExpired into databaseMaintenanceService so expired rows are
  reclaimed daily (purgeExpired returns a count). (#2)
- Count the per-recipient cap across the recipient's identity forms via an
  injected node resolver, so it can't be bypassed by addressing one node by
  several name forms. (#3)
- Mailbox bypasses the per-node cooldown (interactive flow). (#4)
- inbox play <sender> filter matches !hex/node-num forms too. (#5)
- Non-DM commands return [] (no unsolicited DM). (#6)
- inbox delete returns the same response for not-yours vs non-existent ids
  (no id enumeration). (#7)
- Wrap the mailbox dispatch in try/catch like the script branch. (#8)
- Remove the command-prefix tolerance: canonical msg/inbox only. (#9)
- Use shared nodeIdHex (unsigned coerce) for the mailbox log target. (#10)

Docs + tests updated to match.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs(dead-drop): recommend enabling Verify response on the mailbox trigger

Played-state is committed from the delivery-success callback; with Verify
response off (maxAttempts=1) a single unacked send could mark a voicemail
played on transmit. Document enabling Verify response so undelivered bodies
resurface. (PR #3538 review follow-up)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: chrisn <chrisn@DebDev1.corp.tlclocal.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Randall Hand <randall.hand@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant