Skip to content

feat(grpc): add explicit TLS toggle to URL bar#8443

Open
KyuboLim wants to merge 1 commit into
usebruno:mainfrom
KyuboLim:feat/grpc-tls-toggle
Open

feat(grpc): add explicit TLS toggle to URL bar#8443
KyuboLim wants to merge 1 commit into
usebruno:mainfrom
KyuboLim:feat/grpc-tls-toggle

Conversation

@KyuboLim

@KyuboLim KyuboLim commented Jul 1, 2026

Copy link
Copy Markdown

feat(grpc): explicit TLS toggle in the gRPC URL bar

Open this after the discussion lands on a direction. Link the discussion here.

What

Adds a lock/unlock toggle to the gRPC URL bar so users can choose TLS
(grpcs://) vs plaintext (grpc://) explicitly, instead of having to know to
type the scheme by hand.

Closes #6950 (the auto-prefix confusion is resolved by making the scheme visible
and controllable).

Why

TLS for gRPC is currently controlled only by the URL scheme, which is
auto-applied and effectively hidden. Users — especially those coming from
Postman — don't discover it and hit failures. #6950 has been open since v2.13.2
and still reproduces on v3.5.0; the community workaround is to manually prepend
grpc://.

How

  • New module grpcUrl.js with pure, unit-tested helpers for scheme/TLS
    derivation.
  • GrpcQueryUrl:
    • Lock button (🔒 grpcs / 🔓 grpc) at the left of the URL bar.
    • Editor displays host:port only; the scheme is toggle-controlled.
    • Scheme is stored in request.url as before.
  • No backend changesgrpc-client.js already derives TLS and the grpcurl
    -plaintext flag from the scheme.
  • No schema / .bru format change.

Behavior (mirrors the existing backend)

  • No scheme present → lock reflects host default (localhost/127.0.0.1 =
    plaintext, otherwise TLS), matching getParsedGrpcUrlObject.
  • Explicitly typed/pasted scheme wins.
  • Variable-leading hosts ({{baseUrl}}…) → toggle stays available; the lock
    defaults to plaintext and toggling writes an explicit grpc://{{var}} /
    grpcs://{{var}}.

Tests

  • grpcUrl.spec.js — 17 cases covering scheme detection, host inference,
    display stripping, scheme rewriting, variable handling, and input resolution.
  • npx jest src/components/RequestPane/GrpcQueryUrl/grpcUrl.spec.js → 17 passed.

Screenshots

Plaintext (grpc://) TLS (grpcs://)
add 🔓 screenshot add 🔒 screenshot

Checklist

  • Linked discussion
  • Screenshots / short clip added
  • npm run test (bruno-app) passes
  • Manually verified against a plaintext and a TLS gRPC server

Updated the gRPC URL editor to make TLS/plaintext selection explicit in the UI.

  • Added a new grpcUrl helper module in packages/bruno-app to:

    • detect explicit gRPC/http schemes,
    • infer secure vs plaintext behavior from host and variables,
    • strip gRPC schemes for display,
    • apply or replace grpc:// / grpcs:// on demand,
    • resolve the correct secure state while editing.
  • Updated GrpcQueryUrl to use those helpers so:

    • the editor shows host:port instead of a user-facing scheme,
    • the stored request.url still preserves the selected scheme,
    • editing keeps scheme behavior consistent with existing backend inference rules,
    • a new lock/unlock toggle lets users switch between TLS and plaintext,
    • reflection reload is triggered when toggling in reflection mode.
  • Added unit tests covering host inference, variable handling, scheme detection, URL display normalization, scheme rewriting, and secure-state resolution.

  • Added styling for the new TLS toggle.

No backend, schema, or .bru format changes were included.

@coderabbitai

coderabbitai Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

Adds a grpcUrl.js helper module with functions to infer and rewrite the secure (grpcs://) vs plaintext (grpc://) scheme of gRPC URLs based on explicit scheme, variable prefixes, and localhost detection. Wires these helpers into the GrpcQueryUrl component, adding a display URL and a TLS lock toggle button, plus corresponding styles and unit tests.

Changes

gRPC TLS toggle implementation

Layer / File(s) Summary
gRPC URL helper functions and tests
packages/bruno-app/src/components/RequestPane/GrpcQueryUrl/grpcUrl.js, packages/bruno-app/src/components/RequestPane/GrpcQueryUrl/grpcUrl.spec.js
Adds isLocalGrpcHost, startsWithVariable, hasExplicitGrpcScheme, isSecureGrpcUrl, getDisplayGrpcUrl, setGrpcUrlSecureScheme, and resolveSecureForInput helpers with precedence-based scheme inference/rewrite logic, plus a Jest test suite covering each function.
Component wiring and TLS toggle UI
packages/bruno-app/src/components/RequestPane/GrpcQueryUrl/index.js, packages/bruno-app/src/components/RequestPane/GrpcQueryUrl/StyledWrapper.js
Computes displayUrl/tlsSecure, updates onUrlChange to resolve and apply the scheme via the new helpers, adds handleTlsToggle to flip scheme and reload reflection when active, renders a new lock/lock-open toggle button, switches the editor to show displayUrl, and adds .tls-toggle styling.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant GrpcQueryUrlComponent
  participant grpcUrlHelpers
  participant ReduxStore

  User->>GrpcQueryUrlComponent: edits URL in SingleLineEditor
  GrpcQueryUrlComponent->>grpcUrlHelpers: resolveSecureForInput(storedUrl, inputValue)
  grpcUrlHelpers-->>GrpcQueryUrlComponent: secure flag
  GrpcQueryUrlComponent->>grpcUrlHelpers: setGrpcUrlSecureScheme(url, secure)
  grpcUrlHelpers-->>GrpcQueryUrlComponent: finalUrl
  GrpcQueryUrlComponent->>ReduxStore: dispatch requestUrlChanged(finalUrl)

  User->>GrpcQueryUrlComponent: clicks TLS lock toggle
  GrpcQueryUrlComponent->>grpcUrlHelpers: setGrpcUrlSecureScheme(url, !tlsSecure)
  grpcUrlHelpers-->>GrpcQueryUrlComponent: updatedUrl
  GrpcQueryUrlComponent->>ReduxStore: dispatch requestUrlChanged(updatedUrl)
  GrpcQueryUrlComponent->>GrpcQueryUrlComponent: reload reflection methods (if active)
Loading

Suggested reviewers: helloanoop, lohit-bruno, naman-bruno, bijin-bruno

🚥 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 and concisely describes the main change: adding an explicit TLS toggle for the gRPC URL bar.
Linked Issues check ✅ Passed The changes address #6950 by making gRPC scheme/TLS selection explicit in the UI and removing reliance on the auto-added http prefix.
Out of Scope Changes check ✅ Passed The added helper logic, UI toggle, wrapper styles, and tests all support the gRPC TLS-toggle objective and don't appear unrelated.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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.

@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: 4

🤖 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/GrpcQueryUrl/grpcUrl.js`:
- Around line 21-23: The isLocalGrpcHost helper is matching localhost and
127.0.0.1 as raw substrings, which incorrectly treats remote hosts containing
those strings as local. Update isLocalGrpcHost in grpcUrl.js to parse the host
component of the URL and check loopback only on host boundaries, using proper
hostname comparison in the RequestPane/GrpcQueryUrl logic so the TLS/plaintext
default is inferred correctly.
- Around line 79-83: The secure-state resolver is treating unresolved template
values as plaintext, which causes the scheme-less state to be lost. Update
resolveSecureForInput so that when storedUrl or inputValue is a variable-only
placeholder like {{baseUrl}}, it returns an indeterminate/unchanged state
instead of false, and ensure the caller in grpcUrl.js does not rewrite the value
to grpc:// for unresolved variables. Preserve scheme-less behavior for inputs
without an explicit grpc:// or grpcs:// scheme so transport can remain
backend-inferred until the user makes an explicit TLS choice.
- Line 54: Update getDisplayGrpcUrl in grpcUrl.js to strip http:// and https://
in addition to grpc:// and grpcs:// so the display helper always returns a
host-only value. Keep the normalization consistent with hasExplicitGrpcScheme()
and isSecureGrpcUrl() by extending the existing replace logic, and ensure stored
requests using HTTP(S) no longer show a scheme in the editor.

In `@packages/bruno-app/src/components/RequestPane/GrpcQueryUrl/index.js`:
- Around line 132-135: The auto-reflection path in GrpcQueryUrl should not call
handleReflection() for scheme-only values like grpc:// or grpcs://. Update the
guard around the existing !protoFilePath / finalUrl check to verify the
displayed host portion is present before switching into reflection mode, and
apply the same fix in the other matching branch mentioned in the comment so
setIsReflectionMode(true) only runs when there is an actual host.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 6ec91040-cafa-4367-870b-ff556efa3f51

📥 Commits

Reviewing files that changed from the base of the PR and between 4ab68fc and 6ece6c7.

📒 Files selected for processing (4)
  • packages/bruno-app/src/components/RequestPane/GrpcQueryUrl/StyledWrapper.js
  • packages/bruno-app/src/components/RequestPane/GrpcQueryUrl/grpcUrl.js
  • packages/bruno-app/src/components/RequestPane/GrpcQueryUrl/grpcUrl.spec.js
  • packages/bruno-app/src/components/RequestPane/GrpcQueryUrl/index.js
Comment on lines +21 to +23
export const isLocalGrpcHost = (url = '') => {
const value = url.toLowerCase();
return value.includes('localhost') || value.includes('127.0.0.1');

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 | 🟡 Minor | ⚡ Quick win

Match loopback on host boundaries, not substrings.

includes('localhost') / includes('127.0.0.1') also classifies remote hosts like api-localhost.example.com as local, so the inferred default flips from TLS to plaintext for a real remote endpoint.

🤖 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/GrpcQueryUrl/grpcUrl.js` around
lines 21 - 23, The isLocalGrpcHost helper is matching localhost and 127.0.0.1 as
raw substrings, which incorrectly treats remote hosts containing those strings
as local. Update isLocalGrpcHost in grpcUrl.js to parse the host component of
the URL and check loopback only on host boundaries, using proper hostname
comparison in the RequestPane/GrpcQueryUrl logic so the TLS/plaintext default is
inferred correctly.
* Strip a leading grpc:// or grpcs:// for display in the URL editor, so the
* scheme is controlled by the lock toggle instead of being typed by hand.
*/
export const getDisplayGrpcUrl = (url = '') => url.replace(/^(grpc|grpcs):\/\//i, '');

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

Strip http(s):// in the display helper too.

hasExplicitGrpcScheme() and isSecureGrpcUrl() both accept http:// / https://, but getDisplayGrpcUrl() leaves those schemes visible. Existing requests stored with http(s) therefore break the new host-only editor contract until the next edit silently normalizes them to grpc(s)://.

Minimal fix
-export const getDisplayGrpcUrl = (url = '') => url.replace(/^(grpc|grpcs):\/\//i, '');
+export const getDisplayGrpcUrl = (url = '') => url.replace(/^(grpc|grpcs|http|https):\/\//i, '');
🤖 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/GrpcQueryUrl/grpcUrl.js` at
line 54, Update getDisplayGrpcUrl in grpcUrl.js to strip http:// and https:// in
addition to grpc:// and grpcs:// so the display helper always returns a
host-only value. Keep the normalization consistent with hasExplicitGrpcScheme()
and isSecureGrpcUrl() by extending the existing replace logic, and ensure stored
requests using HTTP(S) no longer show a scheme in the editor.
Comment on lines +79 to +83
export const resolveSecureForInput = (storedUrl = '', inputValue = '') => {
if (hasExplicitGrpcScheme(inputValue)) return isSecureGrpcUrl(inputValue);
if (hasExplicitGrpcScheme(storedUrl)) return isSecureGrpcUrl(storedUrl);
return isSecureGrpcUrl(inputValue);
};

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

Preserve the scheme-less state for unresolved variables.

This collapses “unknown until runtime” into false. For storedUrl = '{{baseUrl}}' and inputValue = '{{baseUrl}}', the caller ends up rewriting the value to grpc://{{baseUrl}} on the next edit, which turns backend-inferred transport into explicit plaintext even though the user never touched the TLS toggle.

🤖 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/GrpcQueryUrl/grpcUrl.js` around
lines 79 - 83, The secure-state resolver is treating unresolved template values
as plaintext, which causes the scheme-less state to be lost. Update
resolveSecureForInput so that when storedUrl or inputValue is a variable-only
placeholder like {{baseUrl}}, it returns an indeterminate/unchanged state
instead of false, and ensure the caller in grpcUrl.js does not rewrite the value
to grpc:// for unresolved variables. Preserve scheme-less behavior for inputs
without an explicit grpc:// or grpcs:// scheme so transport can remain
backend-inferred until the user makes an explicit TLS choice.
Comment on lines +132 to 135
if (!protoFilePath && finalUrl) {
setIsReflectionMode(true);
handleReflection(finalUrl);
}

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

Check for an actual host before auto-refreshing reflection.

finalUrl can be just grpc:// / grpcs:// after toggling an empty field, so these truthiness checks call handleReflection() with a scheme-only URL even though the editor still shows no host. Gate this on the displayed host portion instead of raw string truthiness.

Minimal guard
-    if (!protoFilePath && finalUrl) {
+    if (!protoFilePath && getDisplayGrpcUrl(finalUrl)) {
       setIsReflectionMode(true);
       handleReflection(finalUrl);
     }
…
-    if (isReflectionMode && finalUrl) {
+    if (isReflectionMode && getDisplayGrpcUrl(finalUrl)) {
       handleReflection(finalUrl);
     }

Also applies to: 256-258

🤖 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/GrpcQueryUrl/index.js` around
lines 132 - 135, The auto-reflection path in GrpcQueryUrl should not call
handleReflection() for scheme-only values like grpc:// or grpcs://. Update the
guard around the existing !protoFilePath / finalUrl check to verify the
displayed host portion is present before switching into reflection mode, and
apply the same fix in the other matching branch mentioned in the comment so
setIsReflectionMode(true) only runs when there is an actual host.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

1 participant