Skip to content

feat(mqtt): allow standalone mqtt_bridge as client-proxy target (#3134)#3136

Merged
Yeraze merged 2 commits into
mainfrom
fix/3134-standalone-mqtt-bridge
May 22, 2026
Merged

feat(mqtt): allow standalone mqtt_bridge as client-proxy target (#3134)#3136
Yeraze merged 2 commits into
mainfrom
fix/3134-standalone-mqtt-bridge

Conversation

@Yeraze

@Yeraze Yeraze commented May 22, 2026

Copy link
Copy Markdown
Owner

Summary

  • mqtt_bridge sources can now run without a parent mqtt_broker (issue [BUG] mqtt broker cannot be removed #3134). When config.brokerSourceId is omitted, the bridge runs as a pure upstream MQTT client — useful for monitoring a remote broker, or for serving as a target for a meshtastic_tcp source's MQTT client-proxy mqttLink.
  • Deleting an mqtt_broker no longer returns 409 Conflict when bridges depend on it (the original bug in [BUG] mqtt broker cannot be removed #3134). Instead, dependent bridges are automatically detached (brokerSourceId cleared, manager restarted) and the broker is deleted.
  • MeshtasticManager.setupMqttLink now accepts either an mqtt_broker or an mqtt_bridge as the link target. Both source types expose publish() and emit local-packet, so the existing proxy-message paths work unchanged for either.
  • UI: the device MQTT Quick configure dropdown lists brokers and bridges (tagged by type) so a user can wire a device's MQTT proxy straight to an upstream-only bridge in one click. The source-add modal makes "Parent broker" optional with an explicit "None — standalone client proxy" choice and an explanatory help line.

Why

mqtt_bridge originally required an mqtt_broker parent so it could republish upstream traffic locally and forward device traffic upstream. That coupling was the right model for a true broker-to-broker bridge, but it blocked two valid setups:

  1. Pure monitoring of an upstream Meshtastic MQTT broker (e.g. mqtt.meshtastic.org) when you have no locally-connected devices.
  2. MQTT client-proxy — a Meshtastic device with mqtt.proxy_to_client_enabled = true whose MQTT traffic should be relayed straight upstream by MeshMonitor with no embedded broker in between.

Both now work natively.

Files changed

File Change
src/server/routes/sourceRoutes.ts POST validator: brokerSourceId is optional. DELETE on a broker auto-detaches dependent bridges.
src/server/mqttBridgeManager.ts brokerSourceId typed optional; attachParentBroker() is a no-op when unset; new publish() method; emits local-packet on downlink.
src/server/meshtasticManager.ts setupMqttLink accepts mqtt_broker or mqtt_bridge as link target.
src/pages/DashboardPage.tsx Bridge form: optional broker dropdown, save config omits brokerSourceId when blank.
src/components/configuration/MQTTConfigSection.tsx Quick-configure dropdown lists brokers + bridges with type tags; proxyLinkMisconfigured accepts bridges.
src/server/routes/sourceRoutes.standaloneBridge.test.ts (new) 6 cases: standalone create paths, rejection of bad parent refs, broker delete with/without dependents.
src/server/mqttBridgeManager.test.ts 2 new cases: standalone bridge emits local-packet + publish() reaches upstream; publish() throws when disconnected.

Test plan

  • npx tsc --noEmit — clean
  • vitest run src/server — 162 files / 2796 tests pass
  • vitest run src/components src/pages — 470 tests pass
  • npm run build — clean
  • New unit tests for the standalone-bridge create path + broker-delete cascade — 6 pass
  • New integration tests for publish() + local-packet emission on a real Aedes upstream broker — 2 pass
  • Manual: create a bridge with no parent broker via the UI, verify it connects upstream and ingests
  • Manual: point a meshtastic_tcp source's mqttLink at a standalone bridge, enable proxy_to_client on the device firmware, verify device MQTT traffic flows upstream
  • Manual: delete an mqtt_broker that has dependent bridges, verify bridges keep running standalone and the broker is removed

🤖 Generated with Claude Code

Closes #3134. `mqtt_bridge` sources no longer require a parent
`mqtt_broker` — when `config.brokerSourceId` is omitted, the bridge
runs as a pure upstream MQTT client. Deleting an `mqtt_broker` now
detaches its dependent bridges (clears their `brokerSourceId`) instead
of returning 409, and a `meshtastic_tcp` source's `mqttLink` can now
target either a broker or a (standalone) bridge for the device-side
MQTT client-proxy use case. UI updated so both source types appear in
the per-device "Quick configure" dropdown.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented May 22, 2026

Copy link
Copy Markdown

Claude finished @Yeraze's task —— View job


Code Review Complete ✅

I've completed a comprehensive review of PR #3136. This is a well-implemented feature that adds valuable flexibility to the MQTT bridge functionality. Here are my findings:

Strengths

Architecture & Design:

  • Clean implementation of optional broker coupling - making brokerSourceId optional is elegant and backward-compatible
  • Consistent interface - both mqtt_broker and mqtt_bridge expose the same publish() method and local-packet events, allowing seamless substitution in MeshtasticManager.setupMqttLink()
  • Graceful cascade deletion - when a broker is deleted, dependent bridges automatically become standalone rather than breaking

Code Quality:

  • Comprehensive test coverage - both unit tests and integration tests with real Aedes broker
  • Excellent documentation - detailed JSDoc comments explain the standalone mode rationale
  • Proper error handling - publish() throws meaningful errors when disconnected
  • Type safety - proper TypeScript typing throughout

Security & Robustness:

  • Proper validation in sourceRoutes.ts:196-206 - validates brokerSourceId when present, allows omission
  • Safe credential handling - no changes to existing credential stripping logic
  • Echo suppression maintained - existing loop prevention works for both modes

🔍 Minor Observations

Performance:

  • The cascade deletion logic (sourceRoutes.ts:543-561) does multiple database calls in sequence, but this is acceptable for an infrequent operation
  • No performance impact on normal operation - standalone bridges have slightly less overhead (no parent listener)

Edge Cases Handled:

  • ✅ Empty/null/undefined brokerSourceId treated consistently as standalone
  • ✅ Bridge continues working when parent broker stops
  • ✅ UI properly shows both broker and bridge options with type tags
  • ✅ Quick-configure dropdown includes both source types

🎯 UI/UX Improvements

Dashboard Form (DashboardPage.tsx):

  • The help text "None — standalone client proxy" clearly explains the option
  • Conditional omission of brokerSourceId from config when blank is correct

MQTT Config Section:

  • Smart filtering includes both mqtt_broker and mqtt_bridge in proxy targets
  • The proxyLinkMisconfigured logic correctly accepts bridges as valid targets

🧪 Test Coverage Analysis

New Test File (sourceRoutes.standaloneBridge.test.ts):

  • ✅ 6 comprehensive test cases covering create/validate/delete scenarios
  • ✅ Tests both success and failure paths
  • ✅ Validates cascade deletion behavior

Bridge Manager Tests (mqttBridgeManager.test.ts):

  • ✅ Real Aedes broker integration tests
  • ✅ Verifies publish() reaches upstream
  • ✅ Confirms local-packet emission for client-proxy use

🔒 Security Assessment

  • No security concerns - maintains existing permission checks
  • Credential handling unchanged - existing password stripping logic preserved
  • Input validation proper - validates broker references when provided
  • No privilege escalation - standalone mode doesn't bypass any permissions

📈 Performance & Scalability

  • Reduced overhead for standalone bridges (no parent broker listener)
  • Database queries optimized - cascade deletion uses efficient filtering
  • Memory usage comparable - similar footprint to existing bridges

Recommendation: ✅ APPROVE

This PR successfully addresses issue #3134 with a well-designed solution that:

  1. Enables monitoring of remote MQTT brokers without local device connections
  2. Provides a clean client-proxy target for meshtastic_tcp sources
  3. Maintains backward compatibility
  4. Includes comprehensive tests and documentation

The implementation follows MeshMonitor's architectural patterns and coding standards. No blocking issues identified.

Rewrites docs/features/mqtt-broker.md to cover both source types in
depth: what each one does, when to pick each, and a side-by-side feature
matrix (broker vs attached bridge vs standalone bridge). Adds a
use-case recipes section, expands the device-reach table to three
paths now that standalone bridges can serve as client-proxy targets,
and updates the broker-delete troubleshooting note to reflect the
auto-detach behavior. Nav and landing-page card retitled to
"Embedded MQTT Broker & Bridge". Part of #3134.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented May 22, 2026

Copy link
Copy Markdown

Claude finished @Yeraze's task —— View job


Code Review Complete ✅

I've completed a comprehensive review of PR #3136. This is a well-implemented feature that adds valuable flexibility to the MQTT bridge functionality. Here are my findings:

Strengths

Architecture & Design:

  • Clean implementation of optional broker coupling - making brokerSourceId optional is elegant and backward-compatible
  • Consistent interface - both mqtt_broker and mqtt_bridge expose the same publish() method and local-packet events, allowing seamless substitution in MeshtasticManager.setupMqttLink()
  • Graceful cascade deletion - when a broker is deleted, dependent bridges automatically become standalone rather than breaking

Code Quality:

  • Comprehensive test coverage - both unit tests and integration tests with real Aedes broker
  • Excellent documentation - detailed JSDoc comments explain the standalone mode rationale
  • Proper error handling - publish() throws meaningful errors when disconnected
  • Type safety - proper TypeScript typing throughout

Security & Robustness:

  • Proper validation in sourceRoutes.ts:196-206 - validates brokerSourceId when present, allows omission
  • Safe credential handling - no changes to existing credential stripping logic
  • Echo suppression maintained - existing loop prevention works for both modes

🔍 Minor Observations

Performance:

  • The cascade deletion logic (sourceRoutes.ts:543-561) does multiple database calls in sequence, but this is acceptable for an infrequent operation
  • No performance impact on normal operation - standalone bridges have slightly less overhead (no parent listener)

Edge Cases Handled:

  • ✅ Empty/null/undefined brokerSourceId treated consistently as standalone
  • ✅ Bridge continues working when parent broker stops
  • ✅ UI properly shows both broker and bridge options with type tags
  • ✅ Quick-configure dropdown includes both source types

🎯 UI/UX Improvements

Dashboard Form (DashboardPage.tsx):

  • The help text "None — standalone client proxy" clearly explains the option
  • Conditional omission of brokerSourceId from config when blank is correct

MQTT Config Section:

  • Smart filtering includes both mqtt_broker and mqtt_bridge in proxy targets
  • The proxyLinkMisconfigured logic correctly accepts bridges as valid targets

🧪 Test Coverage Analysis

New Test File (sourceRoutes.standaloneBridge.test.ts):

  • ✅ 6 comprehensive test cases covering create/validate/delete scenarios
  • ✅ Tests both success and failure paths
  • ✅ Validates cascade deletion behavior

Bridge Manager Tests (mqttBridgeManager.test.ts):

  • ✅ Real Aedes broker integration tests
  • ✅ Verifies publish() reaches upstream
  • ✅ Confirms local-packet emission for client-proxy use

🔒 Security Assessment

  • No security concerns - maintains existing permission checks
  • Credential handling unchanged - existing password stripping logic preserved
  • Input validation proper - validates broker references when provided
  • No privilege escalation - standalone mode doesn't bypass any permissions

📈 Performance & Scalability

  • Reduced overhead for standalone bridges (no parent broker listener)
  • Database queries optimized - cascade deletion uses efficient filtering
  • Memory usage comparable - similar footprint to existing bridges

Recommendation: ✅ APPROVE

This PR successfully addresses issue #3134 with a well-designed solution that:

  1. Enables monitoring of remote MQTT brokers without local device connections
  2. Provides a clean client-proxy target for meshtastic_tcp sources
  3. Maintains backward compatibility
  4. Includes comprehensive tests and documentation

The implementation follows MeshMonitor's architectural patterns and coding standards. No blocking issues identified.


@Yeraze Yeraze merged commit 851aba8 into main May 22, 2026
20 checks passed
@Yeraze Yeraze deleted the fix/3134-standalone-mqtt-bridge branch May 22, 2026 13:38
Yeraze added a commit that referenced this pull request May 22, 2026
…ish (#3145)

Version bump and CHANGELOG entry for 4.6.5. Highlights:

- #3143 MQTT Bridge mirror dashboard (read-only Open button on bridge
  cards; suppresses every TX surface; MqttBridgeManager now exposes the
  Meshtastic-shaped methods the consolidated /api/poll + /api/connection
  endpoints expect).
- #3142 MeshCore Telemetry dashboard tab via source-agnostic Dashboard.
- #3141 MeshCore: preserve LPP channel byte in remote telemetry rows.
- #3140 DM: skip PKI flag when keyMismatchDetected.
- #3138 docs reorganization.
- #3137 unified: merge nodes across sources so labels show in the
  unified Nodes view.
- #3136 mqtt: allow standalone mqtt_bridge as client-proxy target.

CLAUDE.md version line updated to 4.6.5.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.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