Skip to content

V3.0.2#711

Merged
mjadach-iv merged 8 commits intomainfrom
v3.0.2
Sep 17, 2025
Merged

V3.0.2#711
mjadach-iv merged 8 commits intomainfrom
v3.0.2

Conversation

@mjadach-iv
Copy link
Contributor

@mjadach-iv mjadach-iv commented Sep 15, 2025

Changelog:

  • RPC provider URL on Node Info gets hidden automatically if a secret is detected. It's possible to mask/unmask the secret using a button next to it
  • OPEN session listener modal got updated with 'Max clients', 'Max surb upstream' and new capabiliteis
  • OPEN session listener modal now will remember the session parameters after closing it, for easier deving and debigging
  • In the sessions table, a 'ping button' on the first node of the forward path and the last node of the return path is now present to ping/test if the node can see the nodes it should communicate with while using sessions.
  • Fixed last seen column on aliases page
  • Added 'add alias' button in channels table
  • HOPRd SDK updated to support current (2025-09-17 V3 HOPRd)

Summary by CodeRabbit

  • New Features

    • Mask/unmask RPC provider URL on Node Info with visibility toggle; shows masked/invalid/Faulty RPC states.
    • Expanded session listener UI: new session limits, capability toggles, updated defaults, validation, and layout improvements.
    • Alias creation now works directly by node address (simpler create-alias flow).
  • Bug Fixes

    • Icon buttons disabled while reloading to prevent accidental clicks.
    • Ticket statistics reset shows progress, delays briefly on completion, and refreshes stats.
  • Chores

    • Updated @hoprnet/hopr-sdk prerelease dependency.

@coderabbitai
Copy link

coderabbitai bot commented Sep 15, 2025

📝 Walkthrough

Walkthrough

Bumps @hoprnet/hopr-sdk prerelease version; UI and state updates: IconButton disables during reloading; Node Info masks/unmasks RPC provider URLs; Tickets page ties reset button to resetTicketStatistics flow with a 2s UI cooldown; OpenSessionModal adds session fields/capabilities and layout changes; store now keeps connected peers as a keyed map and connected peer shape changed; Sessions page renders per-hop PingModal and Hop wrappers; CreateAliasModal prop changed to accept address.

Changes

Cohort / File(s) Summary
Dependency bump
package.json
Updated @hoprnet/hopr-sdk from 3.0.3-pr.156-20250910174548 to 3.0.3-pr.156-20250916114518.
IconButton state logic
src/future-hopr-lib-components/Button/IconButton.tsx
Disabled state expanded to include reloading (button disabled when `disabled
Node Info provider masking
src/pages/node/info/index.tsx
Parse info?.provider to detect secret path/search, compute providerShort (masked) or mark invalid, add showWholeProvider toggle with Visibility/VisibilityOff icons, and display masked or full provider (prefixed with “Faulty RPC
Tickets reset flow UI
src/pages/node/tickets.tsx
Add selector resettingTicketStatistics and local resettingStats with effect to keep UI reloading state for 2s after fetch; bind button reloading to resettingStats; add handleResetTicketsStatistics to dispatch reset thunk and refresh stats.
OpenSessionModal changes
src/components/Modal/node/OpenSessionModal.tsx
Add inputs: maxSurbUpstream, maxClientSessions, capability flags (retransmissionAckOnly, noDelay, noRateControl); update layout/labels/validation; include new fields in sessionPayload; remove debug useEffects; adjust dialog title/tooltip and maxWidth; introduce two-column capabilities/protocol layout.
Peers map population
src/store/slices/node/actionsAsync.ts
In getPeersThunk.fulfilled, populate state.peers.parsed.connected as a lookup map keyed by peer.address in addition to existing sorted arrays.
Connected peer shape change
src/store/slices/node/initialState.ts
Per-peer object shape changed: removed peerId and peerAddress, added single address field; other metadata fields unchanged.
CreateAliasModal prop usage
src/pages/node/channelsOutgoing.tsx
Replace peerId-based guarded invocation with direct CreateAliasModal call using address={peerAddress} (CreateAliasModal prop signature changed to accept address).
Sessions page hop rendering & PingModal
src/pages/node/sessions.tsx
Add Hop styled wrapper, render hops individually instead of raw JSON when appropriate, insert PingModal on first forward hop and last return hop, and use session.returnPath for return rendering.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant NodeInfo as Node Info Page
  participant Parser as URL Parser

  User->>NodeInfo: Load page
  NodeInfo->>Parser: Parse provider URL (info?.provider)
  Parser-->>NodeInfo: origin/path/search or Error
  alt secret present (non-root path or search)
    NodeInfo->>NodeInfo: compute masked providerShort or mark invalid
    User->>NodeInfo: Click visibility toggle
    NodeInfo->>NodeInfo: toggle showWholeProvider (display masked/full)
  else no secret
    NodeInfo->>NodeInfo: show full provider
  end
Loading
sequenceDiagram
  autonumber
  actor User
  participant Tickets as Tickets Page
  participant Store as Redux Store
  participant API as Backend

  User->>Tickets: Click "Reset Ticket Statistics"
  Tickets->>Store: dispatch resetTicketStatisticsThunk
  Store->>API: POST reset ticket stats
  API-->>Store: completed
  Store-->>Tickets: resettingTicketStatistics true→false
  Note over Tickets: UI shows reloading while true, then remains for 2s after completion
  Tickets->>Store: dispatch fetchTicketStatistics (refresh)
Loading
sequenceDiagram
  autonumber
  actor User
  participant Sessions as Sessions Page
  participant Renderer as UI Renderer
  participant Ping as PingModal

  User->>Sessions: Open Sessions page
  Sessions->>Renderer: For each session, parse forward/return path
  alt path contains "Hops"
    Renderer->>Renderer: render string representation
  else JSON path with hops array
    Renderer->>Renderer: render each Hop element
    Renderer->>Ping: render PingModal on first forward hop
    Renderer->>Ping: render PingModal on last return hop
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • Jaguaras
  • esterlus

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title Check ❓ Inconclusive The title "V3.0.2" is only a version label and does not describe the primary changes in this PR (which include a hopr-sdk prerelease bump plus multiple UI and store changes such as IconButton reloading behavior, provider masking, session modal enhancements, and peers/aliases updates), so it is too generic for a reviewer to understand the intent at a glance. While it appears related to a release, it fails to highlight the most important change(s) or the scope of the changeset. Because the title is vague and non-descriptive, the check is inconclusive. Please replace the title with a concise descriptive sentence such as "chore(release): v3.0.2 — bump hopr-sdk prerelease and UI/store fixes (IconButton, provider masking, sessions, ticket stats)"; if this is intended as a release PR, prepend "release:" and include a short parenthetical list of the main changes so reviewers can quickly grasp the PR's intent.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch v3.0.2

Tip

👮 Agentic pre-merge checks are now available in preview!

Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.

  • Built-in checks – Quickly apply ready-made checks to enforce title conventions, require pull request descriptions that follow templates, validate linked issues for compliance, and more.
  • Custom agentic checks – Define your own rules using CodeRabbit’s advanced agentic capabilities to enforce organization-specific policies and workflows. For example, you can instruct CodeRabbit’s agent to verify that API documentation is updated whenever API schema files are modified in a PR. Note: Upto 5 custom checks are currently allowed during the preview period. Pricing for this feature will be announced in a few weeks.

Please see the documentation for more information.

Example:

reviews:
  pre_merge_checks:
    custom_checks:
      - name: "Undocumented Breaking Changes"
        mode: "warning"
        instructions: |
          Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).

Please share your feedback with us on this Discord post.


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

❤️ Share

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

@mjadach-iv mjadach-iv linked an issue Sep 15, 2025 that may be closed by this pull request
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
src/pages/node/info/index.tsx (1)

70-74: Default providerContainsSecret to false to avoid initial flicker.

Prevents showing the toggle before parsing runs.

-const [providerContainsSecret, set_providerContainsSecret] = useState(true);
+const [providerContainsSecret, set_providerContainsSecret] = useState(false);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7fff86f and 8265adf.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (4)
  • package.json (1 hunks)
  • src/future-hopr-lib-components/Button/IconButton.tsx (1 hunks)
  • src/pages/node/info/index.tsx (3 hunks)
  • src/pages/node/tickets.tsx (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
src/pages/node/info/index.tsx (1)
src/future-hopr-lib-components/Button/IconButton.tsx (1)
  • IconButton (55-83)
src/pages/node/tickets.tsx (1)
src/store/index.ts (1)
  • useAppSelector (26-26)
🔇 Additional comments (7)
src/future-hopr-lib-components/Button/IconButton.tsx (1)

73-73: Disabling on reloading is correct.

Prevents duplicate clicks while animated; pairs well with the Tooltip span wrapper.

Confirm this doesn’t block any “reloading but still clickable” use cases elsewhere.

src/pages/node/tickets.tsx (2)

119-119: Using resettingStats for the button’s reloading is appropriate.

With the timer fix, the UX “linger” will be consistent.


26-26: Approve — selector & UI state wiring verified.
Confirmed store.node.resetTicketStatistics.isFetching is referenced in src/pages/node/tickets.tsx (line 26) and matching thunk/state entries exist in src/store/slices/node/actionsAsync.ts, actionsFetching.ts, and initialState.ts.

src/pages/node/info/index.tsx (3)

320-326: Copy tweak in tooltip.

Small grammar fix.
[raise_n​itpick_issue]

-                    title="The RPC provider address your node uses sync"
+                    title="The RPC provider address your node uses to sync"

329-357: Toggle UX reads correctly.

Icon/tooltip semantics are clear (Visibility = hide; VisibilityOff = show).


365-368: Provider cell rendering is concise and safe by default.

Masked by default; full URL only on explicit toggle; red highlight on faulty RPC is helpful.

package.json (1)

9-9: SDK bump verified — yarn.lock consistent and single resolved version.
package.json and yarn.lock both reference @hoprnet/hopr-sdk@3.0.3-pr.156-20250915124445; no action required.

Comment on lines +79 to +95
useEffect(() => {
try {
if (!provider) {
set_providerShort('');
return;
}
const providerObject = new URL(provider);
const providerContainsSecret = providerObject.pathname !== '/' || providerObject.search !== "";
set_providerContainsSecret(providerContainsSecret);
const providerShort = providerContainsSecret ? providerObject.origin + '/************' : provider;
set_providerShort(providerShort || '');
} catch (e) {
console.error('Error parsing provider URL', e);
set_providerShort('***Invalid URL***');
}
}, [provider]);

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Harden provider parsing; avoid shadowing; auto-hide on change.

  • Don’t shadow state with a local providerContainsSecret.
  • Reset showWholeProvider on provider change to avoid exposing a new URL.
  • Treat userinfo and query as secrets; fall back cleanly on errors/empty provider.

Apply:

   useEffect(() => {
-    try {
-      if (!provider) {
-        set_providerShort('');
-        return;
-      }
-      const providerObject = new URL(provider);
-      const providerContainsSecret = providerObject.pathname !== '/' || providerObject.search !== "";
-      set_providerContainsSecret(providerContainsSecret);
-      const providerShort = providerContainsSecret ? providerObject.origin + '/************' : provider;
-      set_providerShort(providerShort || '');
-    } catch (e) {
-      console.error('Error parsing provider URL', e);
-      set_providerShort('***Invalid URL***');
-    }
+    // Always hide full URL when provider changes
+    set_showWholeProvider(false);
+    try {
+      if (!provider) {
+        set_providerContainsSecret(false);
+        set_providerShort('');
+        return;
+      }
+      const u = new URL(provider);
+      const containsSecret =
+        u.username !== '' ||
+        u.password !== '' ||
+        u.pathname !== '/' ||
+        u.search !== '';
+      set_providerContainsSecret(containsSecret);
+      set_providerShort(containsSecret ? `${u.origin}/************` : provider);
+    } catch (e) {
+      console.error('Error parsing provider URL', e);
+      set_providerContainsSecret(false);
+      set_providerShort('***Invalid URL***');
+    }
   }, [provider]);
📝 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
useEffect(() => {
try {
if (!provider) {
set_providerShort('');
return;
}
const providerObject = new URL(provider);
const providerContainsSecret = providerObject.pathname !== '/' || providerObject.search !== "";
set_providerContainsSecret(providerContainsSecret);
const providerShort = providerContainsSecret ? providerObject.origin + '/************' : provider;
set_providerShort(providerShort || '');
} catch (e) {
console.error('Error parsing provider URL', e);
set_providerShort('***Invalid URL***');
}
}, [provider]);
useEffect(() => {
// Always hide full URL when provider changes
set_showWholeProvider(false);
try {
if (!provider) {
set_providerContainsSecret(false);
set_providerShort('');
return;
}
const u = new URL(provider);
const containsSecret =
u.username !== '' ||
u.password !== '' ||
u.pathname !== '/' ||
u.search !== '';
set_providerContainsSecret(containsSecret);
set_providerShort(containsSecret ? `${u.origin}/************` : provider);
} catch (e) {
console.error('Error parsing provider URL', e);
set_providerContainsSecret(false);
set_providerShort('***Invalid URL***');
}
}, [provider]);
🤖 Prompt for AI Agents
In src/pages/node/info/index.tsx around lines 79 to 95, the effect that parses
the provider URL shadows component state, doesn't reset the showWholeProvider
flag when provider changes, and doesn't treat URL userinfo or query as secrets;
update the effect to (1) avoid declaring a local providerContainsSecret variable
that shadows state—use the state setter directly, (2) always reset
showWholeProvider to false at the start of the effect when provider changes so a
new provider isn't accidentally revealed, (3) consider pathname !== '/' OR
presence of userinfo OR non-empty search/query as secrets, and (4) on empty
provider or any parsing error set providerShort to a redacted placeholder (e.g.
'***Invalid URL***' or empty) and ensure providerContainsSecret is set false;
keep console.error for debugging.

Comment on lines +38 to +46
useEffect(() => {
if (resettingTicketStatistics) {
set_resettingStats(true);
} else {
setTimeout(() => {
set_resettingStats(false);
}, 2000);
}
}, [resettingTicketStatistics]);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Cleanup timer to avoid stale updates and memory leaks.

Currently, multiple quick toggles can flip reloading off prematurely; also setState may fire after unmount.

Apply:

-  useEffect(() => {
-    if (resettingTicketStatistics) {
-      set_resettingStats(true);
-    } else {
-      setTimeout(() => {
-        set_resettingStats(false);
-      }, 2000);
-    }
-  }, [resettingTicketStatistics]);
+  useEffect(() => {
+    let t: number | undefined;
+    if (resettingTicketStatistics) {
+      set_resettingStats(true);
+    } else {
+      t = window.setTimeout(() => set_resettingStats(false), 2000);
+    }
+    return () => {
+      if (t) window.clearTimeout(t);
+    };
+  }, [resettingTicketStatistics]);
📝 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
useEffect(() => {
if (resettingTicketStatistics) {
set_resettingStats(true);
} else {
setTimeout(() => {
set_resettingStats(false);
}, 2000);
}
}, [resettingTicketStatistics]);
useEffect(() => {
let t: number | undefined;
if (resettingTicketStatistics) {
set_resettingStats(true);
} else {
t = window.setTimeout(() => set_resettingStats(false), 2000);
}
return () => {
if (t) window.clearTimeout(t);
};
}, [resettingTicketStatistics]);
🤖 Prompt for AI Agents
In src/pages/node/tickets.tsx around lines 38 to 46, the useEffect creates a
timeout without clearing it, which can cause stale updates or setState after
unmount and lets rapid toggles flip resettingStats off prematurely; fix by
storing the timeout id in a ref, clear any existing timeout before creating a
new one, and return a cleanup that clears the timeout and marks the component as
unmounted (or checks the ref) so set_resettingStats is not called after unmount.

@mjadach-iv mjadach-iv linked an issue Sep 15, 2025 that may be closed by this pull request
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/store/slices/node/initialState.ts (1)

162-166: Rename connected index signature to peerAddress for consistency

connected in src/store/slices/node/initialState.ts is keyed by address but the index signature is still named peerId; change it to peerAddress to match other maps in this file.

-        [peerId: string]: {
+        [peerAddress: string]: {
           address: string;
           quality: number;
           multiaddr: string | null;

This is a local/type-name rename and safe; other occurrences in the repo already use peerAddress.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8265adf and bcae01a.

📒 Files selected for processing (3)
  • src/components/Modal/node/OpenSessionModal.tsx (0 hunks)
  • src/store/slices/node/actionsAsync.ts (1 hunks)
  • src/store/slices/node/initialState.ts (1 hunks)
💤 Files with no reviewable changes (1)
  • src/components/Modal/node/OpenSessionModal.tsx

Comment on lines +1151 to +1153
action.payload?.connected.forEach((peer) => {
state.peers.parsed.connected[peer.address] = peer;
});
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix stale peers: clear the map before repopulating

Without resetting, disconnected peers linger in parsed.connected. Rebuild the map atomically from payload.

Apply:

-      action.payload?.connected.forEach((peer) => {
-        state.peers.parsed.connected[peer.address] = peer;
-      });
+      state.peers.parsed.connected = Object.fromEntries(
+        (action.payload?.connected ?? []).map((peer) => [peer.address, peer]),
+      );
📝 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
action.payload?.connected.forEach((peer) => {
state.peers.parsed.connected[peer.address] = peer;
});
state.peers.parsed.connected = Object.fromEntries(
(action.payload?.connected ?? []).map((peer) => [peer.address, peer]),
);
🤖 Prompt for AI Agents
In src/store/slices/node/actionsAsync.ts around lines 1151 to 1153, the code
appends peers to state.peers.parsed.connected without clearing it first, causing
stale/disconnected peers to persist; replace the incremental population with an
atomic rebuild: create a fresh map/object from action.payload?.connected (safely
handling undefined) and then assign that new object to
state.peers.parsed.connected (or replace the map in one assignment) so the map
is cleared and repopulated atomically from the payload.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (2)
src/pages/node/channelsOutgoing.tsx (1)

201-211: Avoid falsy checks that drop valid rows (e.g., zero balance) and guard missing entries

Use nullish checks and avoid repeated indexing.

-      const id = channel.id;
-      if (
-        !channelsOutgoingObject[id].peerAddress ||
-        !channelsOutgoingObject[id].balance ||
-        !channelsOutgoingObject[id].status
-      )
-        return;
-
-      const peerAddress = channelsOutgoingObject[id].peerAddress;
-      if (!peerAddress) return;
+      const id = channel.id;
+      const channelObj = channelsOutgoingObject?.[id];
+      if (!channelObj) return;
+      const { peerAddress, balance, status } = channelObj;
+      if (peerAddress == null || balance == null || status == null) return;

Optional follow-up: where used later, prefer channelObj?.isClosing over channelsOutgoingObject[id]?.isClosing for consistency.

src/components/Modal/node/OpenSessionModal.tsx (1)

253-254: Bug: returnPath uses forward hops count

When sendReturnMode === 'numberOfHops', you set returnPath.Hops from numberOfForwardHops. Should be numberOfReturnHops. This will generate incorrect routes.

Apply this diff:

-      sessionPayload.returnPath = {
-        Hops: numberOfForwardHops,
-      };
+      sessionPayload.returnPath = {
+        Hops: numberOfReturnHops,
+      };
🧹 Nitpick comments (6)
src/pages/node/channelsOutgoing.tsx (2)

26-26: Fix double slash in import path

Some tooling (e.g., eslint-plugin-import) flags this; normalize the path.

-import { CreateAliasModal } from '../../components/Modal/node//AddAliasModal';
+import { CreateAliasModal } from '../../components/Modal/node/AddAliasModal';

316-316: Verify default sort key

orderByDefault="number" doesn’t match any header keys ('id', 'node', etc.). If the intent is the row number column, use 'id'.

-        orderByDefault="number"
+        orderByDefault="id"
src/components/Modal/node/OpenSessionModal.tsx (4)

120-126: State defaults OK; consider naming and validation

Defaults look sane. Minor: maxSurbUpstream is a rate; consider naming like maxSurbUpstreamKBps to make units explicit, and clamp values at input or before dispatch.


414-451: Destination + Max client: type duplication and input hygiene

  • TextField for “Max client” sets both type='number' and inputProps={{ type: 'number' }}. Keep one to avoid inconsistencies.
  • Consider clamping to [1, 10000] on change to prevent transient invalid state.
-            <TextField
+            <TextField
               label="Max client"
               value={maxClientSessions}
               onChange={(event) => {
-                set_maxClientSessions(Number(event?.target?.value));
+                const v = Number(event?.target?.value);
+                set_maxClientSessions(Number.isFinite(v) ? Math.min(10000, Math.max(1, v)) : 1);
               }}
-              inputProps={{
-                type: 'number',
+              inputProps={{
                 min: 1,
                 max: 10000,
                 step: 1,
               }}
-              type='number'
               style={{
                 width: '97px',
               }}
             />

459-487: Max SURB upstream: unit in UI mismatches payload

UI shows “kb/s” while buffer uses “kB”. If you standardize to “kB/s” in payload (see earlier comment), also update the adornment here.

-              InputProps={{
-                endAdornment: <InputAdornment position="end">kb/s</InputAdornment>,
-              }}
+              InputProps={{
+                endAdornment: <InputAdornment position="end">kB/s</InputAdornment>,
+              }}

Also applies to: 481-482


414-433: Autocomplete getOptionLabel safety

aliases[address] assumes address is a key in aliases. If aliases is keyed differently (e.g., alias->address), this can render undefined (...). Consider optional chaining or a mapping helper.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bcae01a and 3790bb4.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (3)
  • package.json (1 hunks)
  • src/components/Modal/node/OpenSessionModal.tsx (7 hunks)
  • src/pages/node/channelsOutgoing.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • package.json
🧰 Additional context used
🧬 Code graph analysis (2)
src/pages/node/channelsOutgoing.tsx (1)
src/components/Modal/node/AddAliasModal.tsx (1)
  • CreateAliasModal (21-184)
src/components/Modal/node/OpenSessionModal.tsx (2)
src/store/index.ts (1)
  • useAppSelector (26-26)
src/future-hopr-lib-components/Modal/styled.tsx (1)
  • TopBar (15-20)
🔇 Additional comments (8)
src/pages/node/channelsOutgoing.tsx (1)

236-236: CreateAliasModal API alignment looks good

Passing address={peerAddress} matches the updated modal API and is safe here since you guard for a defined peerAddress above.

Minor: confirm that peerAddress is always an Ethereum‑style address (what isAddress validates in the modal) and never a peerId in this view.

src/components/Modal/node/OpenSessionModal.tsx (7)

132-134: Sensible defaults for listener and target

Using loopback defaults is a good DX choice.


395-395: Confirm SDialog maxWidth typing

maxWidth={'750px'} is not a valid MUI Dialog prop (expects breakpoint enum) unless SDialog re-maps it to a styled prop. Please confirm SDialog supports pixel strings; otherwise, set width via PaperProps.sx={{ maxWidth: 750 }} or container styles.

[suggest_minor_refactor]


398-398: Copy: “Open session listener”

Title change reads clearer.


383-384: Tooltip tweak LGTM

The tooltip ending with “session listener” matches the title.


583-591: NoRateControl toggle

Looks straightforward and independent.


237-239: ```shell
#!/bin/bash
set -euo pipefail

echo "=== rg: maxSurbUpstream ==="
rg -n --hidden -S 'maxSurbUpstream' || true

echo "=== rg: maxClientSessions ==="
rg -n --hidden -S 'maxClientSessions' || true

echo "=== rg: responseBuffer ==="
rg -n --hidden -S 'responseBuffer' || true

echo "=== rg: unit strings (kb/s kB/s kB kb) ==="
rg -n --hidden -S 'kb/s|kB/s|\bkB\b|\bkb\b' || true

echo "=== show OpenSessionModal.tsx (if exists) lines 1-400 ==="
if [ -f src/components/Modal/node/OpenSessionModal.tsx ]; then
sed -n '1,400p' src/components/Modal/node/OpenSessionModal.tsx
else
echo "File not found: src/components/Modal/node/OpenSessionModal.tsx"
fi

echo "=== find types/interfaces mentioning Session or SessionConfig ==="
rg -n --hidden -S 'interface .*Session|type .*Session|SessionConfig|OpenSession|StartSession' -g '!node_modules' || true

echo "=== find calls that might send this payload to SDK/backend ==="
rg -n --hidden -S 'createSession|openSession|startSession|start_node_session|sendSession|startNode' -g '!node_modules' || true

echo "=== search for SDK/client imports/usages ==="
rg -n --hidden -S 'from .*sdk|from .*client|new .*Client|createClient(|getClient(' -g '!node_modules' || true


---

`224-239`: **Verify SDK payload contract for @hoprnet/hopr-sdk 3.0.3-pr.156**

- Provide the exact OpenSessionPayloadType (TS interface or compiled .d.ts) from your installed package or a link to the exact repo/tag/file for 3.0.3-pr.156.
- Confirm allowed capability literals (expected: Retransmission, RetransmissionAckOnly, Segmentation, NoDelay, NoRateControl) and whether `capabilities` is string[] / enum.
- Confirm expected types/units for `responseBuffer` and `maxSurbUpstream` (number vs string, and whether unit suffixes like "kB" / "kb/s" are required).
- Confirm `target` shape `{ Plain: string }` is still valid.

Affects: src/components/Modal/node/OpenSessionModal.tsx lines 224-239 and 262-276.

</blockquote></details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->

Comment on lines 533 to 536
if(!retransmission) {
set_segmentation(true);
}
return !retransmission;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Make Retransmission and RetransmissionAckOnly mutually exclusive; keep Segmentation consistent

Likely the SDK treats Retransmission and RetransmissionAckOnly as exclusive. Right now both can be enabled and both are pushed to capabilities. Also, you set Segmentation true when enabling any of these, but it may remain true after disabling them. Enforce exclusivity and keep Segmentation consistent.

                 <FormControlLabel
                   control={<Checkbox checked={retransmission} />}
                   label="Retransmission"
                   onChange={() => {
-                    set_retransmission((retransmission) => {
-                      if(!retransmission) {
-                        set_segmentation(true);
-                      }
-                      return !retransmission;
-                    });
+                    set_retransmission((prev) => {
+                      const next = !prev;
+                      if (next) {
+                        set_retransmissionAckOnly(false);
+                        set_segmentation(true);
+                      }
+                      return next;
+                    });
                   }}
                 />
                 <FormControlLabel
                   control={<Checkbox checked={retransmissionAckOnly} />}
                   label="RetransmissionAckOnly"
                   onChange={() => {
-                    set_retransmissionAckOnly((retransmissionAckOnly) => {
-                      if(!retransmissionAckOnly) {
-                        set_segmentation(true);
-                      }
-                      return !retransmissionAckOnly;
-                    });
+                    set_retransmissionAckOnly((prev) => {
+                      const next = !prev;
+                      if (next) {
+                        set_retransmission(false);
+                        set_segmentation(true);
+                      }
+                      return next;
+                    });
                   }}
                 />
                 <FormControlLabel
                   control={<Checkbox checked={segmentation} />}
                   label="Segmentation"
                   disabled={noDelay || retransmission || retransmissionAckOnly}
                   title={
                     retransmission ?
                     'Segmentation is required when Retransmission is enabled' :
                       retransmissionAckOnly ?
                       'Segmentation is required when RetransmissionAckOnly is enabled' :
                       noDelay ?
                       'Segmentation is required when NoDelay is enabled' :
                       ''}
                   onChange={() => {
                     set_segmentation((segmentation) => {
                       return !segmentation;
                     });
                   }}
                 />
                 <FormControlLabel
                   control={<Checkbox checked={noDelay} />}
                   label="NoDelay"
                   onChange={() => {
                     set_noDelay((noDelay) => {
                       if(!noDelay) {
                         set_segmentation(true);
                       }
                       return !noDelay;
                     });
                   }}
                 />

Also consider deriving an effectiveSegmentation = segmentation || noDelay || retransmission || retransmissionAckOnly and pushing Segmentation based on that, to avoid stale state.

Also applies to: 540-551, 555-563, 571-581

@mjadach-iv mjadach-iv merged commit 382b001 into main Sep 17, 2025
3 of 4 checks passed
@mjadach-iv mjadach-iv deleted the v3.0.2 branch September 17, 2025 13:38
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

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

⚠️ Outside diff range comments (2)
src/components/Modal/node/OpenSessionModal.tsx (1)

252-255: Return-path bug: uses numberOfForwardHops instead of numberOfReturnHops

This will send the wrong hop count for return paths.

-    if (sendReturnMode === 'numberOfHops') {
-      sessionPayload.returnPath = {
-        Hops: numberOfForwardHops,
-      };
-    }
+    if (sendReturnMode === 'numberOfHops') {
+      sessionPayload.returnPath = {
+        Hops: numberOfReturnHops,
+      };
+    }
src/pages/node/sessions.tsx (1)

165-187: Fix search/copy for sessions "Path" column — TablePro only handles string cell values

TablePro's search only matches when a cell is a string (filterData checks typeof === 'string') and copy-on-double-click only triggers for string values; sessions.tsx sets header.key='path' with search: true, copy: true while parsedTableData.path is JSX — search and copy will not work. (Files: src/pages/node/sessions.tsx; behavior in src/future-hopr-lib-components/Table/table-pro.tsx — filterData & onDoubleClick.)

Actions (choose one):

  • Disable search/copy on the 'path' header in src/pages/node/sessions.tsx.
  • Or add a plain-text fallback (e.g., parsedTableData.pathText) and either update TablePro to prefer a header.searchKey/header.copyKey fallback or wire header to the text key while keeping a separate JSX display field (changes in sessions.tsx and table-pro.tsx).
♻️ Duplicate comments (1)
src/components/Modal/node/OpenSessionModal.tsx (1)

541-551: Make Retransmission and RetransmissionAckOnly mutually exclusive; keep Segmentation consistent

Current UI allows enabling both; also Segmentation state can drift. Enforce exclusivity and force Segmentation when either is enabled. (Same concern raised previously.)

                 <FormControlLabel
                   control={<Checkbox checked={retransmission} />}
                   label="Retransmission"
                   onChange={() => {
-                    set_retransmission((retransmission) => {
-                      if (!retransmission) {
-                        set_segmentation(true);
-                      }
-                      return !retransmission;
-                    });
+                    set_retransmission((prev) => {
+                      const next = !prev;
+                      if (next) {
+                        set_retransmissionAckOnly(false);
+                        set_segmentation(true);
+                      }
+                      return next;
+                    });
                   }}
                 />
                 <FormControlLabel
                   control={<Checkbox checked={retransmissionAckOnly} />}
                   label="RetransmissionAckOnly"
                   onChange={() => {
-                    set_retransmissionAckOnly((retransmissionAckOnly) => {
-                      if (!retransmissionAckOnly) {
-                        set_segmentation(true);
-                      }
-                      return !retransmissionAckOnly;
-                    });
+                    set_retransmissionAckOnly((prev) => {
+                      const next = !prev;
+                      if (next) {
+                        set_retransmission(false);
+                        set_segmentation(true);
+                      }
+                      return next;
+                    });
                   }}
                 />
                 <FormControlLabel
                   control={<Checkbox checked={segmentation} />}
                   label="Segmentation"
-                  disabled={noDelay || retransmission || retransmissionAckOnly}
+                  disabled={noDelay || retransmission || retransmissionAckOnly}
                   title={
                     retransmission
                       ? 'Segmentation is required when Retransmission is enabled'
                       : retransmissionAckOnly
                       ? 'Segmentation is required when RetransmissionAckOnly is enabled'
                       : noDelay
                       ? 'Segmentation is required when NoDelay is enabled'
                       : ''
                   }

Also applies to: 571-582, 555-564

🧹 Nitpick comments (4)
src/pages/node/sessions.tsx (3)

24-29: Avoid fixed height on Hop to prevent clipping

Long addresses or different font metrics can get clipped. Prefer min-height/line-height and add small gap.

-const Hop = styled.div`
-  display: inline-flex;
-  align-items: center;
-  margin: 2px;
-  height: 18px;
-`;
+const Hop = styled.div`
+  display: inline-flex;
+  align-items: center;
+  margin: 2px;
+  min-height: 18px;
+  line-height: 18px;
+  gap: 4px;
+`;

169-174: Don’t use JSON.stringify to detect shape; check properties instead

String-searching for "Hops" is brittle and slower. Check for the key directly.

-            {JSON.stringify(session.forwardPath).includes('Hops') ? (
+            {(session?.forwardPath as any)?.Hops !== undefined ? (
               JSON.stringify(session.forwardPath)
                 .replace(/{|}|\[|\]|"/g, '')
                 .replace('IntermediatePath:', 'IntermediatePath:\n')
                 .replace(/,/g, ' ')
             ) : (
-            {JSON.stringify(session.returnPath).includes('Hops') ? (
+            {(session?.returnPath as any)?.Hops !== undefined ? (
               JSON.stringify(session.returnPath)
                 .replace(/{|}|\[|\]|"/g, '')
                 .replace('IntermediatePath:', 'IntermediatePath:\n')
                 .replace(/,/g, ' ')
             ) : (

Also applies to: 192-197


176-183: Use more stable React keys for hops

Index-only keys can cause incorrect reuse on reordering. Include hop value.

-                    key={`hop-f-${i}`}
+                    key={`hop-f-${i}-${hop}`}
-                    key={`hop-r-${i}`}
+                    key={`hop-r-${i}-${hop}`}

Also applies to: 199-206

src/components/Modal/node/OpenSessionModal.tsx (1)

459-467: Add basic listenHost validation (host:port)

A malformed listenHost will bounce at the API. Add a lightweight check and surface inline error.

-            <TextField
+            <TextField
               label="Listen host"
               placeholder={'127.0.0.1:10000'}
               value={listenHost}
               onChange={(event) => {
                 set_listenHost(event?.target?.value);
               }}
+              error={!!listenHost && !/^[\[\]a-zA-Z0-9._:-]+:\d{1,5}$/.test(listenHost)}
+              helperText={
+                !!listenHost && !/^[\[\]a-zA-Z0-9._:-]+:\d{1,5}$/.test(listenHost)
+                  ? 'Expected host:port (IPv4/IPv6-with-brackets supported)'
+                  : undefined
+              }
               fullWidth
             />
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3790bb4 and 3db8630.

📒 Files selected for processing (3)
  • src/components/Modal/node/OpenSessionModal.tsx (8 hunks)
  • src/pages/node/info/index.tsx (3 hunks)
  • src/pages/node/sessions.tsx (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/pages/node/info/index.tsx
🧰 Additional context used
🧬 Code graph analysis (2)
src/pages/node/sessions.tsx (1)
src/components/Modal/node/PingModal.tsx (1)
  • PingModal (22-186)
src/components/Modal/node/OpenSessionModal.tsx (2)
src/store/index.ts (1)
  • useAppSelector (26-26)
src/future-hopr-lib-components/Modal/styled.tsx (1)
  • TopBar (15-20)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Publish
🔇 Additional comments (4)
src/pages/node/sessions.tsx (1)

18-18: Ping controls on endpoint hops — LGTM

Using PingModal at the first forward hop and last return hop is a sensible UX compromise.

Also applies to: 182-183, 205-206

src/components/Modal/node/OpenSessionModal.tsx (3)

344-347: Closing modal no longer resets key fields — confirm UX

With these resets commented out, subsequent openings keep prior values. If intentional, ignore; otherwise restore resets or add an explicit “Reset” action.


237-239: Confirm SDK contract for maxSurbUpstream and maxClientSessions — provide SDK version/types

I need the exact @hoprnet/hopr-sdk prerelease (package.json version or commit) or the SDK's OpenSessionPayloadType definition so I can verify whether maxSurbUpstream should be a number (bytes/sec or bits/sec) or a unit-suffixed string (kB/s vs kb/s) and the expected type/bounds for maxClientSessions.
Applies to: src/components/Modal/node/OpenSessionModal.tsx lines 237–239 and 120–122.


584-592: Verify that "NoRateControl" is a supported session capability in the SDK / hoprd API

package.json pins @hoprnet/hopr-sdk@3.0.3-pr.156-20250916114518 and this component pushes "NoRateControl" into sessionPayload.capabilities (src/components/Modal/node/OpenSessionModal.tsx); I couldn't find "NoRateControl" in the SDK docs/repo — confirm the exact capability string accepted by the SDK or hoprd OpenAPI and update the payload/label if it differs. (github.com)

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Protect RPC provider address LastSeen column in Alias tab always shows Not seen

1 participant