Skip to content

Conversation

haslinghuis
Copy link
Member

@haslinghuis haslinghuis commented Oct 7, 2025

Purpose

This PR comprehensively fixes port event handling across the configurator, addressing several critical issues with connection management, event processing, and error recovery. The changes improve reliability when connecting to flight controllers, handling device removal, and managing concurrent connection attempts.

Key Issues Fixed

1. MSP Callback Handling with CRC Errors

Previously, callbacks were not notified when CRC errors occurred, leaving the application in an inconsistent state. Now callbacks receive the error flag along with preserved data, allowing proper error handling.

2. Port Event Debouncing and Race Conditions

The firmware flasher lacked proper debouncing for rapid port change events, leading to multiple simultaneous connection attempts. Implemented debouncing (100ms) and proper connection state guards.

3. WebSerial Connection Recovery

When a port was already open (e.g., from a previous failed connection), the configurator would fail to connect. Added recovery logic to attach to already-open ports by acquiring reader/writer streams.

4. Serial Connection Concurrency

Concurrent connection attempts to the same port could cause race conditions. Implemented proper queueing so the second caller waits for the first connection to complete.

5. Reconnection Logic

Improved handling of reconnection requests - if already connected to the requested port, the connection is preserved. If connected to a different port, proper disconnect/reconnect sequence is followed.

6. AutoDetect Preservation

Fixed AutoDetect to avoid disconnecting when a valid connection already exists, preventing unnecessary reconnection cycles.

Summary of Changes

Core Files Modified

src/js/msp/MSPHelper.js

  • Modified process_data to always invoke callbacks, even with CRC errors
  • Callbacks now receive data with crcError flag for proper error handling
  • Wrapped callback invocations in try/catch for robustness

src/js/tabs/firmware_flasher.js

  • Added debouncing constants: PORT_CHANGE_DEBOUNCE_MS (100ms), AUTO_DETECT_DELAY_MS
  • Exposed public event handlers: detectedUsbDevice, detectedSerialDevice, onPortChange, onDeviceRemoved
  • Implemented debounced port change handling
  • Added auto-detect on tab activation
  • Enhanced cleanup to clear timers and unregister handlers

src/js/protocols/WebSerial.js

  • Centralized logging via new logger module
  • Added recovery path for already-open ports (InvalidStateError handling)
  • Acquires reader/writer on already-open ports and resumes operation
  • Enhanced error logging throughout connection lifecycle

src/js/serial.js

  • Added _handleReconnection method for smart reconnection logic
  • Implemented connection queueing for concurrent attempts
  • Added waiting mechanism (up to 5s) for in-flight connections
  • Enhanced error handling with proper state cleanup
  • Modified selectProtocol signature (removed forceDisconnect parameter)

src/js/utils/AutoDetect.js

  • Modified verifyBoard to preserve existing connections instead of disconnecting
  • Only logs warning when connection already exists

src/js/logger.js (new)

  • Test-friendly logger wrapper with info, warn, error methods
  • Facilitates better testing by allowing logger mocking

Test Coverage

test/js/MSPHelper.test.js

  • Updated test descriptions and expectations for CRC error handling
  • Verified callbacks receive crcError flag and preserved data

test/js/firmware_flasher.test.js (new)

  • Tests debounce logic for rapid port changes
  • Verifies USB/serial device detection handlers
  • Tests cleanup behavior (timer clearing, handler unregistration)

test/js/serial_connect.test.js (new)

  • Tests concurrent connection queueing
  • Verifies connection failure handling and state resets
  • Tests disconnect/reconnect flows
  • Uses fake protocols to simulate various scenarios

test/js/webserial.test.js (new)

  • Tests recovery path for already-open ports
  • Verifies failure handling when recovery lacks reader/writer
  • Mocks WebSerial API for isolated testing

Testing Instructions

Manual Testing

  1. Basic Connection Flow

    • Connect a flight controller via USB
    • Verify auto-detection works and board connects successfully
    • Navigate to various tabs and confirm stable connection
  2. Rapid Port Changes

    • Connect and disconnect USB cable rapidly (3-4 times in quick succession)
    • Verify configurator handles changes gracefully without multiple connection attempts
    • Check console for no error spam
  3. Device Removal During Operation

    • Connect to a board
    • Navigate to a tab (e.g., PID Tuning)
    • Unplug the USB cable
    • Verify clean disconnection and appropriate user feedback
    • Reconnect and verify connection resumes properly
  4. Firmware Flasher

    • Go to Firmware Flasher tab
    • Connect board in DFU mode
    • Verify auto-selection of port
    • Flash firmware and verify successful completion
    • Test board detection after flash completes
  5. Concurrent Connection Attempts (requires timing)

    • In console, try rapid serial.connect calls
    • Verify no race conditions or duplicate connections
  6. WebSerial Recovery (Chrome/Edge only)

    • Connect to board
    • Force-close browser (don't disconnect properly)
    • Reopen configurator
    • Verify connection can be re-established

Automated Testing

Run the test suite:

yarn run vitest run

All new and existing tests should pass.

Related Issues

Potentially fixes #4595 (ports tab issues in latest Chrome/Edge)

Breaking Changes

  • Serial.selectProtocol() signature changed - removed forceDisconnect parameter (internal API)

Notes

  • Debounce timing (100ms) is configurable via PORT_CHANGE_DEBOUNCE_MS constant
  • Logger module enables better testability across serial/WebSerial code
  • Comprehensive test coverage prevents regression
  • All changes maintain backward compatibility for public APIs

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

Summary by CodeRabbit

  • New Features

    • Exposed firmware flasher handlers and public APIs for start/clear operations; auto-detects devices after tab activation with debounce.
  • Improvements

    • Centralized logger replaces console noise for serial and flashing.
    • More resilient WebSerial recovery when ports are already open.
    • Smarter connection sequencing to avoid unnecessary disconnects.
  • Bug Fixes

    • Callbacks now notified even when CRC/data errors occur (data preserved).
    • Cleans up timers/handlers on teardown to prevent post-cleanup actions.
  • Tests

    • Added/updated test suites covering debounce, reconnection, WebSerial recovery, and callback behavior.
      <!-- end of auto-generated comment: release notes by coderabbit.ai -->

Summary by CodeRabbit

  • New Features

    • Exposed firmware flasher controls and handlers; debounce and delayed auto-detect for port changes.
  • Improvements

    • Centralized logger usage and richer connection/recovery handling for serial and WebSerial paths.
    • MSP callbacks now always notify callers and include crcError metadata while preserving payload shape.
    • Auto-detect lifecycle and cleanup unified for more reliable teardown.
  • Bug Fixes

    • Prevents unintended reopening of the firmware flasher tab on blocked tab switches.
  • Tests

    • New/expanded tests for flasher, serial connect, WebSerial, and MSP behaviors.

@haslinghuis haslinghuis added this to the 2025.12 milestone Oct 7, 2025
@haslinghuis haslinghuis self-assigned this Oct 7, 2025
@haslinghuis haslinghuis moved this to App in 2025.12.0 Oct 7, 2025
Copy link
Contributor

coderabbitai bot commented Oct 7, 2025

Walkthrough

Multiple modules updated: MSP callbacks now run even on CRC errors; firmware_flasher gained debounced port-change handlers and public APIs; WebSerial uses a logger and adds recovery on InvalidStateError; Serial adds reconnection/wait logic; AutoDetect.verifyBoard became async with centralized cleanup; tests added/updated.

Changes

Cohort / File(s) Summary
MSP callback behavior
src/js/msp/MSPHelper.js, test/js/MSPHelper.test.js
Always invoke callbacks even when crcError is true; preserve data and compute length as data ? data.byteLength : 0; wrap callback in try/catch; tests updated to assert crcError propagation and data preservation.
Firmware flasher handlers & debounce
src/js/tabs/firmware_flasher.js, test/js/firmware_flasher.test.js
Add debounce constants and public fields (portChangeTimer, logHead); expose handlers (detectedUsbDevice, detectedSerialDevice, onPortChange, onDeviceRemoved), startFlashing, clearBufferedFirmware; debounce port-change verification, auto-detect after activation, and unregister/clear timer on cleanup.
WebSerial recovery & centralized logging
src/js/protocols/WebSerial.js, src/js/logger.js, test/js/webserial.test.js
Introduce logger wrapper and replace console logs; on open() InvalidStateError attempt to attach existing reader/writer and recover connection when both acquired; add richer logging and guarded cleanup; tests validate recovery and failure paths.
Serial reconnection, waiters, and error cleanup
src/js/serial.js, test/js/serial_connect.test.js
Add _handleReconnection(requestedPath, options, callback); change selectProtocol signature to selectProtocol(portPath); short-circuit when already connected, wait for in-flight connect/disconnect (up to ~5s), realign/disconnect as needed, and strengthen protocol error cleanup and disconnect notifications; tests for concurrency and failure handling added.
AutoDetect control flow & cleanup
src/js/utils/AutoDetect.js
Make verifyBoard() async, restructure to await serial.connect inside guarded flow, remove immediate early serial.disconnect() paths, add cleanup() to centralize teardown and listener removal; use cleanup() from onFinishClose().
Main UI tab behavior
src/js/main.js
Removed lines that programmatically re-open the firmware flasher tab during a restricted tab toggle; stops forcing the flasher tab to open.
Test imports and new tests
test/js/*
Update/import path fixes in tests and add new suites: serial_connect.test.js, firmware_flasher.test.js, webserial.test.js to cover new behaviors.

Sequence Diagram(s)

sequenceDiagram
  actor User
  participant UI as Firmware Flasher UI
  participant EB as EventBus
  participant AD as AutoDetect
  note over UI,AD: Debounced port-change verification (PORT_CHANGE_DEBOUNCE_MS)
  EB-->>UI: ports-input:change(port)
  UI->>UI: clear pending portChangeTimer
  UI->>UI: schedule verifyBoard() after debounce
  UI->>AD: verifyBoard() (after debounce)
  AD-->>UI: verification result
  UI-->>User: update selection/logs
Loading
sequenceDiagram
  participant App
  participant WS as WebSerial
  participant Port as navigator.serial.Port
  note over WS,Port: Recovery when open() throws InvalidStateError
  App->>WS: connect(path, options)
  WS->>Port: open()
  Port-->>WS: throws InvalidStateError
  WS->>Port: attempt acquire reader/writer & getInfo()
  alt reader & writer acquired
    WS->>WS: set connected, start read loop
    WS-->>App: dispatch connect(true)
  else acquisition failed
    WS-->>App: dispatch connect(false)
  end
Loading
sequenceDiagram
  participant Caller1
  participant Caller2
  participant Serial
  participant Protocol
  note over Serial: Concurrent connect handling + reconnection short-circuit
  Caller1->>Serial: connect(path)
  Serial->>Protocol: connect()
  par while in-flight
    Caller2->>Serial: connect(path)
    Serial->>Serial: wait for connect/disconnect (timeout ~5s)
  end
  alt connected to requested path
    Serial-->>Caller1: true
    Serial-->>Caller2: true
  else failure/path-mismatch
    Serial->>Protocol: disconnect/reconnect as needed
    Serial-->>Caller1: false
    Serial-->>Caller2: false
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

Tested

Suggested reviewers

  • nerdCopter
  • VitroidFPV
  • KarateBrot

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings, 1 inconclusive)
Check name Status Explanation Resolution
Out of Scope Changes Check ⚠️ Warning This PR introduces several changes that fall outside the scope of fixing the ports tab behavior from issue #4595, including updates to MSPHelper callback handling, the addition of a standalone logger module, and removal of auto-open logic in main.js. Those modifications are unrelated to restoring port detection compatibility in the ports tab and should be split into separate PRs or scoped differently to maintain focus on the reported bug. Isolate unrelated changes such as CRC error callback handling in MSPHelper and the new logger integration into separate pull requests, and refocus this PR solely on port detection and UI behavior fixes tied to issue #4595.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Linked Issues Check ❓ Inconclusive While this PR improves port-change event handling, debouncing, and connection recovery, it does not explicitly address the ports tab UI listing issue described in issue #4595. The changes focus on firmware flasher event handling, serial reconnection logic, and error recovery but omit direct modifications or tests for the main ports-tab interface or port enumeration flow in Chrome/Edge 140+. Without such targeted updates, it is unclear whether the linked issue is fully resolved. Please clarify how these changes restore the ports tab UI behavior in Chromium 140+ and include any necessary modifications or tests for the ports-tab interface to ensure issue #4595 is fully addressed.
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed The title “Fix port event handling” concisely and accurately captures the primary focus of this pull request, which centers on improving port-change debouncing, detection handlers, and connection recovery across the configurator. It is specific to the key change without listing every detail, making it clear for team members scanning history. The phrasing is direct and avoids unnecessary noise or overly broad language. It aligns well with the modifications to firmware_flasher and serial modules.
Description Check ✅ Passed The pull request description is well structured with clear sections for Purpose, Key Issues Fixed, Summary of Changes, Testing Instructions, Related Issues, and Breaking Changes. It omits the template guidelines as required and provides both manual and automated test instructions along with detailed descriptions of all modifications and test coverage. The description gives reviewers sufficient context to understand the scope, rationale, and testing strategy for the changes. It aligns with the repository’s expectations for a comprehensive and informative PR description.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8c10a46 and ab70d34.

📒 Files selected for processing (5)
  • src/js/msp/MSPHelper.js (1 hunks)
  • src/js/tabs/firmware_flasher.js (5 hunks)
  • test/js/MSPHelper.test.js (3 hunks)
  • test/js/firmware_flasher.test.js (1 hunks)
  • test/js/msp_helpers.js (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (4)
test/js/firmware_flasher.test.js (1)
src/js/tabs/firmware_flasher.js (1)
  • firmware_flasher (24-50)
src/js/tabs/firmware_flasher.js (3)
src/js/protocols/webstm32.js (1)
  • STM32 (1007-1007)
src/js/gui.js (1)
  • GUI (473-473)
src/js/port_handler.js (2)
  • devicePath (110-110)
  • PortHandler (16-50)
test/js/MSPHelper.test.js (1)
src/js/msp/MSPCodes.js (1)
  • MSPCodes (2-211)
src/js/msp/MSPHelper.js (1)
src/js/msp.js (2)
  • data (74-74)
  • data (355-355)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: deploy

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
src/js/msp/MSPHelper.js (1)

1782-1793: LGTM - Callback behavior change is intentional and well-structured.

The refactoring to always invoke callbacks (regardless of crcError) is a significant but intentional change, as noted in the comment. The try/catch wrapper provides good protection against callback errors.

One minor observation: The warning at line 1792 is redundant with the warning at line 1768 (both log essentially the same CRC failure message). The warning at line 1768 is sufficient. However, if the intent is to emphasize that a callback was invoked despite the CRC error, consider making the message at line 1792 more specific, e.g., console.warn(\code: ${code} - callback invoked despite crc failure`);`

src/js/tabs/firmware_flasher.js (1)

53-135: LGTM - Handler implementations are well-structured.

The module-scoped handlers provide good separation of concerns:

  • Device detection logic is centralized in handleDetectedDevice
  • Port change debouncing prevents rapid re-entry
  • Debounce timer is properly cleared before starting a new one
  • The debounced callback re-checks GUI.connect_lock to handle race conditions

The logic at line 75 (GUI.connect_lock &&= !wasReboot;) is correct but subtle. The comment helps, but consider expanding it slightly for future maintainers:

- // Only clear the global connect lock when we are resuming from a reboot sequence.
+ // Only clear the global connect lock when we are resuming from a reboot sequence.
+ // If wasReboot is true, this clears connect_lock; otherwise, it leaves it unchanged.
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ab70d34 and c2cedc5.

📒 Files selected for processing (4)
  • src/js/msp/MSPHelper.js (1 hunks)
  • src/js/tabs/firmware_flasher.js (5 hunks)
  • test/js/MSPHelper.test.js (5 hunks)
  • test/js/firmware_flasher.test.js (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • test/js/MSPHelper.test.js
  • test/js/firmware_flasher.test.js
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-19T20:41:42.451Z
Learnt from: haslinghuis
PR: betaflight/betaflight-configurator#4510
File: src/js/msp.js:377-382
Timestamp: 2025-09-19T20:41:42.451Z
Learning: In the MSP.send_message function in src/js/msp.js, the doCallbackOnError parameter was removed as it was never used in production code throughout the entire codebase.

Applied to files:

  • src/js/msp/MSPHelper.js
🧬 Code graph analysis (1)
src/js/tabs/firmware_flasher.js (3)
src/js/protocols/webstm32.js (1)
  • STM32 (1007-1007)
src/js/gui.js (1)
  • GUI (473-473)
src/js/port_handler.js (1)
  • PortHandler (16-50)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: deploy
🔇 Additional comments (5)
src/js/tabs/firmware_flasher.js (5)

43-51: LGTM - Clean module-scoped handler organization.

Adding handler references to the firmware_flasher object is a good design choice that enables proper cleanup and testability.


835-844: LGTM - Handler wiring is properly sequenced.

The wiring of module-scoped handlers to EventBus after DOM readiness is the correct approach. Exposing clearBufferedFirmware at line 837 provides the necessary access for both the module handlers and cleanup routines.


1478-1481: LGTM - startFlashing registration is clean.

Binding startFlashing to the instance and exposing it on the firmware_flasher object allows it to be invoked directly by the module-scoped handlers without indirection.


1566-1575: Verify if the 1-second delay is necessary.

The auto-detect logic for already-connected devices is a good addition. However, the 1000ms delay at line 1574 seems arbitrary.

Consider whether a smaller delay (e.g., 100-300ms) would suffice, or if there's a specific reason for 1000ms. If the delay is necessary to wait for DOM elements or other initialization, the comment should be more specific about what is being waited for.

// If a specific initialization sequence requires this delay, document it:
setTimeout(() => {
    AutoDetect.verifyBoard();
}, 1000); // Wait for port picker and select2 widgets to fully initialize

1595-1611: LGTM - Cleanup properly handles handlers and timers.

The cleanup implementation correctly:

  • Unregisters EventBus listeners without nulling handler references (preventing re-initialization issues as noted in past reviews)
  • Clears the pending debounce timer to avoid callbacks firing after cleanup

This ensures resources are released while preserving the handler functions for future tab activations.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0bb260e and 7bf87de.

📒 Files selected for processing (10)
  • src/js/logger.js (1 hunks)
  • src/js/msp/MSPHelper.js (1 hunks)
  • src/js/protocols/WebSerial.js (19 hunks)
  • src/js/serial.js (3 hunks)
  • src/js/tabs/firmware_flasher.js (6 hunks)
  • src/js/utils/AutoDetect.js (0 hunks)
  • test/js/MSPHelper.test.js (6 hunks)
  • test/js/firmware_flasher.test.js (1 hunks)
  • test/js/serial_connect.test.js (1 hunks)
  • test/js/webserial.test.js (1 hunks)
💤 Files with no reviewable changes (1)
  • src/js/utils/AutoDetect.js
🚧 Files skipped from review as they are similar to previous changes (3)
  • test/js/firmware_flasher.test.js
  • test/js/webserial.test.js
  • test/js/MSPHelper.test.js
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-06-19T22:13:09.136Z
Learnt from: blckmn
PR: betaflight/betaflight-configurator#4521
File: src/js/protocols/WebSerial.js:148-151
Timestamp: 2025-06-19T22:13:09.136Z
Learning: In WebSerial.js, there's a timing issue where the cached `this.ports` array doesn't immediately reflect newly permitted devices after `requestPermissionDevice()` completes. The `getDevices()` method needs to refresh the device list from the browser API to return accurate data immediately following a permission request and user acceptance.

Applied to files:

  • src/js/protocols/WebSerial.js
  • src/js/serial.js
📚 Learning: 2025-09-02T07:45:48.606Z
Learnt from: blckmn
PR: betaflight/betaflight-configurator#4583
File: src/js/tabs/firmware_flasher.js:949-961
Timestamp: 2025-09-02T07:45:48.606Z
Learning: In src/js/tabs/firmware_flasher.js, the config file loading code path after firmware loading (in the load_file click handler) cannot be reached when UF2 firmware is loaded, according to the maintainer blckmn. This code is maintained for backward compatibility with unified target settings and will be refactored in a future PR.

Applied to files:

  • src/js/tabs/firmware_flasher.js
📚 Learning: 2025-09-19T20:41:42.451Z
Learnt from: haslinghuis
PR: betaflight/betaflight-configurator#4510
File: src/js/msp.js:377-382
Timestamp: 2025-09-19T20:41:42.451Z
Learning: In the MSP.send_message function in src/js/msp.js, the doCallbackOnError parameter was removed as it was never used in production code throughout the entire codebase.

Applied to files:

  • src/js/msp/MSPHelper.js
🧬 Code graph analysis (4)
test/js/serial_connect.test.js (1)
src/js/serial.js (2)
  • serial (405-405)
  • serial (405-405)
src/js/protocols/WebSerial.js (1)
src/js/logger.js (1)
  • logger (3-7)
src/js/tabs/firmware_flasher.js (3)
src/js/protocols/webstm32.js (1)
  • STM32 (1007-1007)
src/js/gui.js (1)
  • GUI (473-473)
src/js/port_handler.js (1)
  • PortHandler (16-50)
src/js/msp/MSPHelper.js (2)
src/js/tabs/cli.js (1)
  • data (411-411)
src/js/msp.js (2)
  • data (74-74)
  • data (355-355)
🔇 Additional comments (21)
src/js/tabs/firmware_flasher.js (8)

24-25: LGTM: Debounce and delay constants are appropriate.

The 500ms debounce for port changes and 1000ms delay for auto-detection provide good balance between responsiveness and stability.


46-48: LGTM: Module-level properties are well-designed.

The shared debounce timer and logging prefix are correctly exposed for use by handlers and cleanup logic.


50-77: LGTM: USB device detection handler is correctly implemented.

The handler properly checks GUI.connect_lock and STM32.rebootMode, conditionally clears the lock only when resuming from a reboot, and safely calls startFlashing with optional chaining. All concerns from past reviews have been addressed.


78-93: LGTM: Serial device detection handler is correctly implemented.

The handler properly guards against active operations and includes appropriate error handling for AutoDetect.verifyBoard().


94-133: LGTM: Port change handler with debounce is correctly implemented.

The debounce logic is solid:

  • Clears any pending timer before scheduling a new one
  • Re-checks GUI.connect_lock in the debounced callback
  • Properly manages board selection based on lock state

134-144: LGTM: Device removal handler is correctly implemented.

The handler properly guards against clearing state during expected device removals (flashing/reboot) and safely calls clearBufferedFirmware with optional chaining.


1571-1582: LGTM: Auto-detect on activation is correctly guarded.

The activation-time auto-detect properly checks GUI.connect_lock both before scheduling and inside the timeout callback, matching the pattern used in onPortChange. This addresses previous review feedback.


1602-1618: LGTM: Cleanup logic is correctly implemented.

The cleanup function:

  • Properly unregisters event handlers without nullifying the function references (addressing previous review feedback)
  • Correctly clears the debounce timer to prevent callbacks after cleanup
  • Uses property names rather than function references (addressing previous review feedback)
src/js/logger.js (1)

1-9: LGTM!

Clean, test-friendly logger wrapper. The implementation correctly delegates to console methods while providing a mockable interface for tests.

test/js/serial_connect.test.js (5)

4-40: LGTM!

The FakeProtocol correctly simulates protocol behavior with appropriate state management and event dispatching.


47-61: LGTM!

The setup and teardown properly capture and restore original protocol references, preventing test pollution. This addresses the past review concern about leaking fake implementations.


80-113: LGTM!

ThrowingProtocol correctly simulates connection failures for testing error handling paths.


115-157: LGTM!

The test comprehensively validates that connection failures trigger proper state cleanup and notification of waiters.


159-236: LGTM!

The InvalidStateProtocol and associated test properly validate the specific error condition that triggers the recovery path in WebSerial.js.

src/js/protocols/WebSerial.js (3)

3-3: LGTM!

The logger import and console-to-logger replacements correctly use appropriate log levels throughout the early sections of the file.

Also applies to: 61-61, 96-96, 123-123, 139-141, 154-154, 164-164, 177-177, 194-194, 206-206, 218-218


223-312: LGTM!

The InvalidStateError recovery logic correctly addresses past review concerns:

  • Requires both reader and writer before declaring success (lines 263-264)
  • Releases partial locks before failing (lines 266-281)
  • Properly initializes connection state on successful recovery (lines 287-308)

The implementation handles the edge case where the browser throws InvalidStateError on concurrent open() calls while the port is already usable.


326-326: LGTM!

The remaining logger replacements consistently use appropriate log levels for read loop errors, disconnect flow, and send operations.

Also applies to: 366-366, 378-378, 389-389, 394-396, 406-406, 435-435, 444-444, 465-465

src/js/serial.js (4)

114-140: LGTM!

The simplified selectProtocol signature and streamlined protocol selection logic improve clarity. The added logging of the selected protocol aids debugging.


179-241: LGTM!

The in-flight connection wait logic properly addresses past review concerns:

  • Includes a 5-second timeout to prevent hangs (lines 203-206)
  • Clears the timeout when events fire (lines 207-212)
  • Properly cleans up listeners in all paths (lines 195-202)
  • Handles port mismatch after waiting by disconnecting first (lines 232-240)

The implementation prevents race conditions while allowing callers to await connection completion.


243-279: LGTM!

The try-catch wrapper around protocol.connect() properly handles failures by:

  • Resetting protocol state (openRequested, connected, connectionId) to prevent stale state (lines 258-264)
  • Dispatching a disconnect event to notify waiters (line 272)
  • Ensuring the callback receives false (line 277)

This prevents the scenario where a failed connect leaves openRequested true and blocks future connection attempts.


315-317: LGTM!

The clarifying comment about async function return values is accurate and helpful.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/js/serial.js (1)

280-310: Consider using protocol methods for state cleanup to improve encapsulation.

The error handling directly assigns protocol properties (openRequested, connected, connectionId), which breaks encapsulation and assumes these properties exist and are writable. If protocols use getters/setters or have these properties as read-only, the cleanup might fail silently.

Consider one of these approaches:

Option 1: Add a cleanup method to the protocol interface:

// In protocol implementations, add:
resetState() {
    this.openRequested = false;
    this.connected = false;
    this.connectionId = null;
}

// In serial.js error handler:
if (this._protocol?.resetState) {
    this._protocol.resetState();
}

Option 2: Wrap each property assignment in try-catch for safety:

         try {
             if (this._protocol) {
-                // Best-effort state cleanup
-                this._protocol.openRequested = false;
-                this._protocol.connected = false;
-                if (Object.prototype.hasOwnProperty.call(this._protocol, "connectionId")) {
-                    this._protocol.connectionId = null;
-                }
+                // Best-effort state cleanup
+                try { this._protocol.openRequested = false; } catch { /* ignore */ }
+                try { this._protocol.connected = false; } catch { /* ignore */ }
+                try {
+                    if (Object.prototype.hasOwnProperty.call(this._protocol, "connectionId")) {
+                        this._protocol.connectionId = null;
+                    }
+                } catch { /* ignore */ }
             }
         } catch (cleanupErr) {
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7bf87de and c00a971.

📒 Files selected for processing (10)
  • src/js/logger.js (1 hunks)
  • src/js/msp/MSPHelper.js (1 hunks)
  • src/js/protocols/WebSerial.js (19 hunks)
  • src/js/serial.js (4 hunks)
  • src/js/tabs/firmware_flasher.js (5 hunks)
  • src/js/utils/AutoDetect.js (0 hunks)
  • test/js/MSPHelper.test.js (5 hunks)
  • test/js/firmware_flasher.test.js (1 hunks)
  • test/js/serial_connect.test.js (1 hunks)
  • test/js/webserial.test.js (1 hunks)
💤 Files with no reviewable changes (1)
  • src/js/utils/AutoDetect.js
🚧 Files skipped from review as they are similar to previous changes (3)
  • test/js/MSPHelper.test.js
  • test/js/serial_connect.test.js
  • test/js/firmware_flasher.test.js
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-06-19T22:13:09.136Z
Learnt from: blckmn
PR: betaflight/betaflight-configurator#4521
File: src/js/protocols/WebSerial.js:148-151
Timestamp: 2025-06-19T22:13:09.136Z
Learning: In WebSerial.js, there's a timing issue where the cached `this.ports` array doesn't immediately reflect newly permitted devices after `requestPermissionDevice()` completes. The `getDevices()` method needs to refresh the device list from the browser API to return accurate data immediately following a permission request and user acceptance.

Applied to files:

  • src/js/serial.js
  • src/js/protocols/WebSerial.js
📚 Learning: 2025-09-02T07:45:48.606Z
Learnt from: blckmn
PR: betaflight/betaflight-configurator#4583
File: src/js/tabs/firmware_flasher.js:949-961
Timestamp: 2025-09-02T07:45:48.606Z
Learning: In src/js/tabs/firmware_flasher.js, the config file loading code path after firmware loading (in the load_file click handler) cannot be reached when UF2 firmware is loaded, according to the maintainer blckmn. This code is maintained for backward compatibility with unified target settings and will be refactored in a future PR.

Applied to files:

  • src/js/tabs/firmware_flasher.js
📚 Learning: 2025-09-19T20:41:42.451Z
Learnt from: haslinghuis
PR: betaflight/betaflight-configurator#4510
File: src/js/msp.js:377-382
Timestamp: 2025-09-19T20:41:42.451Z
Learning: In the MSP.send_message function in src/js/msp.js, the doCallbackOnError parameter was removed as it was never used in production code throughout the entire codebase.

Applied to files:

  • src/js/msp/MSPHelper.js
🧬 Code graph analysis (3)
src/js/tabs/firmware_flasher.js (3)
src/js/protocols/webstm32.js (1)
  • STM32 (1007-1007)
src/js/gui.js (1)
  • GUI (473-473)
src/js/port_handler.js (2)
  • devicePath (110-110)
  • PortHandler (16-50)
src/js/protocols/WebSerial.js (1)
src/js/logger.js (1)
  • logger (3-7)
src/js/msp/MSPHelper.js (2)
src/js/tabs/cli.js (1)
  • data (411-411)
src/js/msp.js (2)
  • data (74-74)
  • data (355-355)
🔇 Additional comments (3)
test/js/webserial.test.js (1)

83-105: Great coverage of the successful recovery path.

Validating the receive loop and connection metadata ensures the already-open-port scenario stays reliable.

src/js/protocols/WebSerial.js (1)

262-285: Recovery guardrail is spot on.

Requiring both reader and writer—and cleaning up partial locks before returning—prevents the “connected but unusable” state we hit before.

src/js/serial.js (1)

346-348: LGTM!

The clarifying comment accurately describes async function behavior.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
test/js/firmware_flasher.test.js (1)

33-37: Restore mocked $ between tests

The test rebinds global.$ to a stub but never puts the original back, so later specs inherit the mock (or a now-reset vi.fn), which can break assertions that rely on the real jQuery shim. Please cache the original before overwriting and restore it in afterEach (or a finally) when the test concludes.

Apply this patch:

 let original$;
 
 describe("firmware_flasher port change debounce and cleanup", () => {
     beforeEach(() => {
         vi.useFakeTimers();
+        original$ = global.$;
         // ensure no handlers are set
         firmware_flasher.portChangeTimer = null;
@@
-        // Mock jQuery selector for board dropdown
-        global.$ = vi.fn(() => ({
+        // Mock jQuery selector for board dropdown
+        global.$ = vi.fn(() => ({
             is: vi.fn(() => false),
             val: vi.fn().mockReturnThis(),
             trigger: vi.fn().mockReturnThis(),
         }));
@@
         // advance past debounce
         vi.advanceTimersByTime(400);
         expect(spy).toHaveBeenCalledTimes(1);
 
         spy.mockRestore();
     });
@@
     afterEach(() => {
         vi.useRealTimers();
         // restore any spies
         vi.restoreAllMocks();
+        global.$ = original$;
     });
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c00a971 and 35d44cd.

📒 Files selected for processing (10)
  • src/js/logger.js (1 hunks)
  • src/js/msp/MSPHelper.js (1 hunks)
  • src/js/protocols/WebSerial.js (19 hunks)
  • src/js/serial.js (4 hunks)
  • src/js/tabs/firmware_flasher.js (5 hunks)
  • src/js/utils/AutoDetect.js (0 hunks)
  • test/js/MSPHelper.test.js (5 hunks)
  • test/js/firmware_flasher.test.js (1 hunks)
  • test/js/serial_connect.test.js (1 hunks)
  • test/js/webserial.test.js (1 hunks)
💤 Files with no reviewable changes (1)
  • src/js/utils/AutoDetect.js
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/js/logger.js
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-06-19T22:13:09.136Z
Learnt from: blckmn
PR: betaflight/betaflight-configurator#4521
File: src/js/protocols/WebSerial.js:148-151
Timestamp: 2025-06-19T22:13:09.136Z
Learning: In WebSerial.js, there's a timing issue where the cached `this.ports` array doesn't immediately reflect newly permitted devices after `requestPermissionDevice()` completes. The `getDevices()` method needs to refresh the device list from the browser API to return accurate data immediately following a permission request and user acceptance.

Applied to files:

  • src/js/protocols/WebSerial.js
  • src/js/serial.js
📚 Learning: 2025-09-02T07:45:48.606Z
Learnt from: blckmn
PR: betaflight/betaflight-configurator#4583
File: src/js/tabs/firmware_flasher.js:949-961
Timestamp: 2025-09-02T07:45:48.606Z
Learning: In src/js/tabs/firmware_flasher.js, the config file loading code path after firmware loading (in the load_file click handler) cannot be reached when UF2 firmware is loaded, according to the maintainer blckmn. This code is maintained for backward compatibility with unified target settings and will be refactored in a future PR.

Applied to files:

  • src/js/tabs/firmware_flasher.js
🧬 Code graph analysis (6)
test/js/serial_connect.test.js (1)
src/js/serial.js (2)
  • serial (443-443)
  • serial (443-443)
test/js/MSPHelper.test.js (2)
src/js/fc.js (1)
  • FC (132-1006)
src/js/msp/MSPCodes.js (1)
  • MSPCodes (2-211)
src/js/protocols/WebSerial.js (1)
src/js/logger.js (1)
  • logger (3-7)
src/js/tabs/firmware_flasher.js (3)
src/js/protocols/webstm32.js (1)
  • STM32 (1007-1007)
src/js/gui.js (1)
  • GUI (473-473)
src/js/port_handler.js (2)
  • devicePath (110-110)
  • PortHandler (16-50)
test/js/firmware_flasher.test.js (3)
src/js/tabs/firmware_flasher.js (1)
  • firmware_flasher (27-145)
src/js/gui.js (1)
  • GUI (473-473)
src/js/protocols/webstm32.js (1)
  • STM32 (1007-1007)
src/js/msp/MSPHelper.js (2)
src/js/tabs/cli.js (1)
  • data (411-411)
src/js/msp.js (2)
  • data (74-74)
  • data (355-355)
🔇 Additional comments (14)
test/js/webserial.test.js (2)

14-105: LGTM! Comprehensive recovery path test.

The test thoroughly validates the WebSerial recovery flow when a port is already open, including event dispatch and data reception. The mock setup is clean and properly isolates the WebSerial module.

Minor note: The 20ms timeout at line 98 is reasonable for this test but could theoretically be flaky under heavy system load. Consider this acceptable given the immediate response from the fake reader.


107-158: LGTM! Thorough failure path coverage.

The test correctly validates that connect() fails gracefully when recovery cannot obtain reader/writer, ensuring proper cleanup and event dispatch on failure.

src/js/serial.js (4)

36-79: LGTM! Clean reconnection helper.

The _handleReconnection method elegantly handles the common cases:

  • Returns true when already connected to the requested port
  • Disconnects and reconnects when connected to a different port
  • Returns undefined to allow normal connect flow

The try-catch provides appropriate error handling without disrupting the control flow.


161-185: LGTM! Simplified protocol selection.

The simplified signature removes unused parameters and clarifies the protocol selection logic. The protocol mapping is clean and maintainable.


210-279: LGTM! Robust in-flight connection handling.

The wait logic properly handles concurrent connection attempts:

  • 5-second timeout prevents indefinite hangs
  • Post-wait state checks handle all scenarios (same port, different port, still in-flight)
  • Disconnect before reconnecting when ports don't match
  • Listeners are cleaned up in all paths

This addresses the race conditions and timeout concerns from previous reviews.

Based on past review comments.


283-317: LGTM! Comprehensive error handling and cleanup.

The try-catch wrapper ensures protocol state is reset on failures:

  • Clears openRequested and connected flags
  • Resets connectionId
  • Dispatches disconnect event to notify waiters
  • Invokes callback with failure status

The best-effort cleanup in nested try-catch (lines 295-306) handles edge cases gracefully.

src/js/tabs/firmware_flasher.js (8)

24-26: LGTM! Clear timing constants.

The explicit constants for debounce and auto-detect delays improve code readability and maintainability.


50-77: LGTM! Correct USB device handler.

The handler properly:

  • Guards against interference with active operations (connect_lock check)
  • Only clears connect_lock when resuming from reboot
  • Uses optional chaining for safe method invocation
  • Correctly differentiates between reboot and flash-on-connect scenarios

This addresses all the context and timing issues from previous reviews.

Based on past review comments.


78-93: LGTM! Safe serial device handler.

The handler respects GUI.connect_lock and includes error handling around AutoDetect.verifyBoard(), preventing exceptions from disrupting the event flow.


94-133: LGTM! Robust debounced port change handler.

The handler correctly:

  • Clears pending timers before scheduling new verification
  • Guards at both scheduling time and execution time with connect_lock
  • Includes error handling around AutoDetect.verifyBoard()
  • Clears board selection appropriately when no port is selected

The debounce implementation prevents rapid port events from overwhelming the auto-detect logic.


134-144: LGTM! Appropriate device removal handler.

The handler avoids clearing state during expected removals (flashing, reboot) and uses optional chaining for safe method invocation.


845-854: LGTM! Proper function exposure and handler registration.

Functions are exposed immediately after definition (clearBufferedFirmware is defined earlier, startFlashing at line 1457 is hoisted), ensuring module-scoped handlers can invoke them safely. This ordering prevents the race condition flagged in previous reviews.

Based on past review comments.


1571-1582: LGTM! Guarded auto-detect on activation.

The activation-time auto-detect properly checks GUI.connect_lock before scheduling and re-checks within the timeout callback, preventing interference with in-progress operations.

Based on past review comments.


1602-1618: LGTM! Thorough cleanup.

The cleanup properly:

  • Unregisters all EventBus handlers
  • Clears the pending debounce timer to prevent post-cleanup actions
  • Uses a helper for consistent unregistration logic

This ensures no stale handlers or timers remain active after tab switch.

Based on past review comments.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All critical issues have been addressed! 🎉

The PR successfully fixes port event handling with:
✅ Comprehensive test coverage (4 new test suites)
✅ Proper error handling and state management
✅ Debouncing for port changes
✅ WebSerial recovery logic
✅ Serial connection concurrency handling
✅ All blocking issues resolved

Excellent work on this complex refactor!

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All critical issues have been addressed! 🎉

The PR successfully fixes port event handling with:
✅ Comprehensive test coverage (4 new test suites)
✅ Proper error handling and state management
✅ Debouncing for port changes
✅ WebSerial recovery logic
✅ Serial connection concurrency handling
✅ All blocking issues resolved

Excellent work on this complex refactor!

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (3)
src/js/protocols/WebSerial.js (1)

241-261: Consider logging recovery attempt start for clarity.

While warnings are logged when writer/reader acquisition fails (lines 251, 259), adding a log at the start of the recovery attempt would make the flow clearer in debug scenarios.

Add at line 241:

         try {
+            logger.info(`${logHead} Attempting to recover already-open port`);
             const connectionInfo = this.port?.getInfo ? this.port.getInfo() : null;
src/js/serial.js (2)

1-443: Consider migrating to centralized logger for consistency.

WebSerial.js has been migrated to use the centralized logger module (imported at line 3 of WebSerial.js), but serial.js still uses console.log/warn/error directly (e.g., lines 48, 177, 214, 294). For consistency, testability, and alignment with the PR's logging improvements, consider importing and using the logger module here as well.

Example:

+import logger from "./logger";
 import WebSerial from "./protocols/WebSerial.js";
 import WebBluetooth from "./protocols/WebBluetooth.js";

Then replace console calls:

-                console.log(`${this.logHead} Already connected to the requested port`);
+                logger.info(`${this.logHead} Already connected to the requested port`);

234-237: Consider making timeout value configurable.

The 5-second timeout is hardcoded. For different environments or use cases (e.g., slower devices, testing), a configurable timeout might be beneficial.

Example approach:

+        const CONNECT_WAIT_TIMEOUT_MS = 5000; // or make it a class property/config
         const timer = setTimeout(() => {
             cleanup();
             resolve(null);
-        }, 5000);
+        }, CONNECT_WAIT_TIMEOUT_MS);
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 35d44cd and b816e65.

📒 Files selected for processing (11)
  • src/js/logger.js (1 hunks)
  • src/js/main.js (0 hunks)
  • src/js/msp/MSPHelper.js (1 hunks)
  • src/js/protocols/WebSerial.js (19 hunks)
  • src/js/serial.js (4 hunks)
  • src/js/tabs/firmware_flasher.js (5 hunks)
  • src/js/utils/AutoDetect.js (3 hunks)
  • test/js/MSPHelper.test.js (5 hunks)
  • test/js/firmware_flasher.test.js (1 hunks)
  • test/js/serial_connect.test.js (1 hunks)
  • test/js/webserial.test.js (1 hunks)
💤 Files with no reviewable changes (1)
  • src/js/main.js
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/js/utils/AutoDetect.js
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-06-19T22:13:09.136Z
Learnt from: blckmn
PR: betaflight/betaflight-configurator#4521
File: src/js/protocols/WebSerial.js:148-151
Timestamp: 2025-06-19T22:13:09.136Z
Learning: In WebSerial.js, there's a timing issue where the cached `this.ports` array doesn't immediately reflect newly permitted devices after `requestPermissionDevice()` completes. The `getDevices()` method needs to refresh the device list from the browser API to return accurate data immediately following a permission request and user acceptance.

Applied to files:

  • src/js/serial.js
  • src/js/protocols/WebSerial.js
📚 Learning: 2025-09-19T20:41:42.451Z
Learnt from: haslinghuis
PR: betaflight/betaflight-configurator#4510
File: src/js/msp.js:377-382
Timestamp: 2025-09-19T20:41:42.451Z
Learning: In the MSP.send_message function in src/js/msp.js, the doCallbackOnError parameter was removed as it was never used in production code throughout the entire codebase.

Applied to files:

  • src/js/msp/MSPHelper.js
📚 Learning: 2025-09-02T07:45:48.606Z
Learnt from: blckmn
PR: betaflight/betaflight-configurator#4583
File: src/js/tabs/firmware_flasher.js:949-961
Timestamp: 2025-09-02T07:45:48.606Z
Learning: In src/js/tabs/firmware_flasher.js, the config file loading code path after firmware loading (in the load_file click handler) cannot be reached when UF2 firmware is loaded, according to the maintainer blckmn. This code is maintained for backward compatibility with unified target settings and will be refactored in a future PR.

Applied to files:

  • src/js/tabs/firmware_flasher.js
🧬 Code graph analysis (6)
src/js/msp/MSPHelper.js (2)
src/js/tabs/cli.js (1)
  • data (411-411)
src/js/msp.js (2)
  • data (74-74)
  • data (355-355)
test/js/serial_connect.test.js (1)
src/js/serial.js (2)
  • serial (443-443)
  • serial (443-443)
test/js/firmware_flasher.test.js (3)
src/js/tabs/firmware_flasher.js (1)
  • firmware_flasher (27-145)
src/js/gui.js (1)
  • GUI (473-473)
src/js/protocols/webstm32.js (1)
  • STM32 (1007-1007)
test/js/MSPHelper.test.js (2)
src/js/fc.js (1)
  • FC (132-1006)
src/js/msp/MSPCodes.js (1)
  • MSPCodes (2-211)
src/js/protocols/WebSerial.js (1)
src/js/logger.js (1)
  • logger (3-7)
src/js/tabs/firmware_flasher.js (3)
src/js/protocols/webstm32.js (1)
  • STM32 (1007-1007)
src/js/gui.js (1)
  • GUI (473-473)
src/js/port_handler.js (2)
  • devicePath (110-110)
  • PortHandler (16-50)
🔇 Additional comments (6)
src/js/protocols/WebSerial.js (2)

3-3: LGTM: Centralized logging improves testability.

The migration from console methods to the centralized logger module is clean and consistent throughout the file. This enhances testability and aligns with the PR's goal of introducing a test-friendly logger.

Also applies to: 61-61, 96-96, 123-123, 139-141, 154-154, 164-164, 177-177, 194-194, 206-206, 218-218, 223-223


225-312: LGTM: Recovery logic properly handles already-open ports.

The recovery path for InvalidStateError is well-implemented with appropriate safeguards:

  • Validates both reader and writer before declaring success (lines 263-264)
  • Releases partial locks if recovery fails (lines 266-281), preventing port lockup
  • Proper state initialization including event listeners, connection state, and read loop
  • Graceful fallback to the normal failure path if recovery cannot proceed

This addresses Chromium 140+ compatibility issues where concurrent open attempts throw InvalidStateError on already-open ports. The defensive programming (optional chaining at line 242, try-catch around lock operations) ensures robust error handling.

Based on past review feedback that has been addressed in commits 2719276 and 45422a5.

src/js/serial.js (4)

36-79: LGTM: Reconnection helper handles port switching correctly.

The _handleReconnection method properly handles three scenarios:

  1. Already connected to requested port → returns true immediately
  2. Connected to different port → disconnects, then reconnects to new port
  3. Neither case → returns undefined to continue normal flow

Error handling is appropriate with try-catch wrapping and proper callback invocation. The use of undefined as a sentinel value to signal "continue normal flow" is clear and allows the main connect method to proceed with its standard logic.


161-161: LGTM: Simplified selectProtocol signature.

Removing the default parameter and forceDisconnect parameter simplifies the API. The breaking change is documented in the PR summary.


213-279: LGTM: Comprehensive in-flight wait logic prevents race conditions.

The wait logic thoroughly handles concurrent connection attempts:

  • Timeout protection (5s) prevents indefinite hangs (lines 234-237)
  • State validation after timeout checks for lingering openRequested (lines 254-258)
  • Port mismatch handling after wait disconnects if needed (lines 271-278)
  • Proper cleanup of event listeners in all paths (lines 226-233)

This addresses race conditions where multiple callers attempt to connect simultaneously, ensuring they wait for in-flight attempts to complete rather than colliding.

Based on past review feedback that has been addressed in commits 97c806d, bcc1200, 0bb260e, c00a971, and 35d44cd.


283-317: LGTM: Robust error handling prevents stale protocol state.

The try-catch around protocol.connect() ensures:

  • State cleanup on errors (openRequested, connected, connectionId reset)
  • Event notification via disconnect event dispatch so waiters are unblocked
  • Callback invocation with failure signal
  • Graceful degradation with nested try-catch for cleanup operations

This prevents the protocol from getting stuck in an openRequested state when connection fails, which would block future connection attempts.

Copy link

sonarqubecloud bot commented Oct 9, 2025

Quality Gate Failed Quality Gate failed

Failed conditions
8.2% Duplication on New Code (required ≤ 3%)
C Reliability Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

Copy link
Contributor

github-actions bot commented Oct 9, 2025

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
src/js/protocols/WebSerial.js (1)

17-31: Use the shared logger inside the read loop helper

Everything else in this module now routes through logger, but streamAsyncIterable still writes straight to console.warn. Swapping those two calls to logger.warn keeps logging consistent (and makes it easier to stub in tests).

Apply this diff:

-                console.warn(`${logHead} Read error in streamAsyncIterable:`, error);
+                logger.warn(`${logHead} Read error in streamAsyncIterable:`, error);
@@
-            console.warn(`${logHead} Error releasing reader lock:`, error);
+            logger.warn(`${logHead} Error releasing reader lock:`, error);
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b816e65 and 48350d3.

📒 Files selected for processing (11)
  • src/js/logger.js (1 hunks)
  • src/js/main.js (0 hunks)
  • src/js/msp/MSPHelper.js (1 hunks)
  • src/js/protocols/WebSerial.js (19 hunks)
  • src/js/serial.js (4 hunks)
  • src/js/tabs/firmware_flasher.js (5 hunks)
  • src/js/utils/AutoDetect.js (3 hunks)
  • test/js/MSPHelper.test.js (5 hunks)
  • test/js/firmware_flasher.test.js (1 hunks)
  • test/js/serial_connect.test.js (1 hunks)
  • test/js/webserial.test.js (1 hunks)
💤 Files with no reviewable changes (1)
  • src/js/main.js
🚧 Files skipped from review as they are similar to previous changes (5)
  • src/js/logger.js
  • test/js/webserial.test.js
  • src/js/msp/MSPHelper.js
  • test/js/firmware_flasher.test.js
  • test/js/MSPHelper.test.js
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-09-02T07:45:48.606Z
Learnt from: blckmn
PR: betaflight/betaflight-configurator#4583
File: src/js/tabs/firmware_flasher.js:949-961
Timestamp: 2025-09-02T07:45:48.606Z
Learning: In src/js/tabs/firmware_flasher.js, the config file loading code path after firmware loading (in the load_file click handler) cannot be reached when UF2 firmware is loaded, according to the maintainer blckmn. This code is maintained for backward compatibility with unified target settings and will be refactored in a future PR.

Applied to files:

  • src/js/tabs/firmware_flasher.js
📚 Learning: 2025-06-19T22:13:09.136Z
Learnt from: blckmn
PR: betaflight/betaflight-configurator#4521
File: src/js/protocols/WebSerial.js:148-151
Timestamp: 2025-06-19T22:13:09.136Z
Learning: In WebSerial.js, there's a timing issue where the cached `this.ports` array doesn't immediately reflect newly permitted devices after `requestPermissionDevice()` completes. The `getDevices()` method needs to refresh the device list from the browser API to return accurate data immediately following a permission request and user acceptance.

Applied to files:

  • src/js/serial.js
  • src/js/protocols/WebSerial.js
🧬 Code graph analysis (4)
src/js/tabs/firmware_flasher.js (3)
src/js/protocols/webstm32.js (1)
  • STM32 (1007-1007)
src/js/gui.js (1)
  • GUI (473-473)
src/js/port_handler.js (2)
  • devicePath (110-110)
  • PortHandler (16-50)
src/js/utils/AutoDetect.js (4)
src/js/port_handler.js (1)
  • PortHandler (16-50)
src/js/gui.js (1)
  • TABS (8-8)
src/js/gui_log.js (1)
  • gui_log (7-21)
src/js/msp.js (1)
  • MSP (5-455)
test/js/serial_connect.test.js (2)
src/js/serial.js (2)
  • serial (440-440)
  • serial (440-440)
test/js/webserial.test.js (2)
  • result (83-83)
  • result (149-149)
src/js/protocols/WebSerial.js (1)
src/js/logger.js (1)
  • logger (3-7)

@nerdCopter
Copy link
Member

48350d3

  • briefly tested
  • flashed 1 FC
  • tested recent un-selected DFU/re-select DFU
  • tested basic tabs (Ports, Config, Modes, OSD, Blackbox, CLI)

OSD drag/drop is slow, but highly unlikely anything to do with this PR.

Copy link
Member

@nerdCopter nerdCopter left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • approving after basic test. saw only 1 coderabbitai nitpick.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

هك@

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

انهي وانگر

@haslinghuis haslinghuis marked this pull request as draft October 15, 2025 16:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: App

Development

Successfully merging this pull request may close these issues.

Latest configurator is messing ports tab in latest chrome/edge browser

3 participants