Skip to content

feat(signalr): implement SignalR protocol support#8519

Open
Aragorn7372 wants to merge 14 commits into
usebruno:mainfrom
Aragorn7372:feature/signal-r-implementation
Open

feat(signalr): implement SignalR protocol support#8519
Aragorn7372 wants to merge 14 commits into
usebruno:mainfrom
Aragorn7372:feature/signal-r-implementation

Conversation

@Aragorn7372

@Aragorn7372 Aragorn7372 commented Jul 7, 2026

Copy link
Copy Markdown

Problem

Bruno lacked support for SignalR protocol, one of the most popular real-time communication frameworks for ASP.NET. Users had no way to test SignalR connections, send invocations, or receive server events from within Bruno.

Fix

  • Schema & validation: Added SignalRInvocation type with uid/name/type/content/selected fields and Yup validation for signalr-request items.
  • Bru language: Extended grammars with signalr {} meta block and body:signalr {} message blocks for parsing and generation.
  • File formats: Added bru and YML format support for reading/writing SignalR requests, including OpenCollection converters.
  • SignalR engine (Electron): Integrated @microsoft/signalr via HubConnectionBuilder with WebSocket transport, automatic reconnect, and server event interception through _invokeClientMethod override.
  • Connection management: IPC handlers for start/stop/send-message/connection-status, plus renderer-side event listeners that dispatch Redux actions for connection state, errors, and hub events.
  • UI components: SignalRQueryUrl with connect/disconnect controls; refactored WsBody/SingleWSMessage to be reusable via messagesKey prop; SignalR creation options (transient, untitled, new request); SIGNALR badge in sidebar with themed color.
  • Redux store: Actions for newSignalRRequest, signalrConnectOnly, and sending SignalR invocations; slices for body mode signalr and connection event handling.
  • Test server: SignalR-compatible test endpoint (/hub/negotiate) with WebSocket upgrade path, plus e2e tests (connection, messages, persistence) with fixtures.
  • Themes: Added signalr color (#8caaee) across all 13 themes.

Summary

New Features

  • Full SignalR protocol support — connect, disconnect, send invocations, receive server events.
  • SignalR request type available in sidebar, transient/new request menus, and collection presets.
  • Dedicated SignalR URL bar with connection controls and status indicators.

Bug Fixes

  • Fixed SignalRRequestPane to use body.signalr instead of body.ws.
  • Fixed StyledWrapper export name in WSAuth component.
  • Fixed typo RecieveMessageReceiveMessage in preview component.

Tests

  • SignalR e2e test suite: connection, multi-message, headers/auth, and persistence specs.
  • SignalR converter unit tests for OpenCollection round-trip.
  • SignalR YML stringify unit tests.
  • Test server with negotiate endpoint and WebSocket hub upgrade.

Test Repository / SignalR Hubs

You can test SignalR requests using a local Bruno test server or a realistic ASP.NET Core SignalR server:

Service URL Notes
Bruno test server http://localhost:3001/hub Run node packages/bruno-tests/src/index.js
ASP.NET Core SignalR example signalRTestForBruno Fully functional ASP.NET Core SignalR server

To test with the ASP.NET Core example:

git clone https://github.com/Aragorn7372/signalRTestForBruno.git
cd signalRTestForBruno
dotnet run
``` {data-source-line="61"}

Then create a SignalR request in Bruno pointing to the hub URL defined in that project.

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

* **New Features**
  * Added end-to-end SignalR request support, including creation (presets and empty-state), a hub URL editor, auth and settings UI, and SignalR message authoring with connect/disconnect.
  * Added SignalR support across BRU/OpenCollection import/export and conversion, including SignalR-specific request parsing/serialization.
* **Bug Fixes**
  * Improved request/run behavior for SignalR tabs (including connection shutdown on close) and hardened SignalR/WS response/event handling.
* **Tests**
  * Added automated SignalR UI tests for connections, messaging, and persistence, plus conversion and round-trip coverage.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

…d Electron IPC

- Add SignalR schema types (SignalRInvocation with uid/name/type/content/selected)
- Add Yup validation for signalr-request items
- Add bru language grammar for signalr{} and body:signalr{} blocks
- Add bru format parse/stringify for SignalR requests
- Add YML format parse/stringify for SignalR requests
- Add OpenCollection converter to/from SignalR items
- Add Electron IPC handlers using @microsoft/signalr (HubConnection with auto-reconnect, WebSocket transport)
- Wire collection IPC to handle signalr-request create/delete
- Add @microsoft/signalr dependency to bruno-electron
…d tests

- Add Redux actions (newSignalRRequest, signalrConnectOnly, sendSignalR)
- Add Redux slices for signalr body mode and connection events
- Add SignalRQueryUrl component with connect/disconnect controls
- Refactor WsBody and SingleWSMessage to support messagesKey reuse
- Add SignalR option to CreateTransientRequest, CreateUntitledRequest, NewRequest
- Add SIGNALR badge to sidebar request method with themed color
- Add signalr preset to collection settings
- Wire SignalR event listeners (state-changed, error, hub events)
- Add signalr color (#8caaee) to all 13 themes
- Add signalr to schema/oss theme config
- Add test signalr server (hub/negotiate, WebSocket upgrade)
- Add e2e tests (connection, messages, persistence) with fixtures
@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Walkthrough

This PR adds SignalR as a new request type across shared schemas, BRU/YAML/OpenCollection conversion paths, Electron IPC, renderer request panes, request creation entrypoints, and end-to-end test coverage. It also updates theming, tab/default routing, and request-method display for SignalR.

Changes

SignalR Request Support

Layer / File(s) Summary
Contracts and conversion
packages/bruno-schema-types/src/..., packages/bruno-schema/src/collections/index.js, packages/bruno-filestore/src/formats/..., packages/bruno-lang/v2/src/..., packages/bruno-converters/src/opencollection/...
Adds SignalR request types, schemas, parsing, stringifying, and OpenCollection conversion support for SignalR request bodies and messages.
Runtime connection handling
packages/bruno-app/src/utils/signalr/..., packages/bruno-electron/src/ipc/network/..., packages/bruno-app/src/providers/ReduxStore/slices/collections/..., pages/Bruno/index.js
Adds SignalR connection lifecycle helpers, Electron IPC handlers, renderer event listeners, and Redux thunks/actions for connect/send/stop/status flows.
Renderer UI and message editor
packages/bruno-app/src/components/RequestPane/SignalR*, .../WsBody/*, RequestTabPanel, RequestTabs/RequestTab, RunnerResults/*, Sidebar/.../RequestMethod/*
Adds SignalR request panes, URL/auth controls, shared message editing support, tab integration, and request-method display/closing behavior.
Request creation, export, and theming
CollectionSettings/Presets, CreateTransientRequest, CreateUntitledRequest, Sidebar/NewRequest, utils/collections/*, themes/*, utils/common/constants.js, themes/schema/oss.js
Adds SignalR to request creation entrypoints, export/import normalization, default pane routing, and theme/color/schema configuration.
Converters, fixtures, and tests
packages/bruno-converters/tests/..., packages/bruno-filestore/src/formats/yml/items/*spec.ts, packages/bruno-tests/src/..., tests/signalr/*, tests/utils/page/locators.ts
Adds conversion round-trip tests, a SignalR test hub, Playwright flows, fixtures, and SignalR-specific locators.
Misc request-type wiring
ShareCollection, ResponsePane/Timeline, utils/{tabs,snapshot,collections,importers}/*, packages/bruno-electron/src/ipc/collection.js
Propagates signalr-request through request classification, export/import, snapshot/default-tab logic, and Electron item handling.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

  • usebruno/bruno#7719: Shares the same WsBody / SingleWSMessage multi-message editing path that this PR extends for SignalR.
  • usebruno/bruno#6878: Related through the same request-creation UI flow in CreateTransientRequest.
  • usebruno/bruno#7994: Related through request-pane tab snapshot/default-tab logic shared with this PR.

Suggested labels: size/XXL

Suggested reviewers: helloanoop, lohit-bruno, naman-bruno, bijin-bruno, sid-bruno, vijayh-bruno, utkarsh-bruno

Poem

A SignalR spark now joins the wire,
With hubs and tabs and UI fire.
JSON hums, the messages sing,
And Bruno learns a new request-string.
The tests march on, the colors glow—
A newer path for data flow.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding SignalR protocol support.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Implements end-to-end SignalR request support in Bruno, spanning schema/types, Bru/YML serialization, Electron IPC connection management, renderer UI/Redux handling, and an accompanying test hub + e2e coverage.

Changes:

  • Added signalr-request as a first-class request type with schema/types updates and Bru + YML read/write support (including OpenCollection converters).
  • Implemented SignalR connection lifecycle + invocation sending via Electron IPC, plus renderer-side listeners that surface hub events/connection state in the existing WS-style timeline/response UI.
  • Added SignalR fixtures, a SignalR-compatible test server endpoint, and Playwright e2e specs (connection/messages/persistence), plus theme color support.

Reviewed changes

Copilot reviewed 85 out of 86 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
tests/utils/page/locators.ts Adds shared Playwright locators for SignalR request UI.
tests/signalr/persistence.spec.ts Adds SignalR persistence e2e coverage (URL + message name).
tests/signalr/messages.spec.ts Adds SignalR multi-message UI e2e coverage.
tests/signalr/init-user-data/preferences.json Adds init user data for SignalR e2e runs.
tests/signalr/fixtures/collection/signalr-test.bru Adds a basic SignalR request fixture.
tests/signalr/fixtures/collection/signalr-persistence.bru Adds a persistence-oriented SignalR fixture.
tests/signalr/fixtures/collection/signalr-multi-msg.bru Adds a multi-invocation SignalR fixture.
tests/signalr/fixtures/collection/signalr-headers-auth.bru Adds a headers/auth SignalR fixture.
tests/signalr/fixtures/collection/collection.bru Adds collection-level fixture content for SignalR tests.
tests/signalr/fixtures/collection/bruno.json Adds collection metadata for SignalR fixtures.
tests/signalr/connection.spec.ts Adds SignalR connect/disconnect e2e coverage.
packages/bruno-tests/src/signalr/index.js Adds a SignalR test hub implementation for e2e.
packages/bruno-tests/src/index.js Wires SignalR negotiate + upgrade routing into test server.
packages/bruno-schema/src/collections/index.js Adds Yup validation for signalr-request items and settings.
packages/bruno-schema-types/src/requests/signalr.ts Introduces TS types for SignalR requests + invocations.
packages/bruno-schema-types/src/requests/index.ts Exports SignalR request types and extends Request union.
packages/bruno-schema-types/src/collection/item.ts Adds signalr-request to supported item types.
packages/bruno-lang/v2/src/jsonToBru.js Adds JSON→Bru generation for signalr {} + body:signalr {}.
packages/bruno-lang/v2/src/bruToJson.js Extends Bru grammar + AST to parse SignalR blocks/messages.
packages/bruno-filestore/src/formats/yml/stringifyItem.ts Adds YML stringifier dispatch for SignalR items.
packages/bruno-filestore/src/formats/yml/parseItem.ts Adds YML parser dispatch + backwards-compat auth handling for SignalR.
packages/bruno-filestore/src/formats/yml/items/stringifySignalRRequest.ts Implements SignalR request→OpenCollection YML serialization.
packages/bruno-filestore/src/formats/yml/items/stringifySignalRRequest.spec.ts Adds unit tests for stringify/parse round-trip.
packages/bruno-filestore/src/formats/yml/items/parseSignalRRequest.ts Implements OpenCollection YML→Bruno SignalR parsing.
packages/bruno-filestore/src/formats/bru/index.ts Adds .bru parse/stringify support for SignalR request type.
packages/bruno-electron/src/ipc/network/signalr-event-handlers.js Adds main-process SignalR IPC handlers + event forwarding.
packages/bruno-electron/src/ipc/network/index.js Registers SignalR IPC handlers during network IPC setup.
packages/bruno-electron/src/ipc/collection.js Includes SignalR request type in filesystem collection operations.
packages/bruno-electron/package.json Adds @microsoft/signalr dependency to Electron package.
packages/bruno-converters/tests/opencollection/signalr.spec.js Adds OpenCollection conversion tests for SignalR items.
packages/bruno-converters/src/opencollection/types.ts Exports SignalR types for converter layer.
packages/bruno-converters/src/opencollection/items/signalr.ts Implements OC⇄Bruno conversion logic for SignalR items.
packages/bruno-converters/src/opencollection/items/index.ts Wires SignalR into generic OC item conversion dispatch.
packages/bruno-app/src/utils/tabs/index.js Treats SignalR items as request tabs.
packages/bruno-app/src/utils/snapshot/index.js Adds SignalR to request tab snapshot/default tab selection logic.
packages/bruno-app/src/utils/signalr/signalr-client.js Adds renderer-side SignalR client helper.
packages/bruno-app/src/utils/signalr/connections.js Adds renderer IPC wrappers for SignalR connection lifecycle.
packages/bruno-app/src/utils/network/signalr-event-listeners.js Adds renderer listeners for SignalR state/errors/events via IPC.
packages/bruno-app/src/utils/network/index.js Adds SignalR connect/send/stop/status functions into network layer.
packages/bruno-app/src/utils/importers/common.js Extends import normalization to support signalr type.
packages/bruno-app/src/utils/common/constants.js Adds SignalR to request-type constants + presets.
packages/bruno-app/src/utils/collections/index.js Extends save/export transforms to handle SignalR bodies and types.
packages/bruno-app/src/utils/collections/export.js Includes SignalR requests/examples in export type mapping.
packages/bruno-app/src/utils/collections/emptyStateRequest.js Adds SignalR option in empty-state create menu.
packages/bruno-app/src/themes/schema/oss.js Extends theme schema to require a SignalR color.
packages/bruno-app/src/themes/light/vscode.js Adds SignalR color to VS Code light theme.
packages/bruno-app/src/themes/light/light.js Adds SignalR color to light theme.
packages/bruno-app/src/themes/light/light-pastel.js Adds SignalR color to light pastel theme.
packages/bruno-app/src/themes/light/light-monochrome.js Adds SignalR color to light monochrome theme.
packages/bruno-app/src/themes/light/catppuccin-latte.js Adds SignalR color to Catppuccin Latte theme.
packages/bruno-app/src/themes/dark/vscode.js Adds SignalR color to VS Code dark theme.
packages/bruno-app/src/themes/dark/nord.js Adds SignalR color to Nord theme.
packages/bruno-app/src/themes/dark/dark.js Adds SignalR color to dark theme.
packages/bruno-app/src/themes/dark/dark-pastel.js Adds SignalR color to dark pastel theme.
packages/bruno-app/src/themes/dark/dark-monochrome.js Adds SignalR color to dark monochrome theme.
packages/bruno-app/src/themes/dark/catppuccin-mocha.js Adds SignalR color to Catppuccin Mocha theme.
packages/bruno-app/src/themes/dark/catppuccin-macchiato.js Adds SignalR color to Catppuccin Macchiato theme.
packages/bruno-app/src/themes/dark/catppuccin-frappe.js Adds SignalR color to Catppuccin Frappe theme.
packages/bruno-app/src/providers/ReduxStore/slices/tabs.js Updates default request pane tab selection for SignalR.
packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js Extends request body handling + WS-like response handling robustness.
packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js Adds SignalR actions (create/connect/send) and request dispatch integration.
packages/bruno-app/src/pages/Bruno/index.js Registers SignalR IPC listeners at app startup.
packages/bruno-app/src/components/SignalR/SignalRRequest.jsx Adds a standalone SignalR UI component (currently not wired in).
packages/bruno-app/src/components/SignalR/SignalRConsole.jsx Adds a basic console component for the standalone SignalR UI.
packages/bruno-app/src/components/Sidebar/NewRequest/index.js Adds SignalR to “New Request” modal and presets handling.
packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/RequestMethod/StyledWrapper.js Adds themed sidebar method color styling for SignalR.
packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/RequestMethod/index.js Adds SignalR method badge/class handling in sidebar list.
packages/bruno-app/src/components/ShareCollection/index.js Includes SignalR in “share collection” supported types display.
packages/bruno-app/src/components/RunnerResults/RunConfigurationPanel/index.jsx Disables SignalR in runner configuration (like WS/gRPC).
packages/bruno-app/src/components/ResponsePane/Timeline/index.js Treats SignalR like WS/gRPC for timeline/auth-source behavior.
packages/bruno-app/src/components/RequestTabs/RequestTab/index.js Ensures SignalR tabs are treated as request tabs and handles close behavior.
packages/bruno-app/src/components/RequestTabPanel/index.js Adds SignalR query URL + request pane rendering and URL validation.
packages/bruno-app/src/components/RequestPane/WsBody/SingleWSMessage/index.js Generalizes WS message editor to support SignalR messages.
packages/bruno-app/src/components/RequestPane/WsBody/index.js Generalizes WS body component for WS/SignalR via messagesKey.
packages/bruno-app/src/components/RequestPane/SignalRRequestPane/WSAuth/WSAuthMode/index.js Adds auth mode selector for SignalR (reusing WS auth modes).
packages/bruno-app/src/components/RequestPane/SignalRRequestPane/WSAuth/StyledWrapper.js Adds styling wrapper for SignalR auth UI.
packages/bruno-app/src/components/RequestPane/SignalRRequestPane/WSAuth/index.js Adds SignalR auth panel (reusing WS auth behavior).
packages/bruno-app/src/components/RequestPane/SignalRRequestPane/StyledWrapper.js Adds wrapper for SignalR request pane.
packages/bruno-app/src/components/RequestPane/SignalRRequestPane/index.js Adds SignalR request pane UI (messages/headers/auth/settings/docs).
packages/bruno-app/src/components/RequestPane/SignalRQueryUrl/StyledWrapper.js Adds SignalR URL bar styling + connect/disconnect UI.
packages/bruno-app/src/components/RequestPane/SignalRQueryUrl/index.js Adds SignalR URL bar component with connect/disconnect + send.
packages/bruno-app/src/components/CreateUntitledRequest/index.js Adds SignalR as an option when creating “Untitled” requests.
packages/bruno-app/src/components/CreateTransientRequest/index.js Adds SignalR as an option for transient request creation.
packages/bruno-app/src/components/CollectionSettings/Presets/index.js Adds SignalR request type to collection presets.
packages/bruno-app/package.json Adds @microsoft/signalr dependency for app package.
package-lock.json Locks new SignalR-related dependencies in root lockfile.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/bruno-app/src/components/RequestTabs/RequestTab/index.js Outdated
Comment thread packages/bruno-schema/src/collections/index.js
Comment thread packages/bruno-tests/src/signalr/index.js Outdated
Comment thread packages/bruno-electron/src/ipc/network/signalr-event-handlers.js
Comment thread packages/bruno-electron/src/ipc/network/signalr-event-handlers.js
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 85 out of 86 changed files in this pull request and generated 7 comments.

Comment thread packages/bruno-app/src/utils/signalr/connections.js
Comment thread packages/bruno-electron/src/ipc/network/signalr-event-handlers.js
Comment thread packages/bruno-app/src/utils/collections/index.js
Comment thread packages/bruno-app/src/utils/collections/index.js

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 11

🧹 Nitpick comments (17)
packages/bruno-app/src/components/RequestTabPanel/index.js (1)

600-611: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Combine identical ws-request/signalr-request branches.

renderResponsePane returns the exact same <WSResponsePane .../> JSX for both ws-request and signalr-request. A fallthrough case would remove the duplication.

♻️ Proposed fix
   const renderResponsePane = () => {
     switch (item.type) {
       case 'grpc-request':
         return <GrpcResponsePane item={item} collection={collection} response={item.response} />;
       case 'ws-request':
-        return <WSResponsePane item={item} collection={collection} response={item.response} />;
       case 'signalr-request':
         return <WSResponsePane item={item} collection={collection} response={item.response} />;
       default:
         return <ResponsePane item={item} collection={collection} response={item.response} />;
     }
   };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/bruno-app/src/components/RequestTabPanel/index.js` around lines 600
- 611, The renderResponsePane switch contains duplicate handling for ws-request
and signalr-request, both rendering the same WSResponsePane JSX. Refactor the
switch in RequestTabPanel to combine these cases with a fallthrough so both item
types share one branch, while keeping grpc-request and the default ResponsePane
behavior unchanged.
packages/bruno-app/src/components/ResponsePane/Timeline/index.js (1)

62-62: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Variable name no longer reflects its scope.

isGrpcRequest now also covers ws-request and signalr-request, which is a bit misleading — it drives which timeline item component (GrpcTimelineItem vs TimelineItem) is used for all three "binary/streaming" protocols, not just gRPC. Consider renaming to something like isStreamingProtocolRequest for clarity.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/bruno-app/src/components/ResponsePane/Timeline/index.js` at line 62,
The variable name in the timeline item selection logic is now misleading because
it checks grpc-request, ws-request, and signalr-request, not just gRPC. Rename
the boolean in the Timeline/index.js component (the one used to choose between
GrpcTimelineItem and TimelineItem) to something that reflects all supported
streaming protocols, such as isStreamingProtocolRequest, and update any nearby
references to match.
packages/bruno-app/src/themes/dark/catppuccin-macchiato.js (1)

247-247: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Use the named palette constant instead of a repeated hex literal.

signalr: '#8aadf4' duplicates colors.BLUE (same hex value) as a raw literal, while every sibling entry (grpc: colors.SKY, ws: colors.MAUVE) references the palette. Referencing colors.BLUE keeps the theme internally consistent and easier to retheme later.

♻️ Proposed fix
     grpc: colors.SKY,
     ws: colors.MAUVE,
-    signalr: '`#8aadf4`',
+    signalr: colors.BLUE,
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/bruno-app/src/themes/dark/catppuccin-macchiato.js` at line 247, The
`signalr` entry in the Catppuccin Macchiato theme is using a raw hex literal
that duplicates an existing palette value; update it to reference the named
palette constant used by the other protocol entries. In
`catppuccin-macchiato.js`, replace the `signalr` color assignment with the
shared `colors.BLUE` symbol so the theme stays consistent and avoids hardcoded
duplicates.
packages/bruno-app/src/themes/dark/dark-pastel.js (1)

252-252: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Hardcoded raw color value bypasses the theme's palette pattern.

Every other request.methods/request entry in this theme references a named colors.* constant (e.g. colors.MAGENTA, colors.PINK), but signalr uses a raw hsl(...) literal not defined anywhere in the colors object. This makes it harder to keep the palette consistent/reusable across the theme.

♻️ Proposed fix
   MAGENTA: '`#e09fd9`', // Orchid
+  SIGNALR: 'hsl(210, 55%, 45%)', // Azure glow
     ws: colors.MAGENTA, // Orchid
-    signalr: 'hsl(210, 55%, 45%)',
+    signalr: colors.SIGNALR,
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/bruno-app/src/themes/dark/dark-pastel.js` at line 252, The `signalr`
entry in the dark pastel theme is using a raw `hsl(...)` literal instead of the
shared palette constants used throughout the `request.methods`/`request` map.
Update the `dark-pastel` theme definition so `signalr` references a named
`colors.*` value from the local `colors` object, matching the existing pattern
in this theme and keeping palette values centralized and reusable.
packages/bruno-app/src/components/Sidebar/NewRequest/index.js (1)

153-206: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider consolidating the grpc/ws/signalr submit branches.

The new isSignalRRequest branch (177-191) is a byte-for-byte copy of the isWsRequest branch aside from the action creator, and closely mirrors the isGrpcRequest branch too. Three near-identical dispatch+then+catch blocks now exist; a lookup from requestType to action creator would remove the duplication and prevent future divergence (e.g. a bugfix applied to one branch but not the others).

♻️ Suggested consolidation
+    const actionByType = {
+      'grpc-request': newGrpcRequest,
+      'ws-request': newWsRequest,
+      'signalr-request': newSignalRRequest
+    };
+
     onSubmit: (values) => {
-      const isGrpcRequest = values.requestType === 'grpc-request';
-      const isWsRequest = values.requestType === 'ws-request';
-      const isSignalRRequest = values.requestType === 'signalr-request';
+      const nonHttpAction = actionByType[values.requestType];
       const filename = values.filename;
 
-      if (isGrpcRequest) {
-        dispatch(newGrpcRequest({ ... })).then(...).catch(...);
-      } else if (isSignalRRequest) {
-        dispatch(newSignalRRequest({ ... })).then(...).catch(...);
-      } else if (isWsRequest) {
-        dispatch(newWsRequest({ ... })).then(...).catch(...);
-      } else if (isEphemeral) {
+      if (nonHttpAction) {
+        dispatch(
+          nonHttpAction({
+            requestName: values.requestName,
+            requestMethod: values.requestMethod,
+            filename: filename,
+            requestType: values.requestType,
+            requestUrl: values.requestUrl,
+            collectionUid: collection.uid,
+            itemUid: item ? item.uid : null
+          })
+        )
+          .then(() => {
+            toast.success('New request created!');
+            onClose();
+          })
+          .catch((err) => toast.error(err ? err.message : 'An error occurred while adding the request'));
+      } else if (isEphemeral) {

(Verify newGrpcRequest tolerates the extra requestMethod field it currently doesn't receive, since it's unused elsewhere.)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/bruno-app/src/components/Sidebar/NewRequest/index.js` around lines
153 - 206, The onSubmit handler in NewRequest duplicates nearly identical
dispatch/then/catch logic across grpc, ws, and signalr branches, so consolidate
it by mapping requestType to the correct action creator and sharing one submit
flow. Refactor the branch selection around newGrpcRequest, newWsRequest, and
newSignalRRequest so the common toast/onClose and error handling lives in one
place, and make sure newGrpcRequest still works if the shared path includes
requestMethod.
tests/signalr/messages.spec.ts (1)

6-32: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

No test.step usage.

None of the three tests use test.step, making the generated report harder to scan for what failed within each test.

As per path instructions, "Promote the use of test.step as much as possible so the generated reports are easier to read."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/signalr/messages.spec.ts` around lines 6 - 32, The three tests in the
signalr messages suite should be wrapped in test.step blocks to make the report
easier to scan and pinpoint failures. Update each test in the signalr messages
describe block to group the navigation and assertion actions into clearly named
test.step sections, using the existing test cases and the
buildSignalRCommonLocators helper as the main entry points to structure the
steps. Keep the test behavior unchanged, but split the setup, visibility/name
checks, and delete flow into step-labeled chunks so failures are reported with
more context.

Source: Path instructions

packages/bruno-filestore/src/formats/yml/items/parseSignalRRequest.ts (1)

105-105: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Avoid as any cast for settings.

Casting signalrSettings to any bypasses type checking against Item['settings']. Consider defining a proper settings type shared with the ws-request settings shape instead.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/bruno-filestore/src/formats/yml/items/parseSignalRRequest.ts` at
line 105, The parseSignalRRequest mapping is bypassing type safety by casting
signalrSettings to any for Item['settings']. Replace that cast by introducing or
reusing a shared settings type that matches the ws-request settings shape, and
update the parseSignalRRequest return object so settings is assignable without
any. Use the signalrSettings and Item['settings'] types to guide the fix.
packages/bruno-app/src/components/CreateUntitledRequest/index.js (1)

143-160: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

SignalR menu item reuses the WebSocket icon.

websocket (Line 145) and signalr (Line 151) both render <IconPlugConnected>, so the dropdown gives users no visual way to tell the two protocols apart at a glance.

🎨 Suggested fix: use a distinct icon for SignalR
     {
       id: 'signalr',
       label: 'SignalR',
-      leftSection: <IconPlugConnected size={16} strokeWidth={2} />,
+      leftSection: <IconBroadcast size={16} strokeWidth={2} />,
       onClick: handleCreateSignalRRequest
     },
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/bruno-app/src/components/CreateUntitledRequest/index.js` around
lines 143 - 160, The SignalR dropdown item in CreateUntitledRequest currently
uses the same icon as WebSocket, so update the signalr menu entry to render a
distinct icon while keeping websocket on IconPlugConnected. Locate the menu
items array in CreateUntitledRequest and change only the SignalR item’s
leftSection so users can visually distinguish it at a glance.
packages/bruno-app/src/components/SignalR/SignalRConsole.jsx (1)

1-13: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Console output isn't themed or testable.

The <pre> element has no data-testid and isn't wrapped in a styled-component, so it won't pick up theme colors and Playwright can't reliably target it.

🎨 Suggested fix: styled wrapper + test id
 import React from 'react';
+import styled from 'styled-components';
+
+const StyledPre = styled.pre`
+  color: ${(props) => props.theme.text};
+  background: ${(props) => props.theme.bg};
+`;

 export default function SignalRConsole({
   logs = []
 }) {
   return (
-    <pre>
+    <StyledPre data-testid="signalr-console">
       {
         JSON.stringify(logs, null, 2)
       }
-    </pre>
+    </StyledPre>
   );
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/bruno-app/src/components/SignalR/SignalRConsole.jsx` around lines 1
- 13, The SignalRConsole output is a plain <pre>, so it is not theme-aware and
has no stable test hook. Update SignalRConsole to wrap the console output in a
styled-component so it can consume theme colors, and add a data-testid to the
rendered element for Playwright targeting. Keep the existing logs rendering
logic in SignalRConsole and ensure the styled wrapper is used in place of the
raw <pre>.

Source: Coding guidelines

packages/bruno-app/src/components/RequestPane/SignalRRequestPane/WSAuth/WSAuthMode/index.js (1)

1-73: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Extract a shared auth-mode selector or rename this copy. This is the same dropdown as packages/bruno-app/src/components/RequestPane/WSRequestPane/WSAuth/WSAuthMode/index.js, so duplicating it under SignalRRequestPane/WSAuth will drift. If SignalR needs the same behavior, share the component; otherwise rename the folder/component to something SignalR-specific.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/bruno-app/src/components/RequestPane/SignalRRequestPane/WSAuth/WSAuthMode/index.js`
around lines 1 - 73, The WSAuthMode component duplicates the existing auth-mode
dropdown used elsewhere, so it will drift over time. Refactor the shared logic
in WSAuthMode and MenuDropdown usage into a common reusable component or hook,
then have both request panes consume it; if SignalR truly needs different
behavior, rename WSAuthMode and its folder to a SignalR-specific symbol so the
copy is intentional and easy to distinguish.
tests/signalr/connection.spec.ts (1)

8-41: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider wrapping actions in test.step.

Connect/assert/disconnect/assert sequences aren't broken into test.step blocks, making failure reports harder to scan.

As per path instructions, "Promote the use of test.step as much as possible so the generated reports are easier to read."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/signalr/connection.spec.ts` around lines 8 - 41, Wrap the interaction
flows in these SignalR tests with test.step blocks so the
connect/assert/disconnect/assert sequences in signalr connects to hub and
signalr disconnects from hub are easier to read in reports. Use test.step around
the major actions in the connection.spec.ts cases, keeping the existing locators
from buildSignalRCommonLocators, locators.connectionControls.connect/disconnect,
and the message assertions intact while grouping each logical step separately.

Source: Path instructions

packages/bruno-app/src/utils/signalr/connections.js (1)

1-1: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

Remove the unused eventHandlers registry. SignalR callbacks are already wired and dispatched in packages/bruno-electron/src/ipc/network/signalr-event-handlers.js, so this renderer-side map is never read.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/bruno-app/src/utils/signalr/connections.js` at line 1, The
renderer-side eventHandlers registry is unused because SignalR callbacks are
already handled in signalr-event-handlers.js, so remove the Map from
connections.js and any related setup or references there. Keep the SignalR
connection logic in connections.js focused on connection management only, and
ensure no code still reads from or writes to eventHandlers after removing it.
packages/bruno-app/src/components/RequestPane/SignalRRequestPane/WSAuth/index.js (1)

43-104: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider data-testid on auth-state messages for e2e targeting.

The various inherited/unsupported-auth messages (Lines 46, 62, 74, 85-87, 94) have no data-testid, making them harder to target from the new SignalR Playwright specs.

Based on coding guidelines: "Add data-testid to testable elements for Playwright in React components".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/bruno-app/src/components/RequestPane/SignalRRequestPane/WSAuth/index.js`
around lines 43 - 104, The auth-state notice elements rendered by getAuthView in
WSAuth need stable Playwright hooks. Add data-testid attributes to each
user-facing message branch for unsupported/inherited auth states (including
OAuth 2, OAuth 1.0, inherited supported auth, and inherited not supported) so
the new SignalR e2e specs can target them reliably. Keep the existing logic and
UI text unchanged, and update the JSX in the authMode switch to attach unique
test ids to the relevant message containers.

Source: Coding guidelines

packages/bruno-tests/src/signalr/index.js (1)

126-140: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Unbounded connections map growth for abandoned negotiations.

Entries added in handleNegotiate are only removed on socket close (line 94); a negotiate call that never completes the WS handshake leaks its entry for the life of the test process.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/bruno-tests/src/signalr/index.js` around lines 126 - 140, The
`connections` map grows forever when `handleNegotiate` creates an entry but the
WebSocket handshake never finishes, since cleanup only happens on socket close.
Update `handleNegotiate` and the surrounding SignalR test server flow to add a
timeout or expiry-based cleanup for negotiated-but-unused `connectionId`
entries, and make sure the cleanup path removes the same `connections` entry
even if no socket is ever established.
packages/bruno-tests/src/index.js (1)

108-115: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

Consider exact/segment match instead of startsWith.

url.pathname.startsWith('/hub') would also match unrelated paths like /hubfoo. Low risk here since it's test fixture code, but worth tightening (e.g. /hub or /hub/) to avoid accidentally misrouting future endpoints.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/bruno-tests/src/index.js` around lines 108 - 115, The upgrade
routing in the server.on('upgrade') handler is using a broad pathname check that
can misroute unrelated paths. Tighten the condition in the upgrade callback by
matching only the intended /hub endpoint (and optionally its subpaths) instead
of using url.pathname.startsWith, so signalrRouter is only selected for exact
hub routes and wsRouter handles everything else.
tests/signalr/persistence.spec.ts (1)

25-74: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

No test.step usage; consider wrapping Arrange/Act/Assert phases.

Neither test uses test.step, making generated reports harder to scan for the persistence flow (edit → save → restart → verify).

As per path instructions, "Promote the use of test.step as much as possible so the generated reports are easier to read."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/signalr/persistence.spec.ts` around lines 25 - 74, The SignalR
persistence tests are missing `test.step` wrappers, making the
edit/save/restart/verify flow harder to read in reports. Update the `save new
signalr url` and `save new message name` tests in `persistence.spec.ts` to wrap
Arrange, Act, and Assert sections in `test.step` blocks while keeping the
existing helpers like `buildSignalRCommonLocators`, `isRequestSaved`, and
`restartApp` in place.

Source: Path instructions

packages/bruno-app/src/themes/light/catppuccin-latte.js (1)

245-248: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Use colors.SKY instead of duplicating its hex literal.

signalr: '#04a5e5' hardcodes the same value as colors.SKY (line 18) instead of referencing the constant, breaking the file's convention of using named palette colors everywhere else.

🎨 Proposed fix
     grpc: colors.SKY,
     ws: colors.MAUVE,
-    signalr: '`#04a5e5`',
+    signalr: colors.SKY,
     gql: colors.PINK
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/bruno-app/src/themes/light/catppuccin-latte.js` around lines 245 -
248, The theme palette in the catppuccin-latte color map is duplicating the SKY
hex value for signalr instead of using the shared constant. Update the object in
the light theme palette so signalr references colors.SKY, matching the existing
convention used by grpc, ws, and the rest of the file.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@packages/bruno-app/src/components/RequestPane/SignalRRequestPane/WSAuth/index.js`:
- Around line 23-25: The WSAuth save handler is returning the thunk from
saveRequest instead of dispatching it, so auth changes never persist when
BasicAuth/BearerAuth/ApiKeyAuth call save() directly from onSave. Update the
save function in SignalRRequestPane/WSAuth to dispatch saveRequest(item.uid,
collection.uid) before returning, using the existing saveRequest and save
symbols to preserve the current call flow.

In `@packages/bruno-app/src/components/SignalR/SignalRRequest.jsx`:
- Around line 5-19: The connect flow in SignalRRequest is missing both failure
handling and a guard against duplicate connections. Update connect() to
short-circuit when connection already exists/has already been started, and wrap
createSignalRConnection/conn.start in error handling so failures are caught and
reflected in state. Make sure setConnection and setLogs are updated for both
success and failure paths, and use the existing SignalRRequest, connect, and
conn.start symbols to keep the fix localized.

In `@packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js`:
- Around line 553-582: The signalrConnectOnly thunk is wrapping an async
executor in new Promise, which can swallow synchronous throws and leave callers
waiting forever. Remove the async Promise executor pattern and make
signalrConnectOnly handle failures by rejecting/returning the connectSignalR
chain properly, or explicitly wrapping the synchronous setup work in try/catch
and calling reject on error. Use the signalrConnectOnly,
getGlobalEnvironmentVariables, and findEnvironmentInCollection flow to ensure
any thrown error is surfaced through the returned promise and the toast/error
path.

In `@packages/bruno-app/src/utils/network/index.js`:
- Around line 382-390: The JSON parsing in the selected message args flow is
swallowing malformed input and defaulting to empty args, which hides invalid
message bodies. Update the parsing logic around selectedMsg.content so that
malformed JSON is surfaced as an error instead of continuing with []. Use the
existing args preparation path to either reject/throw on parse failure or notify
the caller before the hub invocation, and keep the array-wrapping behavior only
for valid parsed values in this network/index.js flow.

In `@packages/bruno-app/src/utils/network/signalr-event-listeners.js`:
- Around line 52-63: The ipcRenderer.on handler in signalr-event-listeners.js
can throw if JSON.stringify is called on non-serializable args, so update the
main:signalr:event listener to safely serialize event payloads before
dispatching wsResponseReceived. Add a guarded serialization path around the
eventData.message construction in the callback, falling back to a safe string
when stringify fails so the renderer does not crash on circular references,
BigInt, or similar values.

In `@packages/bruno-app/src/utils/signalr/connections.js`:
- Around line 45-66: `registerSignalRHandler` and `removeSignalRHandler` trigger
`ipcRenderer.invoke(...)` without exposing the returned promise, so callers
cannot handle failures. Update both functions in `connections.js` to return the
`ipcRenderer.invoke('renderer:signalr:register-handler', ...)` and
`ipcRenderer.invoke('renderer:signalr:remove-handler', ...)` promises, matching
the pattern used by the other exported SignalR helpers. Keep the event handler
map updates as-is, but ensure the IPC call is returned so rejections can be
awaited or caught by the caller.

In `@packages/bruno-electron/src/ipc/collection.js`:
- Line 1182: `renderer:resequence-items` is using a request type list that now
includes SignalR, but `REQUEST_TYPES` in the shared constants does not, so
SignalR items lose their persisted `seq` updates. Update the shared request-type
source of truth used by `collection.js` and `constants.js` to include
`signalr-request`, or consolidate both checks to use the same exported constant
so the type lists cannot drift again.

In `@packages/bruno-electron/src/ipc/network/signalr-event-handlers.js`:
- Around line 81-161: The renderer-side IPC handlers
`renderer:signalr:register-handler` and `renderer:signalr:remove-handler` in
`signalr-event-handlers` are no longer effective because
`connection._invokeClientMethod` now intercepts all incoming invocations and
bypasses `state.connection.on(...)`. Either remove these dead handlers entirely
or update the `_invokeClientMethod` override to dispatch through the existing
handler table so `register-handler`/`remove-handler` still work as intended.

In `@packages/bruno-filestore/src/formats/yml/items/stringifySignalRRequest.ts`:
- Around line 87-94: The SignalR settings serialization in
stringifySignalRRequest is incorrectly writing default 0 values for missing
timeout and keepAliveInterval fields whenever item.settings exists. Update the
item.settings handling so ocRequest.settings only includes timeout and
keepAliveInterval when those properties are actually present and valid on
signalrSettings, instead of coercing undefined to 0; keep the logic localized to
stringifySignalRRequest and its ocRequest.settings assignment.

In `@tests/signalr/connection.spec.ts`:
- Around line 9-10: The spec is bypassing the page-module pattern by using
inline locators instead of centralized builders/actions. Move the sidebar
collection and collections-title interactions into the appropriate page module
under tests/utils/page/*, then update this spec to consume those locator
builders via buildCommonLocators or a feature-specific page module; also replace
any other direct page.locator/page.getByTestId usages in this file the same way.

In `@tests/signalr/messages.spec.ts`:
- Around line 8-9: The SignalR spec is bypassing the page-module locator pattern
by using direct page selectors in the test body. Update the affected
assertions/actions in messages.spec.ts to go through buildSignalRCommonLocators
or a feature-specific page module instead of calling page.locator and
page.getByTestId directly; add any missing accessors to
buildCommonLocators/tests/utils/page/locators.ts so the spec can consume them
consistently.

---

Nitpick comments:
In `@packages/bruno-app/src/components/CreateUntitledRequest/index.js`:
- Around line 143-160: The SignalR dropdown item in CreateUntitledRequest
currently uses the same icon as WebSocket, so update the signalr menu entry to
render a distinct icon while keeping websocket on IconPlugConnected. Locate the
menu items array in CreateUntitledRequest and change only the SignalR item’s
leftSection so users can visually distinguish it at a glance.

In
`@packages/bruno-app/src/components/RequestPane/SignalRRequestPane/WSAuth/index.js`:
- Around line 43-104: The auth-state notice elements rendered by getAuthView in
WSAuth need stable Playwright hooks. Add data-testid attributes to each
user-facing message branch for unsupported/inherited auth states (including
OAuth 2, OAuth 1.0, inherited supported auth, and inherited not supported) so
the new SignalR e2e specs can target them reliably. Keep the existing logic and
UI text unchanged, and update the JSX in the authMode switch to attach unique
test ids to the relevant message containers.

In
`@packages/bruno-app/src/components/RequestPane/SignalRRequestPane/WSAuth/WSAuthMode/index.js`:
- Around line 1-73: The WSAuthMode component duplicates the existing auth-mode
dropdown used elsewhere, so it will drift over time. Refactor the shared logic
in WSAuthMode and MenuDropdown usage into a common reusable component or hook,
then have both request panes consume it; if SignalR truly needs different
behavior, rename WSAuthMode and its folder to a SignalR-specific symbol so the
copy is intentional and easy to distinguish.

In `@packages/bruno-app/src/components/RequestTabPanel/index.js`:
- Around line 600-611: The renderResponsePane switch contains duplicate handling
for ws-request and signalr-request, both rendering the same WSResponsePane JSX.
Refactor the switch in RequestTabPanel to combine these cases with a fallthrough
so both item types share one branch, while keeping grpc-request and the default
ResponsePane behavior unchanged.

In `@packages/bruno-app/src/components/ResponsePane/Timeline/index.js`:
- Line 62: The variable name in the timeline item selection logic is now
misleading because it checks grpc-request, ws-request, and signalr-request, not
just gRPC. Rename the boolean in the Timeline/index.js component (the one used
to choose between GrpcTimelineItem and TimelineItem) to something that reflects
all supported streaming protocols, such as isStreamingProtocolRequest, and
update any nearby references to match.

In `@packages/bruno-app/src/components/Sidebar/NewRequest/index.js`:
- Around line 153-206: The onSubmit handler in NewRequest duplicates nearly
identical dispatch/then/catch logic across grpc, ws, and signalr branches, so
consolidate it by mapping requestType to the correct action creator and sharing
one submit flow. Refactor the branch selection around newGrpcRequest,
newWsRequest, and newSignalRRequest so the common toast/onClose and error
handling lives in one place, and make sure newGrpcRequest still works if the
shared path includes requestMethod.

In `@packages/bruno-app/src/components/SignalR/SignalRConsole.jsx`:
- Around line 1-13: The SignalRConsole output is a plain <pre>, so it is not
theme-aware and has no stable test hook. Update SignalRConsole to wrap the
console output in a styled-component so it can consume theme colors, and add a
data-testid to the rendered element for Playwright targeting. Keep the existing
logs rendering logic in SignalRConsole and ensure the styled wrapper is used in
place of the raw <pre>.

In `@packages/bruno-app/src/themes/dark/catppuccin-macchiato.js`:
- Line 247: The `signalr` entry in the Catppuccin Macchiato theme is using a raw
hex literal that duplicates an existing palette value; update it to reference
the named palette constant used by the other protocol entries. In
`catppuccin-macchiato.js`, replace the `signalr` color assignment with the
shared `colors.BLUE` symbol so the theme stays consistent and avoids hardcoded
duplicates.

In `@packages/bruno-app/src/themes/dark/dark-pastel.js`:
- Line 252: The `signalr` entry in the dark pastel theme is using a raw
`hsl(...)` literal instead of the shared palette constants used throughout the
`request.methods`/`request` map. Update the `dark-pastel` theme definition so
`signalr` references a named `colors.*` value from the local `colors` object,
matching the existing pattern in this theme and keeping palette values
centralized and reusable.

In `@packages/bruno-app/src/themes/light/catppuccin-latte.js`:
- Around line 245-248: The theme palette in the catppuccin-latte color map is
duplicating the SKY hex value for signalr instead of using the shared constant.
Update the object in the light theme palette so signalr references colors.SKY,
matching the existing convention used by grpc, ws, and the rest of the file.

In `@packages/bruno-app/src/utils/signalr/connections.js`:
- Line 1: The renderer-side eventHandlers registry is unused because SignalR
callbacks are already handled in signalr-event-handlers.js, so remove the Map
from connections.js and any related setup or references there. Keep the SignalR
connection logic in connections.js focused on connection management only, and
ensure no code still reads from or writes to eventHandlers after removing it.

In `@packages/bruno-filestore/src/formats/yml/items/parseSignalRRequest.ts`:
- Line 105: The parseSignalRRequest mapping is bypassing type safety by casting
signalrSettings to any for Item['settings']. Replace that cast by introducing or
reusing a shared settings type that matches the ws-request settings shape, and
update the parseSignalRRequest return object so settings is assignable without
any. Use the signalrSettings and Item['settings'] types to guide the fix.

In `@packages/bruno-tests/src/index.js`:
- Around line 108-115: The upgrade routing in the server.on('upgrade') handler
is using a broad pathname check that can misroute unrelated paths. Tighten the
condition in the upgrade callback by matching only the intended /hub endpoint
(and optionally its subpaths) instead of using url.pathname.startsWith, so
signalrRouter is only selected for exact hub routes and wsRouter handles
everything else.

In `@packages/bruno-tests/src/signalr/index.js`:
- Around line 126-140: The `connections` map grows forever when
`handleNegotiate` creates an entry but the WebSocket handshake never finishes,
since cleanup only happens on socket close. Update `handleNegotiate` and the
surrounding SignalR test server flow to add a timeout or expiry-based cleanup
for negotiated-but-unused `connectionId` entries, and make sure the cleanup path
removes the same `connections` entry even if no socket is ever established.

In `@tests/signalr/connection.spec.ts`:
- Around line 8-41: Wrap the interaction flows in these SignalR tests with
test.step blocks so the connect/assert/disconnect/assert sequences in signalr
connects to hub and signalr disconnects from hub are easier to read in reports.
Use test.step around the major actions in the connection.spec.ts cases, keeping
the existing locators from buildSignalRCommonLocators,
locators.connectionControls.connect/disconnect, and the message assertions
intact while grouping each logical step separately.

In `@tests/signalr/messages.spec.ts`:
- Around line 6-32: The three tests in the signalr messages suite should be
wrapped in test.step blocks to make the report easier to scan and pinpoint
failures. Update each test in the signalr messages describe block to group the
navigation and assertion actions into clearly named test.step sections, using
the existing test cases and the buildSignalRCommonLocators helper as the main
entry points to structure the steps. Keep the test behavior unchanged, but split
the setup, visibility/name checks, and delete flow into step-labeled chunks so
failures are reported with more context.

In `@tests/signalr/persistence.spec.ts`:
- Around line 25-74: The SignalR persistence tests are missing `test.step`
wrappers, making the edit/save/restart/verify flow harder to read in reports.
Update the `save new signalr url` and `save new message name` tests in
`persistence.spec.ts` to wrap Arrange, Act, and Assert sections in `test.step`
blocks while keeping the existing helpers like `buildSignalRCommonLocators`,
`isRequestSaved`, and `restartApp` in place.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 297884e3-7ac9-4f15-ab4b-360dee11af77

📥 Commits

Reviewing files that changed from the base of the PR and between 4592d2c and 7c64168.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (85)
  • packages/bruno-app/package.json
  • packages/bruno-app/src/components/CollectionSettings/Presets/index.js
  • packages/bruno-app/src/components/CreateTransientRequest/index.js
  • packages/bruno-app/src/components/CreateUntitledRequest/index.js
  • packages/bruno-app/src/components/RequestPane/SignalRQueryUrl/StyledWrapper.js
  • packages/bruno-app/src/components/RequestPane/SignalRQueryUrl/index.js
  • packages/bruno-app/src/components/RequestPane/SignalRRequestPane/StyledWrapper.js
  • packages/bruno-app/src/components/RequestPane/SignalRRequestPane/WSAuth/StyledWrapper.js
  • packages/bruno-app/src/components/RequestPane/SignalRRequestPane/WSAuth/WSAuthMode/index.js
  • packages/bruno-app/src/components/RequestPane/SignalRRequestPane/WSAuth/index.js
  • packages/bruno-app/src/components/RequestPane/SignalRRequestPane/index.js
  • packages/bruno-app/src/components/RequestPane/WsBody/SingleWSMessage/index.js
  • packages/bruno-app/src/components/RequestPane/WsBody/index.js
  • packages/bruno-app/src/components/RequestTabPanel/index.js
  • packages/bruno-app/src/components/RequestTabs/RequestTab/index.js
  • packages/bruno-app/src/components/ResponsePane/Timeline/index.js
  • packages/bruno-app/src/components/RunnerResults/RunConfigurationPanel/index.jsx
  • packages/bruno-app/src/components/ShareCollection/index.js
  • packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/RequestMethod/StyledWrapper.js
  • packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/RequestMethod/index.js
  • packages/bruno-app/src/components/Sidebar/NewRequest/index.js
  • packages/bruno-app/src/components/SignalR/SignalRConsole.jsx
  • packages/bruno-app/src/components/SignalR/SignalRRequest.jsx
  • packages/bruno-app/src/pages/Bruno/index.js
  • packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js
  • packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js
  • packages/bruno-app/src/providers/ReduxStore/slices/tabs.js
  • packages/bruno-app/src/themes/dark/catppuccin-frappe.js
  • packages/bruno-app/src/themes/dark/catppuccin-macchiato.js
  • packages/bruno-app/src/themes/dark/catppuccin-mocha.js
  • packages/bruno-app/src/themes/dark/dark-monochrome.js
  • packages/bruno-app/src/themes/dark/dark-pastel.js
  • packages/bruno-app/src/themes/dark/dark.js
  • packages/bruno-app/src/themes/dark/nord.js
  • packages/bruno-app/src/themes/dark/vscode.js
  • packages/bruno-app/src/themes/light/catppuccin-latte.js
  • packages/bruno-app/src/themes/light/light-monochrome.js
  • packages/bruno-app/src/themes/light/light-pastel.js
  • packages/bruno-app/src/themes/light/light.js
  • packages/bruno-app/src/themes/light/vscode.js
  • packages/bruno-app/src/themes/schema/oss.js
  • packages/bruno-app/src/utils/collections/emptyStateRequest.js
  • packages/bruno-app/src/utils/collections/export.js
  • packages/bruno-app/src/utils/collections/index.js
  • packages/bruno-app/src/utils/common/constants.js
  • packages/bruno-app/src/utils/importers/common.js
  • packages/bruno-app/src/utils/network/index.js
  • packages/bruno-app/src/utils/network/signalr-event-listeners.js
  • packages/bruno-app/src/utils/signalr/connections.js
  • packages/bruno-app/src/utils/signalr/signalr-client.js
  • packages/bruno-app/src/utils/snapshot/index.js
  • packages/bruno-app/src/utils/tabs/index.js
  • packages/bruno-converters/src/opencollection/items/index.ts
  • packages/bruno-converters/src/opencollection/items/signalr.ts
  • packages/bruno-converters/src/opencollection/types.ts
  • packages/bruno-converters/tests/opencollection/signalr.spec.js
  • packages/bruno-electron/package.json
  • packages/bruno-electron/src/ipc/collection.js
  • packages/bruno-electron/src/ipc/network/index.js
  • packages/bruno-electron/src/ipc/network/signalr-event-handlers.js
  • packages/bruno-filestore/src/formats/bru/index.ts
  • packages/bruno-filestore/src/formats/yml/items/parseSignalRRequest.ts
  • packages/bruno-filestore/src/formats/yml/items/stringifySignalRRequest.spec.ts
  • packages/bruno-filestore/src/formats/yml/items/stringifySignalRRequest.ts
  • packages/bruno-filestore/src/formats/yml/parseItem.ts
  • packages/bruno-filestore/src/formats/yml/stringifyItem.ts
  • packages/bruno-lang/v2/src/bruToJson.js
  • packages/bruno-lang/v2/src/jsonToBru.js
  • packages/bruno-schema-types/src/collection/item.ts
  • packages/bruno-schema-types/src/requests/index.ts
  • packages/bruno-schema-types/src/requests/signalr.ts
  • packages/bruno-schema/src/collections/index.js
  • packages/bruno-tests/src/index.js
  • packages/bruno-tests/src/signalr/index.js
  • tests/signalr/connection.spec.ts
  • tests/signalr/fixtures/collection/bruno.json
  • tests/signalr/fixtures/collection/collection.bru
  • tests/signalr/fixtures/collection/signalr-headers-auth.bru
  • tests/signalr/fixtures/collection/signalr-multi-msg.bru
  • tests/signalr/fixtures/collection/signalr-persistence.bru
  • tests/signalr/fixtures/collection/signalr-test.bru
  • tests/signalr/init-user-data/preferences.json
  • tests/signalr/messages.spec.ts
  • tests/signalr/persistence.spec.ts
  • tests/utils/page/locators.ts

Comment thread packages/bruno-app/src/components/SignalR/SignalRRequest.jsx
Comment thread packages/bruno-app/src/utils/network/index.js
Comment thread packages/bruno-app/src/utils/network/signalr-event-listeners.js
Comment thread packages/bruno-electron/src/ipc/collection.js
Comment thread packages/bruno-electron/src/ipc/network/signalr-event-handlers.js
Comment thread tests/signalr/connection.spec.ts Outdated
Comment thread tests/signalr/messages.spec.ts Outdated
- Remove unused wsResponseReceived import from collections actions
- Add protocol field to signalrRequestSchema Yup validation
- Fix multi-frame SignalR message handling in test server
- Return empty string instead of null in accessTokenFactory
- Forward full request context for variable interpolation and custom headers
- Preserve original _invokeClientMethod for connection.on() compatibility
- Guard signalr body map with Array.isArray null safety
- Fix user-facing auth text from 'WebSockets' to 'SignalR'
Replace white-space: nowrap with white-space: normal and add
max-width: 300px + overflow-wrap: break-word on infotip-text
across SignalR, WS, and gRPC query URL tooltips
.infotip-text had z-index: 1, matching the active tab z-index,
causing tooltips to render behind the tab bar. Increase to 10.
Tooltip was positioned above the button (bottom: 34px) and
clipped by the parent container overflow-hidden. Changed to
top: calc(100% + 8px) with arrow flipped to point upward.
- WSAuth/index.js: dispatch(saveRequest) to persist auth changes
- SignalRRequest.jsx: add connection guard and try/catch in connect()
- actions.js: wrap async executor in try/catch with reject(err)
- network/index.js: throw Error on malformed JSON instead of silent args=[]
- signalr-event-listeners.js: wrap JSON.stringify in try/catch
- connections.js: return ipcRenderer.invoke() promise with .catch()
- constants.js: add 'signalr-request' to REQUEST_TYPES
- stringifySignalRRequest.ts: only emit timeout/keepAliveInterval when present
- connection.spec.ts, messages.spec.ts: use locator builders instead of inline selectors
- locators.ts: add sidebarCollectionName() and requestByTitle() accessors

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
packages/bruno-app/src/components/SignalR/SignalRRequest.jsx (3)

37-44: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Missing data-testid on input/buttons.

The PR adds e2e coverage for SignalR; without test ids on the URL input and Connect/Disconnect buttons, Playwright specs are stuck using brittle selectors.

🔧 Proposed fix
       <input
         type="text"
         placeholder="SignalR URL"
         value={url}
         onChange={(e) => setUrl(e.target.value)}
+        data-testid="signalr-url-input"
       />
-      <button onClick={connect}>Connect</button>
-      <button onClick={disconnect}>Disconnect</button>
+      <button onClick={connect} data-testid="signalr-connect-button">Connect</button>
+      <button onClick={disconnect} data-testid="signalr-disconnect-button">Disconnect</button>
As per coding guidelines, "Add `data-testid` to testable elements for Playwright in React components."
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/bruno-app/src/components/SignalR/SignalRRequest.jsx` around lines 37
- 44, The SignalR request controls in SignalRRequest need stable Playwright
hooks: add data-testid attributes to the URL input and the Connect/Disconnect
buttons so e2e tests do not rely on brittle selectors. Update the JSX in
SignalRRequest.jsx where the input element and the two button elements are
rendered, using clear identifiers for each control, and keep the existing
behavior unchanged.

Source: Coding guidelines


25-31: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

disconnect() lacks the error handling connect() just gained.

If connection.stop() rejects, the rejection is unhandled and setConnection(null) never runs — the UI keeps referencing a connection that may already be broken.

🔧 Proposed fix
   async function disconnect() {
     if (connection) {
-      await connection.stop();
-      setConnection(null);
-      setLogs((prev) => [...prev, { event: 'SYSTEM', data: 'Disconnected' }]);
+      try {
+        await connection.stop();
+        setLogs((prev) => [...prev, { event: 'SYSTEM', data: 'Disconnected' }]);
+      } catch (err) {
+        setLogs((prev) => [...prev, { event: 'SYSTEM', data: `Disconnect failed: ${err?.message || err}` }]);
+      } finally {
+        setConnection(null);
+      }
     }
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/bruno-app/src/components/SignalR/SignalRRequest.jsx` around lines 25
- 31, The disconnect() flow in SignalRRequest should handle failures from
connection.stop() the same way connect() now does. Wrap the stop/cleanup logic
in a try/catch, and if stop rejects, still clear the stale connection state with
setConnection(null) and record an appropriate log entry using the existing
setLogs pattern. Keep the fix localized to disconnect() and preserve the current
success behavior when stop() completes normally.

5-31: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Connection lifecycle should live in a custom hook, and there's no unmount cleanup.

connect/disconnect/connection state are business logic + side effects embedded directly in the component. There's also no useEffect to stop an active connection on unmount, so navigating away while connected leaks the underlying WebSocket. Extracting a useSignalRConnection(url) hook would centralize the lifecycle and let you add the missing cleanup effect in one place.

As per coding guidelines, "MUST: Prefer custom hooks for business logic, data fetching, and side-effects in React" and "MUST: Avoid useEffect unless absolutely needed" (unmount cleanup of an external connection qualifies as one of those necessary cases).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/bruno-app/src/components/SignalR/SignalRRequest.jsx` around lines 5
- 31, The SignalR connection lifecycle is implemented directly inside
SignalRRequest instead of a reusable hook, and the active connection is not
cleaned up on unmount. Move the connection state and the connect/disconnect
logic from SignalRRequest into a custom hook such as useSignalRConnection(url),
keeping the component focused on rendering and consuming the hook’s API. Add the
necessary cleanup in that hook with a useEffect teardown so an active connection
is stopped when the component unmounts or the URL changes, and ensure the hook
owns createSignalRConnection, conn.start(), and connection.stop().

Source: Coding guidelines

🧹 Nitpick comments (1)
packages/bruno-electron/src/ipc/network/signalr-event-handlers.js (1)

104-115: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Monkey-patching private SignalR internals is fragile across upgrades.

_invokeClientMethod, _sendWithProtocol, and _createCompletionMessage are underscore-prefixed private members in @microsoft/signalr's source, not part of the public API/semver contract. A minor library bump could silently break this override.

Consider pinning @microsoft/signalr to an exact version and adding a regression test that exercises server→client invocation forwarding, so an upgrade that changes this internal surface is caught immediately.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/bruno-electron/src/ipc/network/signalr-event-handlers.js` around
lines 104 - 115, The SignalR event forwarding logic in signalr-event-handlers.js
relies on private underscore-prefixed internals like _invokeClientMethod,
_sendWithProtocol, and _createCompletionMessage, which is fragile across
`@microsoft/signalr` upgrades. Update the connection-hooking approach to avoid
depending on those internals where possible, and if the override must remain,
pin `@microsoft/signalr` to an exact version and add a regression test around the
server→client invocation path in the same handler flow so API changes fail
loudly.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/bruno-app/src/components/SignalR/SignalRRequest.jsx`:
- Around line 9-24: The current guard in SignalRRequest's connect function
relies on React state, so rapid repeated clicks can start multiple connections
before setConnection runs. Add a ref-based in-flight lock in SignalRRequest and
check it at the start of connect to prevent duplicate conn.start() calls, then
clear it in both success and catch paths (or a finally block) while keeping the
existing connection state update and ReceiveMessage listener logic intact.

In `@packages/bruno-electron/src/ipc/network/signalr-event-handlers.js`:
- Line 106: `main:signalr:event` is being emitted twice for registered SignalR
handlers because `_invokeClientMethod` broadcasts every invocation and
`register-handler` also forwards the same target through `connection.on(...)`.
Update `signalr-event-handlers.js` so a given invocation goes through only one
path: either keep the generic `sendEvent('main:signalr:event', ...)` broadcast
in `_invokeClientMethod` and prevent the registered handler from re-emitting, or
skip the broadcast when `register-handler` has a matching target. Use the
existing `_invokeClientMethod`, `register-handler`, and `sendEvent` flow to gate
the duplicate emission.

---

Outside diff comments:
In `@packages/bruno-app/src/components/SignalR/SignalRRequest.jsx`:
- Around line 37-44: The SignalR request controls in SignalRRequest need stable
Playwright hooks: add data-testid attributes to the URL input and the
Connect/Disconnect buttons so e2e tests do not rely on brittle selectors. Update
the JSX in SignalRRequest.jsx where the input element and the two button
elements are rendered, using clear identifiers for each control, and keep the
existing behavior unchanged.
- Around line 25-31: The disconnect() flow in SignalRRequest should handle
failures from connection.stop() the same way connect() now does. Wrap the
stop/cleanup logic in a try/catch, and if stop rejects, still clear the stale
connection state with setConnection(null) and record an appropriate log entry
using the existing setLogs pattern. Keep the fix localized to disconnect() and
preserve the current success behavior when stop() completes normally.
- Around line 5-31: The SignalR connection lifecycle is implemented directly
inside SignalRRequest instead of a reusable hook, and the active connection is
not cleaned up on unmount. Move the connection state and the connect/disconnect
logic from SignalRRequest into a custom hook such as useSignalRConnection(url),
keeping the component focused on rendering and consuming the hook’s API. Add the
necessary cleanup in that hook with a useEffect teardown so an active connection
is stopped when the component unmounts or the URL changes, and ensure the hook
owns createSignalRConnection, conn.start(), and connection.stop().

---

Nitpick comments:
In `@packages/bruno-electron/src/ipc/network/signalr-event-handlers.js`:
- Around line 104-115: The SignalR event forwarding logic in
signalr-event-handlers.js relies on private underscore-prefixed internals like
_invokeClientMethod, _sendWithProtocol, and _createCompletionMessage, which is
fragile across `@microsoft/signalr` upgrades. Update the connection-hooking
approach to avoid depending on those internals where possible, and if the
override must remain, pin `@microsoft/signalr` to an exact version and add a
regression test around the server→client invocation path in the same handler
flow so API changes fail loudly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 33c5ddb8-6991-48c4-b564-2991889357af

📥 Commits

Reviewing files that changed from the base of the PR and between 7c64168 and 4cedc1e.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (26)
  • packages/bruno-app/src/components/RequestPane/GrpcQueryUrl/StyledWrapper.js
  • packages/bruno-app/src/components/RequestPane/SignalRQueryUrl/StyledWrapper.js
  • packages/bruno-app/src/components/RequestPane/SignalRRequestPane/WSAuth/index.js
  • packages/bruno-app/src/components/RequestPane/WsBody/SingleWSMessage/index.js
  • packages/bruno-app/src/components/RequestPane/WsBody/index.js
  • packages/bruno-app/src/components/RequestPane/WsQueryUrl/StyledWrapper.js
  • packages/bruno-app/src/components/RequestTabPanel/index.js
  • packages/bruno-app/src/components/SignalR/SignalRRequest.jsx
  • packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js
  • packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js
  • packages/bruno-app/src/utils/collections/index.js
  • packages/bruno-app/src/utils/importers/common.js
  • packages/bruno-app/src/utils/network/index.js
  • packages/bruno-app/src/utils/network/signalr-event-listeners.js
  • packages/bruno-app/src/utils/signalr/connections.js
  • packages/bruno-electron/src/ipc/network/signalr-event-handlers.js
  • packages/bruno-electron/src/utils/constants.js
  • packages/bruno-filestore/src/formats/yml/items/stringifySignalRRequest.ts
  • packages/bruno-lang/v2/src/bruToJson.js
  • packages/bruno-lang/v2/src/jsonToBru.js
  • packages/bruno-schema-types/src/collection/item.ts
  • packages/bruno-schema/src/collections/index.js
  • packages/bruno-tests/src/signalr/index.js
  • tests/signalr/connection.spec.ts
  • tests/signalr/messages.spec.ts
  • tests/utils/page/locators.ts
✅ Files skipped from review due to trivial changes (1)
  • packages/bruno-electron/src/utils/constants.js
🚧 Files skipped from review as they are similar to previous changes (20)
  • tests/signalr/messages.spec.ts
  • packages/bruno-schema-types/src/collection/item.ts
  • packages/bruno-app/src/components/RequestPane/WsBody/index.js
  • tests/utils/page/locators.ts
  • packages/bruno-schema/src/collections/index.js
  • tests/signalr/connection.spec.ts
  • packages/bruno-app/src/utils/importers/common.js
  • packages/bruno-app/src/utils/network/signalr-event-listeners.js
  • packages/bruno-app/src/utils/network/index.js
  • packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js
  • packages/bruno-app/src/components/RequestTabPanel/index.js
  • packages/bruno-lang/v2/src/bruToJson.js
  • packages/bruno-app/src/components/RequestPane/SignalRQueryUrl/StyledWrapper.js
  • packages/bruno-filestore/src/formats/yml/items/stringifySignalRRequest.ts
  • packages/bruno-lang/v2/src/jsonToBru.js
  • packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js
  • packages/bruno-app/src/utils/collections/index.js
  • packages/bruno-tests/src/signalr/index.js
  • packages/bruno-app/src/components/RequestPane/SignalRRequestPane/WSAuth/index.js
  • packages/bruno-app/src/components/RequestPane/WsBody/SingleWSMessage/index.js

Comment on lines +9 to +24
async function connect() {
if (connection) return;
const conn = createSignalRConnection({
url
});
conn.on('ReceiveMessage', (...arg) => {
setLogs((prevLogs) => [...prevLogs, { type: 'ReceiveMessage', data: arg }]);
});
try {
await conn.start();
setConnection(conn);
setLogs((prev) => [...prev, { event: 'SYSTEM', data: 'Connected' }]);
} catch (err) {
setLogs((prev) => [...prev, { event: 'SYSTEM', data: `Connection failed: ${err?.message || err}` }]);
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Guard is state-based, so rapid double-clicks can still open two connections.

if (connection) return checks React state, which won't reflect the in-flight connect() call until setConnection runs after await conn.start() resolves. A second click before that completes bypasses the guard and spins up a duplicate connection.

🔧 Proposed fix using a ref-based lock
+  const isConnectingRef = useRef(false);
+
   async function connect() {
-    if (connection) return;
+    if (connection || isConnectingRef.current) return;
+    isConnectingRef.current = true;
     const conn = createSignalRConnection({
       url
     });
     conn.on('ReceiveMessage', (...arg) => {
       setLogs((prevLogs) => [...prevLogs, { type: 'ReceiveMessage', data: arg }]);
     });
     try {
       await conn.start();
       setConnection(conn);
       setLogs((prev) => [...prev, { event: 'SYSTEM', data: 'Connected' }]);
     } catch (err) {
       setLogs((prev) => [...prev, { event: 'SYSTEM', data: `Connection failed: ${err?.message || err}` }]);
+    } finally {
+      isConnectingRef.current = false;
     }
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async function connect() {
if (connection) return;
const conn = createSignalRConnection({
url
});
conn.on('ReceiveMessage', (...arg) => {
setLogs((prevLogs) => [...prevLogs, { type: 'ReceiveMessage', data: arg }]);
});
try {
await conn.start();
setConnection(conn);
setLogs((prev) => [...prev, { event: 'SYSTEM', data: 'Connected' }]);
} catch (err) {
setLogs((prev) => [...prev, { event: 'SYSTEM', data: `Connection failed: ${err?.message || err}` }]);
}
}
const isConnectingRef = useRef(false);
async function connect() {
if (connection || isConnectingRef.current) return;
isConnectingRef.current = true;
const conn = createSignalRConnection({
url
});
conn.on('ReceiveMessage', (...arg) => {
setLogs((prevLogs) => [...prevLogs, { type: 'ReceiveMessage', data: arg }]);
});
try {
await conn.start();
setConnection(conn);
setLogs((prev) => [...prev, { event: 'SYSTEM', data: 'Connected' }]);
} catch (err) {
setLogs((prev) => [...prev, { event: 'SYSTEM', data: `Connection failed: ${err?.message || err}` }]);
} finally {
isConnectingRef.current = false;
}
}
🧰 Tools
🪛 ast-grep (0.44.1)

[warning] 18-18: Avoid using the initial state variable in setState
Context: setConnection(conn)
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(setstate-same-var)


[error] 18-18: React's useState should not be directly called
Context: setConnection(conn)
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(usestate-direct-usage)


[error] 19-19: React's useState should not be directly called
Context: setLogs((prev) => [...prev, { event: 'SYSTEM', data: 'Connected' }])
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(usestate-direct-usage)


[error] 21-21: React's useState should not be directly called
Context: setLogs((prev) => [...prev, { event: 'SYSTEM', data: Connection failed: ${err?.message || err} }])
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(usestate-direct-usage)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/bruno-app/src/components/SignalR/SignalRRequest.jsx` around lines 9
- 24, The current guard in SignalRRequest's connect function relies on React
state, so rapid repeated clicks can start multiple connections before
setConnection runs. Add a ref-based in-flight lock in SignalRRequest and check
it at the start of connect to prevent duplicate conn.start() calls, then clear
it in both success and catch paths (or a finally block) while keeping the
existing connection state update and ReceiveMessage listener logic intact.

// Preserve the original _invokeClientMethod so handlers registered via connection.on() still fire.
const originalInvokeClientMethod = connection._invokeClientMethod?.bind(connection);
connection._invokeClientMethod = async function (invocationMessage) {
sendEvent('main:signalr:event', requestId, collectionUid, invocationMessage.target, invocationMessage.arguments);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the SignalR event handler implementation around the cited lines.
file='packages/bruno-electron/src/ipc/network/signalr-event-handlers.js'
wc -l "$file"
sed -n '1,260p' "$file"

# Find all references to the emitted event and handler registration.
rg -n "main:signalr:event|register-handler|_invokeClientMethod|originalInvokeClientMethod|connection\.on\(" packages/bruno-electron/src/ipc/network -S

Repository: usebruno/bruno

Length of output: 7891


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find renderer consumers of the SignalR event stream and handler registration.
rg -n "main:signalr:event|renderer:signalr:register-handler|signalr:event" packages/bruno-electron -S

# Narrow to likely renderer-side files if present.
fd -a "signalr" packages/bruno-electron

Repository: usebruno/bruno

Length of output: 746


🌐 Web query:

@microsoft/signalr _invokeClientMethod connection.on invocationMessage.target source code

💡 Result:

In the @microsoft/signalr JavaScript client, the method invokeClientMethod (or _invokeClientMethod in newer versions) is an internal function responsible for dispatching incoming messages from the server to the appropriate, user-registered client-side handlers [1][2]. When a message is received from the server, the client parses it into an InvocationMessage, which contains a target property representing the name of the client method to be executed [3][4]. The invokeClientMethod function then performs the following steps: 1. It takes the target string from the InvocationMessage and converts it to lowercase to perform a case-insensitive lookup [1][2]. 2. It searches a registry (typically stored as this.methods or this._methods) for any handlers registered with that specific method name using the connection.on method [1][2]. 3. If matching handlers are found, it executes them by passing the arguments included in the invocationMessage [1][2]. 4. If no handlers are found, it logs a warning stating that no client method with that name was found [1][2]. The connection.on method is the public API used to register these handlers [5][6]. When you call connection.on('methodName', handler), the client stores the provided handler function in an internal map associated with 'methodname' [1][2]. Consequently, when the server sends an invocation with a target matching that name, the internal invokeClientMethod logic retrieves and executes the corresponding function [1][2].

Citations:


main:signalr:event is emitted twice for registered handlers. _invokeClientMethod broadcasts every invocation, then register-handler forwards the same target again through connection.on(...). That duplicates renderer events for any registered SignalR method; route each invocation through one path only, or skip the generic broadcast when a handler exists.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/bruno-electron/src/ipc/network/signalr-event-handlers.js` at line
106, `main:signalr:event` is being emitted twice for registered SignalR handlers
because `_invokeClientMethod` broadcasts every invocation and `register-handler`
also forwards the same target through `connection.on(...)`. Update
`signalr-event-handlers.js` so a given invocation goes through only one path:
either keep the generic `sendEvent('main:signalr:event', ...)` broadcast in
`_invokeClientMethod` and prevent the registered handler from re-emitting, or
skip the broadcast when `register-handler` has a matching target. Use the
existing `_invokeClientMethod`, `register-handler`, and `sendEvent` flow to gate
the duplicate emission.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants