Skip to content

feat(supportedVersions): role-targeted expiration messages - #3371

Merged
jeanfbrito merged 2 commits into
masterfrom
feat/supported-versions-message-roles
Jun 25, 2026
Merged

feat(supportedVersions): role-targeted expiration messages#3371
jeanfbrito merged 2 commits into
masterfrom
feat/supported-versions-message-roles

Conversation

@ggazzo

@ggazzo ggazzo commented Jun 23, 2026

Copy link
Copy Markdown
Member

Jira: ARCH-2192

Proposal

Add an optional roles field to the supportedVersions Message so the signed payload can restrict a version-expiration warning to specific roles (e.g. workspace admins), instead of showing it to every logged-in user.

Behavior

  • Message.roles omitted or empty → message shown to everyone. Keeps current behavior; clients that predate the field ignore it, so existing payloads are unaffected.
  • Message.roles present → message shown only to users whose roles intersect the list. If the client doesn't know the user's roles, the message is not shown (honors the restriction).

How roles are known (hybrid)

The desktop had no notion of user role. Roles are now acquired two ways, with a clear precedence:

  1. Bridge (authoritative, reactive)RocketChatDesktop.setUserRoles(roles). The web client observes its own reactive user and pushes roles. No token handling, no REST, and role changes propagate live.
  2. REST fallback — on login, if the bridge hasn't provided roles, the desktop reads them from /api/v1/me using the session token already present in the webview localStorage. Covers web clients that predate the bridge call. Skipped once the bridge provides roles; bridge values always win.

Any failure leaves roles unset, which makes role-targeted messages fall back to being shown to everyone — the pre-existing behavior.

The webview hosting the web app runs with contextIsolation: true, so the preload cannot read the page's Meteor user directly, and Meteor's localStorage only holds the login token + userId (not roles). Hence the bridge push / REST fetch rather than direct observation.

Why

The expiration warning is only actionable by admins. Targeting it via the signed payload lets us tune who sees the warning without shipping a desktop release for each change.

Changes

  • Message.roles?: string[] + Server.userRoles?: string[]
  • Role-based filtering in getExpirationMessage (all three branches: exception, supported version, enforcement)
  • WEBVIEW_USER_ROLES_CHANGED action + reducer case
  • RocketChatDesktop.setUserRoles bridge method
  • servers/preload/userRoles.ts — bridge push + /api/v1/me fallback, cleared on logout
  • Tests: message targeting (4 cases) + role acquisition precedence (5 cases)

Notes

  • The supportedVersions payload is a shared contract (also consumed by mobile, produced by cloud). The roles field should be agreed centrally so other clients honor it; this PR is the desktop side. The setUserRoles bridge call is the matching web-client change.
  • Filtering is client-side UX, not a security boundary.

Verification

  • tsc --noEmit clean
  • eslint clean on changed files
  • jest → 81 passed (incl. 9 new across two specs)

Add an optional `roles` field to supportedVersions `Message`. When set, an
expiration message is only shown to users whose roles intersect the list;
when omitted (or empty) the message is shown to everyone, so older clients
and existing payloads are unaffected.

To evaluate targeting, capture the logged-in user's roles from the workspace
REST API (/api/v1/me) on login and store them on the server state. Any
failure leaves roles unset, which falls back to showing the message to all
users — the pre-existing behavior.

This lets the signed payload restrict version warnings to admins (the only
ones who can act on them) without a desktop release for each targeting change.
@coderabbitai

coderabbitai Bot commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

Adds role-based targeting for server version expiration messages. A new preloader module supports role sourcing from a bridge API (with setUserRoles) and a REST fallback that fetches roles from /api/v1/me on login, clearing them on logout. Roles are dispatched via WEBVIEW_USER_ROLES_CHANGED action and persisted in Redux. Expiration messages are filtered by role intersection before display. The new setUserRoles capability is exposed on window.RocketChatDesktop.

Changes

Role-based expiration message targeting

Layer / File(s) Summary
Data contracts
src/servers/common.ts, src/servers/supportedVersions/types.ts, src/ui/actions.ts
Server gains optional userRoles?: string[], Message gains optional roles?: string[], and WEBVIEW_USER_ROLES_CHANGED is added as an action constant with typed payload { url, userRoles }.
Role fetch preloader: bridge and REST fallback
src/servers/preload/userRoles.ts
New module with buildMeEndpoint, setUserRoles (bridge entry point validating string array and setting rolesProvidedByBridge flag), updateUserRoles (REST fallback reading auth tokens from localStorage, fetching /api/v1/me with X-Auth-Token/X-User-Id headers, re-checking bridge flag post-fetch to prevent override, validating roles, and dispatching), and clearUserRoles (resetting bridge flag and dispatching undefined).
Login/logout wiring
src/servers/preload/userLoggedIn.ts
Imports updateUserRoles and clearUserRoles, extends setUserLoggedIn to trigger updateUserRoles() on login and clearUserRoles() on logout.
Preload API exposure
src/servers/preload/api.ts
Imports setUserRoles from ./userRoles, adds setUserRoles(roles: string[]) to ExtendedIRocketChatDesktop interface, and wires it into the RocketChatDesktop object for window.RocketChatDesktop.setUserRoles() availability.
Redux reducer
src/servers/reducers.ts
Imports WEBVIEW_USER_ROLES_CHANGED, extends ServersActionTypes union, and adds reducer case that upserts userRoles onto the matching server entry by URL.
Role-targeted message filtering
src/servers/supportedVersions/main.ts
Introduces messageMatchesUserRoles to gate visibility on role intersection; extends getExpirationMessage to accept optional userRoles and pre-filter messages by role before sorting. Passes server.userRoles at all three getExpirationMessage call sites within isServerVersionSupported.
Test coverage
src/servers/preload/userRoles.spec.ts, src/servers/supportedVersions/main.main.spec.ts
New userRoles.spec.ts validates setUserRoles dispatches validated roles, updateUserRoles fetches /api/v1/me on cache miss with correct headers, bridge prevents fallback fetch, and missing session token prevents fetch. New isServerVersionSupported test block covers role-absent messages (visible to all), role-matched messages (visible when user role intersects), role-unmatched messages (hidden when user role absent), and unknown-roles cases.

Sequence Diagram(s)

sequenceDiagram
  participant setUserLoggedIn as setUserLoggedIn (preload)
  participant userRoles as userRoles.ts
  participant localStorage
  participant API as Workspace /api/v1/me
  participant store as Redux store
  participant reducer as servers reducer
  participant main as isServerVersionSupported

  setUserLoggedIn->>userRoles: updateUserRoles() [on login]
  userRoles->>localStorage: read Meteor.loginToken, Meteor.userId
  userRoles->>API: GET /api/v1/me (X-Auth-Token, X-User-Id)
  API-->>userRoles: { roles: string[] }
  userRoles->>store: dispatch WEBVIEW_USER_ROLES_CHANGED({ url, userRoles })
  store->>reducer: WEBVIEW_USER_ROLES_CHANGED
  reducer-->>store: upsert server.userRoles

  main->>main: getExpirationMessage({ messages, userRoles: server.userRoles })
  main->>main: messageMatchesUserRoles — filter by role intersection
  main-->>main: first eligible message or undefined

  setUserLoggedIn->>userRoles: clearUserRoles() [on logout]
  userRoles->>store: dispatch WEBVIEW_USER_ROLES_CHANGED({ url, userRoles: undefined })
  store->>reducer: WEBVIEW_USER_ROLES_CHANGED
  reducer-->>store: upsert server.userRoles to undefined
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~28 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The PR title accurately describes the primary feature: adding role-targeted expiration messages to the supportedVersions system, which is the main objective of all changes across files.
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


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: 2

🤖 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 `@src/servers/preload/userRoles.ts`:
- Around line 23-24: The comment describing the fallback behavior for unset
roles in the userRoles.ts file incorrectly states that role-targeted messages
fall back to being shown to everyone. However, the actual runtime filtering
logic in messageMatchesUserRoles (located in
src/servers/supportedVersions/main.ts) hides targeted messages when userRoles is
unknown rather than showing them to everyone. Update the comment to accurately
reflect the true behavior: when roles are unset or unknown, role-targeted
messages are hidden from users, not displayed to everyone.
- Around line 26-50: The updateUserRoles function can persist stale role data
when async responses complete after the authentication context changes (logout
or account switch). After the fetch completes in updateUserRoles, re-validate
that the current authToken and userId from localStorage still match the values
used to initiate the fetch. If they do not match, this indicates the user has
logged out or switched accounts, and you should call dispatchUserRoles with an
empty array to clear stale roles instead of persisting the fetched roles.
Additionally, ensure that failed fetch responses (when response.ok is false)
also clear the existing roles by dispatching an empty array rather than silently
returning and leaving stale data 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 033ec533-ff9a-4b59-8134-39016d82cea2

📥 Commits

Reviewing files that changed from the base of the PR and between 41b0d7e and 2049049.

📒 Files selected for processing (8)
  • src/servers/common.ts
  • src/servers/preload/userLoggedIn.ts
  • src/servers/preload/userRoles.ts
  • src/servers/reducers.ts
  • src/servers/supportedVersions/main.main.spec.ts
  • src/servers/supportedVersions/main.ts
  • src/servers/supportedVersions/types.ts
  • src/ui/actions.ts
📜 Review details
⏰ Context from checks skipped due to timeout. (3)
  • GitHub Check: check (ubuntu-latest)
  • GitHub Check: check (macos-latest)
  • GitHub Check: check (windows-latest)
🧰 Additional context used
📓 Path-based instructions (6)
**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.ts: Use TypeScript for all new code unless explicitly told otherwise
Use optional chaining with fallbacks for platform-specific APIs instead of mocking when possible. Example: const uid = process.getuid?.() ?? 1000;

Files:

  • src/servers/supportedVersions/types.ts
  • src/servers/common.ts
  • src/servers/preload/userLoggedIn.ts
  • src/servers/reducers.ts
  • src/servers/supportedVersions/main.main.spec.ts
  • src/servers/preload/userRoles.ts
  • src/servers/supportedVersions/main.ts
  • src/ui/actions.ts
**/*.{tsx,ts}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{tsx,ts}: MANDATORY: Use Fuselage components for all UI work. Only create custom components when Fuselage doesn't provide what's needed
Import UI components from @rocket.chat/fuselage and check Theme.d.ts for valid color tokens
Use React functional components with hooks
Use PascalCase for component file names

Files:

  • src/servers/supportedVersions/types.ts
  • src/servers/common.ts
  • src/servers/preload/userLoggedIn.ts
  • src/servers/reducers.ts
  • src/servers/supportedVersions/main.main.spec.ts
  • src/servers/preload/userRoles.ts
  • src/servers/supportedVersions/main.ts
  • src/ui/actions.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Redux actions must follow FSA (Flux Standard Action) pattern
Avoid unnecessary comments — write self-documenting code through clear naming
Always verify libraries by checking official docs and .d.ts files in node_modules/. Never assume props, tokens, or APIs work without verification
Avoid subjective descriptors ('smart', 'excellent', 'dumb') in documentation and comments
Use measurable descriptions in code documentation: 'reduced memory usage', 'improved by X%' instead of subjective claims
NEVER invent metrics — don't include estimated time spent or speculated user counts. Only include numbers from actual logs, error messages, or documented sources

Files:

  • src/servers/supportedVersions/types.ts
  • src/servers/common.ts
  • src/servers/preload/userLoggedIn.ts
  • src/servers/reducers.ts
  • src/servers/supportedVersions/main.main.spec.ts
  • src/servers/preload/userRoles.ts
  • src/servers/supportedVersions/main.ts
  • src/ui/actions.ts
**/*.spec.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Use *.spec.ts file naming for Renderer process tests

Files:

  • src/servers/supportedVersions/main.main.spec.ts
**/*.main.spec.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Use *.main.spec.ts file naming for Main process tests

Files:

  • src/servers/supportedVersions/main.main.spec.ts
**/*.{spec.ts,main.spec.ts}

📄 CodeRabbit inference engine (CLAUDE.md)

Only mock platform-specific APIs when defensive coding isn't possible. Linux-only APIs requiring mocks: process.getuid(), process.getgid(), process.geteuid(), process.getegid()

Files:

  • src/servers/supportedVersions/main.main.spec.ts
🔇 Additional comments (8)
src/servers/supportedVersions/main.ts (2)

187-231: LGTM!


386-386: LGTM!

Also applies to: 412-412, 433-433

src/servers/supportedVersions/main.main.spec.ts (1)

914-978: LGTM!

src/servers/common.ts (1)

20-20: LGTM!

src/servers/supportedVersions/types.ts (1)

11-18: LGTM!

src/ui/actions.ts (1)

85-85: LGTM!

Also applies to: 238-241

src/servers/preload/userLoggedIn.ts (1)

5-5: LGTM!

Also applies to: 16-20

src/servers/reducers.ts (1)

19-19: LGTM!

Also applies to: 60-60, 200-204

Comment thread src/servers/preload/userRoles.ts Outdated
Comment on lines +23 to +24
* roles unset, which makes role-targeted messages fall back to being shown to
* everyone — the pre-existing behavior.

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.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Fix the fallback-behavior comment to match actual role filtering.

The comment says unset roles make role-targeted messages visible to everyone, but runtime filtering (src/servers/supportedVersions/main.ts, messageMatchesUserRoles) hides targeted messages when userRoles is unknown. This mismatch can lead to incorrect follow-up changes.

🤖 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 `@src/servers/preload/userRoles.ts` around lines 23 - 24, The comment
describing the fallback behavior for unset roles in the userRoles.ts file
incorrectly states that role-targeted messages fall back to being shown to
everyone. However, the actual runtime filtering logic in messageMatchesUserRoles
(located in src/servers/supportedVersions/main.ts) hides targeted messages when
userRoles is unknown rather than showing them to everyone. Update the comment to
accurately reflect the true behavior: when roles are unset or unknown,
role-targeted messages are hidden from users, not displayed to everyone.

Comment on lines +26 to +50
export const updateUserRoles = async (): Promise<void> => {
try {
const serverUrl = getServerUrl();
if (!serverUrl) return;

const authToken = localStorage.getItem('Meteor.loginToken');
const userId = localStorage.getItem('Meteor.userId');
if (!authToken || !userId) return;

const response = await fetch(buildMeEndpoint(serverUrl), {
headers: {
'X-Auth-Token': authToken,
'X-User-Id': userId,
},
});
if (!response.ok) return;

const data = await response.json();
const roles: unknown = data?.roles;
if (!Array.isArray(roles)) return;

const userRoles = roles.filter(
(role): role is string => typeof role === 'string'
);
dispatchUserRoles(userRoles);

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.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Prevent stale roles from being persisted across failed or outdated fetches.

updateUserRoles can keep/apply stale role data: failed refresh paths never clear existing roles, and late async responses can overwrite state after auth context changes (logout/account switch). Guard the response against current auth context and clear stale roles before refresh.

💡 Suggested fix
 export const updateUserRoles = async (): Promise<void> => {
   try {
     const serverUrl = getServerUrl();
-    if (!serverUrl) return;
+    if (!serverUrl) {
+      dispatchUserRoles(undefined);
+      return;
+    }

     const authToken = localStorage.getItem('Meteor.loginToken');
     const userId = localStorage.getItem('Meteor.userId');
-    if (!authToken || !userId) return;
+    if (!authToken || !userId) {
+      dispatchUserRoles(undefined);
+      return;
+    }
+
+    // Avoid reusing stale roles while refresh is in flight.
+    dispatchUserRoles(undefined);

     const response = await fetch(buildMeEndpoint(serverUrl), {
       headers: {
         'X-Auth-Token': authToken,
         'X-User-Id': userId,
       },
     });
     if (!response.ok) return;

     const data = await response.json();
     const roles: unknown = data?.roles;
     if (!Array.isArray(roles)) return;

     const userRoles = roles.filter(
       (role): role is string => typeof role === 'string'
     );
+
+    // Drop stale responses if auth context changed while awaiting network/JSON.
+    if (
+      localStorage.getItem('Meteor.loginToken') !== authToken ||
+      localStorage.getItem('Meteor.userId') !== userId
+    ) {
+      return;
+    }
+
     dispatchUserRoles(userRoles);
   } catch {
🤖 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 `@src/servers/preload/userRoles.ts` around lines 26 - 50, The updateUserRoles
function can persist stale role data when async responses complete after the
authentication context changes (logout or account switch). After the fetch
completes in updateUserRoles, re-validate that the current authToken and userId
from localStorage still match the values used to initiate the fetch. If they do
not match, this indicates the user has logged out or switched accounts, and you
should call dispatchUserRoles with an empty array to clear stale roles instead
of persisting the fetched roles. Additionally, ensure that failed fetch
responses (when response.ok is false) also clear the existing roles by
dispatching an empty array rather than silently returning and leaving stale data
in place.

Make role acquisition hybrid. Expose RocketChatDesktop.setUserRoles so the
web client can push the logged-in user's roles reactively (no token handling,
propagates on role changes). The /api/v1/me fetch becomes a fallback used only
when the bridge hasn't provided roles, covering web clients that predate the
bridge call. Bridge-provided roles always take precedence over the fetch.

@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.

Caution

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

⚠️ Outside diff range comments (1)
src/servers/preload/userRoles.ts (1)

65-76: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Preserve bridge precedence after JSON parsing.

Line 67 checks rolesProvidedByBridge before await response.json(), but bridge roles can arrive during JSON parsing. Then Line 76 can still dispatch REST roles and overwrite authoritative bridge roles.

Suggested fix
-    // The bridge may have resolved while the request was in flight; if so, keep
-    // the authoritative value.
-    if (rolesProvidedByBridge) return;
-
-    const data = await response.json();
+    // The bridge may resolve at any point while this async path is running.
+    if (rolesProvidedByBridge) return;
+    const data = await response.json();
+    if (rolesProvidedByBridge) return;
     const roles: unknown = data?.roles;
     if (!Array.isArray(roles)) return;

     const userRoles = roles.filter(
       (role): role is string => typeof role === 'string'
     );
+    if (rolesProvidedByBridge) return;
     dispatchUserRoles(userRoles);
🤖 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 `@src/servers/preload/userRoles.ts` around lines 65 - 76, The
rolesProvidedByBridge flag is checked at the beginning before JSON parsing, but
since bridge roles can arrive asynchronously during the await response.json()
call, the flag can become true before the dispatchUserRoles(userRoles) call at
the end. Add a second check for rolesProvidedByBridge right before calling
dispatchUserRoles to ensure that if bridge roles arrived during JSON parsing,
the REST roles are not dispatched and do not overwrite the authoritative bridge
values.
🧹 Nitpick comments (1)
src/servers/preload/userRoles.spec.ts (1)

78-87: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add an explicit in-flight precedence test (fetch started before bridge update).

Current coverage checks pre-fetch bridge precedence, but not the race where bridge roles arrive while /api/v1/me is still pending. A dedicated deferred-fetch test would lock this contract down.

♻️ Suggested test addition
+  it('does not override bridge roles when bridge updates during an in-flight fetch', async () => {
+    let resolveFetch!: (value: {
+      ok: boolean;
+      json: () => Promise<{ roles: unknown }>;
+    }) => void;
+    const fetchPromise = new Promise<{
+      ok: boolean;
+      json: () => Promise<{ roles: unknown }>;
+    }>((resolve) => {
+      resolveFetch = resolve;
+    });
+
+    global.fetch = jest.fn(() => fetchPromise) as unknown as typeof global.fetch;
+
+    const pendingUpdate = updateUserRoles();
+    setUserRoles(['admin']);
+    resolveFetch({ ok: true, json: async () => ({ roles: ['user'] }) });
+    await pendingUpdate;
+
+    expect(dispatchMock).toHaveBeenCalledTimes(1);
+    expect(dispatchMock).toHaveBeenCalledWith({
+      type: WEBVIEW_USER_ROLES_CHANGED,
+      payload: { url: 'https://rocket.chat', userRoles: ['admin'] },
+    });
+  });
🤖 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 `@src/servers/preload/userRoles.spec.ts` around lines 78 - 87, Add a new test
case that verifies the race condition scenario where the bridge provides roles
while an in-flight fetch request to /api/v1/me is still pending. Create a test
that sets up a deferred fetch promise (that doesn't resolve immediately), calls
updateUserRoles() to initiate the fetch, then invokes setUserRoles() to simulate
the bridge providing roles while the fetch is still pending, and finally verify
that dispatch is not called with fetch results and that the pending fetch
promise is properly handled. This test complements the existing test which only
checks the scenario where bridge roles arrive before fetch is initiated.
🤖 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.

Outside diff comments:
In `@src/servers/preload/userRoles.ts`:
- Around line 65-76: The rolesProvidedByBridge flag is checked at the beginning
before JSON parsing, but since bridge roles can arrive asynchronously during the
await response.json() call, the flag can become true before the
dispatchUserRoles(userRoles) call at the end. Add a second check for
rolesProvidedByBridge right before calling dispatchUserRoles to ensure that if
bridge roles arrived during JSON parsing, the REST roles are not dispatched and
do not overwrite the authoritative bridge values.

---

Nitpick comments:
In `@src/servers/preload/userRoles.spec.ts`:
- Around line 78-87: Add a new test case that verifies the race condition
scenario where the bridge provides roles while an in-flight fetch request to
/api/v1/me is still pending. Create a test that sets up a deferred fetch promise
(that doesn't resolve immediately), calls updateUserRoles() to initiate the
fetch, then invokes setUserRoles() to simulate the bridge providing roles while
the fetch is still pending, and finally verify that dispatch is not called with
fetch results and that the pending fetch promise is properly handled. This test
complements the existing test which only checks the scenario where bridge roles
arrive before fetch is initiated.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 5a95b732-8f9d-46bb-b6bb-02fd0a82ea60

📥 Commits

Reviewing files that changed from the base of the PR and between 2049049 and 1f97098.

📒 Files selected for processing (3)
  • src/servers/preload/api.ts
  • src/servers/preload/userRoles.spec.ts
  • src/servers/preload/userRoles.ts
📜 Review details
⏰ Context from checks skipped due to timeout. (3)
  • GitHub Check: check (windows-latest)
  • GitHub Check: check (ubuntu-latest)
  • GitHub Check: check (macos-latest)
🧰 Additional context used
📓 Path-based instructions (5)
**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.ts: Use TypeScript for all new code unless explicitly told otherwise
Use optional chaining with fallbacks for platform-specific APIs instead of mocking when possible. Example: const uid = process.getuid?.() ?? 1000;

Files:

  • src/servers/preload/userRoles.spec.ts
  • src/servers/preload/userRoles.ts
  • src/servers/preload/api.ts
**/*.{tsx,ts}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{tsx,ts}: MANDATORY: Use Fuselage components for all UI work. Only create custom components when Fuselage doesn't provide what's needed
Import UI components from @rocket.chat/fuselage and check Theme.d.ts for valid color tokens
Use React functional components with hooks
Use PascalCase for component file names

Files:

  • src/servers/preload/userRoles.spec.ts
  • src/servers/preload/userRoles.ts
  • src/servers/preload/api.ts
**/*.spec.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Use *.spec.ts file naming for Renderer process tests

Files:

  • src/servers/preload/userRoles.spec.ts
**/*.{spec.ts,main.spec.ts}

📄 CodeRabbit inference engine (CLAUDE.md)

Only mock platform-specific APIs when defensive coding isn't possible. Linux-only APIs requiring mocks: process.getuid(), process.getgid(), process.geteuid(), process.getegid()

Files:

  • src/servers/preload/userRoles.spec.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Redux actions must follow FSA (Flux Standard Action) pattern
Avoid unnecessary comments — write self-documenting code through clear naming
Always verify libraries by checking official docs and .d.ts files in node_modules/. Never assume props, tokens, or APIs work without verification
Avoid subjective descriptors ('smart', 'excellent', 'dumb') in documentation and comments
Use measurable descriptions in code documentation: 'reduced memory usage', 'improved by X%' instead of subjective claims
NEVER invent metrics — don't include estimated time spent or speculated user counts. Only include numbers from actual logs, error messages, or documented sources

Files:

  • src/servers/preload/userRoles.spec.ts
  • src/servers/preload/userRoles.ts
  • src/servers/preload/api.ts
🪛 ast-grep (0.44.0)
src/servers/preload/userRoles.spec.ts

[warning] 30-30: Do not store sensitive data (credentials, tokens, PII) in localStorage or sessionStorage; it is readable by any script and persists on the device.
Context: localStorage.setItem('Meteor.loginToken', 'token')
Note: [CWE-312] Cleartext Storage of Sensitive Information.

(local-storage-sensitive-data-typescript)

🔇 Additional comments (3)
src/servers/preload/userRoles.spec.ts (1)

1-77: LGTM!

Also applies to: 89-97

src/servers/preload/userRoles.ts (1)

42-44: Fallback-behavior comments still conflict with runtime behavior.

Already raised in prior review; keeping as duplicate to avoid restating.

Also applies to: 78-80

src/servers/preload/api.ts (1)

43-43: LGTM!

Also applies to: 63-63, 92-92

@jeanfbrito

Copy link
Copy Markdown
Member

Reviewed the desktop side end-to-end against current master. Verified in an isolated worktree off pull/3371/head: 81 tests pass, tsc --noEmit clean.

Looks solid

  • Backward compatibility is correct. messageMatchesUserRoles returns true when Message.roles is absent/empty, so existing payloads and older clients are unaffected.
  • clearUserRoles genuinely clearsupsert spreads {...server, ...update}, so dispatching userRoles: undefined overwrites the stored value rather than being skipped.
  • REST fallback follows an established patternuserRoles.ts mirrors internalVideoChatWindow.ts (localStorage token + X-Auth-Token/X-User-Id headers), running in the same preload/webview context. Confirmed /api/v1/me returns a top-level roles: string[].
  • Bridge precedence race is handledrolesProvidedByBridge is checked both before the fetch and again after it resolves.
  • Login trigger timing is soundsetUserLoggedIn fires from the Meteor.userId() autorun in injected.ts, by which point the login token is present.

Notes (non-blocking)

  • Bridge contract dependency is satisfied by feat(desktop): push user roles to the desktop app Rocket.Chat#41056, which declares setUserRoles on IRocketChatDesktop + ships the changeset. The two are mutually consistent (name/arity/type align) and both degrade gracefully, so they can merge independently.
  • autorun re-fetch: for web clients that never call the bridge, each setUserLoggedIn(true) re-fires updateUserRoles(). Meteor.userId() is stable per session so this is effectively once per login — fine as-is; a fetched-flag would close it if churn ever shows up.
  • Agree with the framing that this is client-side UX, not a security boundary.

Approving.

@jeanfbrito
jeanfbrito merged commit d0dee3f into master Jun 25, 2026
10 checks passed
@jeanfbrito
jeanfbrito deleted the feat/supported-versions-message-roles branch June 25, 2026 14:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants