Skip to content

feat: auto-clear stale cache on server version change#3298

Open
jeanfbrito wants to merge 3 commits intodevfrom
feat/smart-cache-clear
Open

feat: auto-clear stale cache on server version change#3298
jeanfbrito wants to merge 3 commits intodevfrom
feat/smart-cache-clear

Conversation

@jeanfbrito
Copy link
Copy Markdown
Member

@jeanfbrito jeanfbrito commented Apr 13, 2026

Summary

Detects Rocket.Chat server upgrades and clears the stale client-side Meteor bundle cache that otherwise forces users to reload manually after an admin-side deploy.

What's in

  • Tighter cache clear — only IndexedDB + service workers + cachestorage + HTTP cache. Cookies, localStorage, drafts, login token, and UI preferences are preserved.
  • Pre-auth upgrade detection via /app/utils/rocketchat.info (Info.commit.hash + Info.version) and the cache_version cookie, compared against per-server persisted values. On first observation, values are recorded; on subsequent change, the cache is cleared.
  • Post-auth safety net — existing WEBVIEW_GIT_COMMIT_HASH_CHECK path kept in place.
  • In-flight dedup so concurrent build-check signals for the same server can't trigger duplicate clears.

What's out

An earlier commit on this branch also introduced a <webview> destroy+remount flow via a React key nonce, intended to rescue stuck webviews. That path produced a gray-screen regression on the second reload and has been reverted in `e69e71fba`. Both the manual "Clear trusted content" dialog and the auto-clear path now fall back to `reloadIgnoringCache()` after clearing storage — the pre-PR behavior on the reload side.

Test plan

  • Log in to a server; confirm admin preferences and drafts persist after an auto-clear is triggered
  • Deploy a new build on the target server; confirm the client auto-clears on next focus/load without user action
  • Use the right-click menu "Reload server — Clear trusted content" → "Keep login data"; confirm login survives the reload
  • Use the same menu with "Delete login data"; confirm user is logged out and reloaded to the login screen

Summary by CodeRabbit

  • New Features

    • Added automatic detection of server build and cache version updates.
  • Improvements

    • Optimized cache clearing to intelligently refresh outdated data when servers update while preserving active login sessions.

Detects server upgrades pre-auth by reading commit.hash (or version) from
/app/utils/rocketchat.info and the cache_version cookie, comparing against
per-server persisted values. On change, clears only the Meteor bundle
cache (IndexedDB + service workers + cachestorage + HTTP cache) — keeping
cookies, localStorage drafts, login token, and UI preferences intact —
then destroys and recreates the webview via a React key nonce.

The recreate path also fixes a long-standing issue where the webview
could get stuck and ignore reload() calls, previously only recoverable
by removing the server or restarting the app.

- Tighten clearWebviewStorageKeepingLoginData to the minimal storage set
- Add lastServerBuildId / lastCacheVersion / webviewNonce fields to Server
- Add WEBVIEW_SERVER_BUILD_CHECK pre-auth listener with first-observation
  recording and change-triggered clear
- Add SERVER_WEBVIEW_RECREATE_REQUESTED action that bumps webviewNonce
- Update ServersView key to include webviewNonce so React remounts the
  webview element on bump
- Keep the existing post-auth WEBVIEW_GIT_COMMIT_HASH_CHECK as safety net
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 13, 2026

Walkthrough

The PR implements build and cache version tracking for webview servers. It detects server build or cache version changes from the preload layer, signals the main process, and automatically clears webview storage while preserving login data when changes occur.

Changes

Cohort / File(s) Summary
UI Action Definitions
src/ui/actions.ts
Added two new action type constants (WEBVIEW_SERVER_BUILD_CHECK, WEBVIEW_SERVER_BUILD_UPDATED) and their corresponding payload type mappings with url, optional buildId, and optional cacheVersion fields.
Preload Build Signaling
src/servers/preload/serverBuild.ts, src/servers/preload/api.ts
Introduced new module serverBuild.ts to dispatch build signals; extended api.ts to extract build commit hash and cache version from cookies, compute a buildId, and trigger signaling via the new setServerBuildSignals function.
Main Process Event Handling
src/servers/main.ts
Added event handler for WEBVIEW_SERVER_BUILD_CHECK that detects build or cache version changes, uses a per-URL lock to gate concurrent work, clears webview storage (preserving login data) when changes are detected, and dispatches the WEBVIEW_SERVER_BUILD_UPDATED action.
State & Type Management
src/servers/reducers.ts, src/servers/common.ts
Extended Server type with lastServerBuildId and lastCacheVersion optional fields; added reducer case to handle WEBVIEW_SERVER_BUILD_UPDATED and selectively update these new fields.
Cache Clearing Logic
src/servers/cache.ts
Refined the "keep login data" storage clearing path to exclude cookies, filesystem, shadercache, and websql; removed optional chaining from reload calls.

Sequence Diagram

sequenceDiagram
    participant Preload as Preload (WebView)
    participant Main as Main Process
    participant Redux as Redux State
    participant Cache as Cache Manager

    Preload->>Preload: setServerInfo() called<br/>with commit hash & version
    Preload->>Preload: Compute buildId from commit.hash
    Preload->>Preload: Extract cacheVersion from cookies
    Preload->>Main: Dispatch WEBVIEW_SERVER_BUILD_CHECK<br/>{url, buildId, cacheVersion}
    
    Main->>Main: Locate server by url
    Main->>Main: Classify event:<br/>First observation,<br/>Build change, or<br/>Cache version change?
    
    alt Build or Cache Change Detected
        Main->>Main: Acquire buildCheckInFlight lock for url
        Main->>Cache: clearWebviewStorageKeepingLoginData()
        Cache->>Cache: Clear all but indexdb,<br/>serviceworkers, cachestorage
        Main->>Redux: Dispatch WEBVIEW_SERVER_BUILD_UPDATED<br/>{url, buildId, cacheVersion}
        Redux->>Redux: Update lastServerBuildId<br/>and lastCacheVersion
    else First Observation or Initial Fill
        Main->>Redux: Dispatch WEBVIEW_SERVER_BUILD_UPDATED<br/>without clearing storage
    end
    
    Main->>Main: Release buildCheckInFlight lock
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main feature: automatically clearing stale cache when the server version changes. The changes across multiple files consistently implement this detection and cache-clearing workflow.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ 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 and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/servers/cache.ts`:
- Around line 3-15: Replace the direct use of select with the safeSelect helper
to avoid throwing when the Redux store isn't initialized: update the import line
to pull safeSelect from '../store' (instead of or in addition to select) and
change getServerUrlFromWebContents to call safeSelect(({ servers }) => servers)
so the servers lookup will return undefined safely; keep the rest of
getServerUrlFromWebContents (including the webContentsId match and ?.url)
unchanged.

In `@src/servers/main.ts`:
- Around line 209-263: The listener for WEBVIEW_SERVER_BUILD_CHECK can run
concurrent clear+recreate flows for the same url; add an in-flight deduplication
mechanism (e.g., a module-scoped Set<string> in the same file) keyed by url
inside the listen callback: before starting the clear path (the branch that
calls getWebContentsByServerUrl and await clearWebviewStorageKeepingLoginData),
check the Set and skip if the url is already in-progress, otherwise add the url
to the Set, perform the await, and remove the url from the Set in a finally
block so the flag is always cleared; apply this to the code paths that dispatch
WEBVIEW_SERVER_BUILD_UPDATED after clearWebviewStorageKeepingLoginData so
duplicate handlers won't run concurrently.

In `@src/servers/reducers.ts`:
- Around line 216-229: The reducer branches for WEBVIEW_SERVER_BUILD_UPDATED and
SERVER_WEBVIEW_RECREATE_REQUESTED currently call upsert(state, ...) which can
re-add a server removed during an in-flight async clear; change these to
update-only behavior by first locating the existing Server (using state.find((s)
=> s.url === url) or similar) and if none exists return state unchanged,
otherwise apply an update (not an upsert) to merge the partial patch into the
existing record (for WEBVIEW_SERVER_BUILD_UPDATED set
lastServerBuildId/lastCacheVersion on the found Server; for
SERVER_WEBVIEW_RECREATE_REQUESTED compute nextNonce from existing.webviewNonce
and update that server) so no new server entries are inserted.
🪄 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: 0066a86a-12e6-4cd0-bd39-5d609f2a5d49

📥 Commits

Reviewing files that changed from the base of the PR and between 7f961f4 and 307db21.

📒 Files selected for processing (9)
  • src/servers/actions.ts
  • src/servers/cache.ts
  • src/servers/common.ts
  • src/servers/main.ts
  • src/servers/preload/api.ts
  • src/servers/preload/serverBuild.ts
  • src/servers/reducers.ts
  • src/ui/actions.ts
  • src/ui/components/ServersView/index.tsx
📜 Review details
⏰ 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). (3)
  • GitHub Check: check (ubuntu-latest)
  • GitHub Check: check (windows-latest)
  • GitHub Check: check (macos-latest)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Use TypeScript for all new code in this codebase unless explicitly told otherwise
Use Fuselage components from @rocket.chat/fuselage for all UI work — only create custom components when Fuselage doesn't provide the needed functionality
Check Theme.d.ts for valid color tokens when working with Fuselage components
Use optional chaining with fallbacks for platform-specific APIs instead of mocks (e.g., process.getuid?.() ?? 1000) to ensure code works across all platforms without requiring mocks
TypeScript code must use strict mode
Use React functional components with hooks instead of class components
Redux actions must follow the FSA (Flux Standard Action) pattern
Use camelCase for file naming
Use PascalCase for component file names (React components)
Write self-documenting code through clear naming — avoid unnecessary comments

Files:

  • src/ui/components/ServersView/index.tsx
  • src/servers/common.ts
  • src/servers/preload/serverBuild.ts
  • src/servers/reducers.ts
  • src/servers/actions.ts
  • src/servers/preload/api.ts
  • src/servers/main.ts
  • src/ui/actions.ts
  • src/servers/cache.ts
🔇 Additional comments (4)
src/ui/components/ServersView/index.tsx (1)

12-12: Good use of a nonce-backed key here.

This gives ServerPane a guaranteed remount path when the recreate action fires, which matches the cleanup behavior in src/ui/components/ServersView/ServerPane.tsx:31-107.

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

74-81: Nice best-effort signal collection.

Using commit.hash with a version fallback, plus an optional cookie read, keeps the detection path resilient without making setServerInfo depend on either signal being present.

src/servers/cache.ts (2)

18-23: Recreate action helper looks good.

This keeps the dispatch sites tidy, and the { type, payload } shape is FSA-compliant.

As per coding guidelines, Redux actions must follow the FSA (Flux Standard Action) pattern.


29-38: Targeted clear + remount flow matches the PR intent.

Switching both paths to prefer webview recreation over reload should address the stuck-webview case, and the keep-login variant now limits storage clearing to the bundle-related stores instead of wiping auth/session data.

Also applies to: 47-52

- Use safeSelect in getServerUrlFromWebContents so the helper degrades to
  reloadIgnoringCache() instead of throwing if the Redux store isn't
  initialized when a clear is requested.
- Add per-URL in-flight dedup around the auto cache-clear branch so a
  second WEBVIEW_SERVER_BUILD_CHECK for the same server during the async
  clear window can't trigger a duplicate clear+recreate.
- Reducers for WEBVIEW_SERVER_BUILD_UPDATED and SERVER_WEBVIEW_RECREATE_REQUESTED
  now use update (with an early-return when the server is missing)
  instead of upsert, so a server removed mid-clear can't be re-inserted
  as a partial ghost record.
The webview recreate machinery introduced a gray-screen regression on
reload: the <webview> remount timing via ReparentingContainer leaves
the guest's src assignment silently dropped, leaving an empty surface
that can't be recovered without removing and re-adding the server.

Remove the Part C surface only:
- SERVER_WEBVIEW_RECREATE_REQUESTED action and reducer case
- webviewNonce field on Server
- React key nonce suffix in ServersView
- requestWebviewRecreate dispatches from clearWebviewStorage* paths

Keep Parts A and B: the tighter storage clear (indexdb + service
workers + cachestorage + HTTP cache, cookies/localStorage preserved)
and the pre-auth build-change detection via rocketchat.info commit
hash + cache_version cookie. Both the manual "Clear trusted content"
dialog and the auto-detection path now fall back to
reloadIgnoringCache() after clearing storage.
@jeanfbrito jeanfbrito changed the title feat: auto-clear cache and recreate webview on server version change feat: auto-clear stale cache on server version change Apr 13, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
src/servers/reducers.ts (1)

211-218: Avoid no-op state churn when payload has no patch fields.

If both buildId and cacheVersion are absent, this branch still reaches update() with { url }, creating a needless object update. Early-returning keeps referential stability and avoids unnecessary rerenders.

Suggested tweak
     case WEBVIEW_SERVER_BUILD_UPDATED: {
       const { url, buildId, cacheVersion } = action.payload;
+      if (buildId === undefined && cacheVersion === undefined) return state;
       if (!state.some((s) => s.url === url)) return state;
       const patch: Partial<Server> & { url: string } = { url };
       if (buildId !== undefined) patch.lastServerBuildId = buildId;
       if (cacheVersion !== undefined) patch.lastCacheVersion = cacheVersion;
       return update(state, patch as Server);
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/servers/reducers.ts` around lines 211 - 218, The
WEBVIEW_SERVER_BUILD_UPDATED branch currently calls update(state, patch as
Server) even when payload contains neither buildId nor cacheVersion, causing
no-op state churn; modify the handler in reducers.ts to check the extracted
buildId and cacheVersion from action.payload and early-return state if both are
undefined (i.e., only proceed to construct patch and call update(state, patch as
Server) when at least one of buildId or cacheVersion is present), keeping the
existing URL existence check (state.some((s) => s.url === url)).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/servers/reducers.ts`:
- Around line 211-218: The WEBVIEW_SERVER_BUILD_UPDATED branch currently calls
update(state, patch as Server) even when payload contains neither buildId nor
cacheVersion, causing no-op state churn; modify the handler in reducers.ts to
check the extracted buildId and cacheVersion from action.payload and
early-return state if both are undefined (i.e., only proceed to construct patch
and call update(state, patch as Server) when at least one of buildId or
cacheVersion is present), keeping the existing URL existence check
(state.some((s) => s.url === url)).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 64202316-d3ca-4bc0-90e8-0d46d407602b

📥 Commits

Reviewing files that changed from the base of the PR and between 30667c0 and e69e71f.

📒 Files selected for processing (3)
  • src/servers/cache.ts
  • src/servers/common.ts
  • src/servers/reducers.ts
✅ Files skipped from review due to trivial changes (1)
  • src/servers/common.ts
📜 Review details
⏰ 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). (3)
  • GitHub Check: check (windows-latest)
  • GitHub Check: check (macos-latest)
  • GitHub Check: check (ubuntu-latest)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Use TypeScript for all new code in this codebase unless explicitly told otherwise
Use Fuselage components from @rocket.chat/fuselage for all UI work — only create custom components when Fuselage doesn't provide the needed functionality
Check Theme.d.ts for valid color tokens when working with Fuselage components
Use optional chaining with fallbacks for platform-specific APIs instead of mocks (e.g., process.getuid?.() ?? 1000) to ensure code works across all platforms without requiring mocks
TypeScript code must use strict mode
Use React functional components with hooks instead of class components
Redux actions must follow the FSA (Flux Standard Action) pattern
Use camelCase for file naming
Use PascalCase for component file names (React components)
Write self-documenting code through clear naming — avoid unnecessary comments

Files:

  • src/servers/reducers.ts
  • src/servers/cache.ts
🧠 Learnings (2)
📚 Learning: 2026-04-01T15:41:58.866Z
Learnt from: jeanfbrito
Repo: RocketChat/Rocket.Chat.Electron PR: 3284
File: src/logging/index.ts:423-445
Timestamp: 2026-04-01T15:41:58.866Z
Learning: In the RocketChat/Rocket.Chat.Electron repository, Rocket.Chat server instances are always deployed at distinct origins (unique hostname or port combinations). Multiple servers sharing the same hostname but differing only by path (e.g., `https://host/team-a` vs `https://host/team-b`) is not a real-world deployment scenario. Therefore, comparing `new URL(s.url).origin` alone is sufficient for matching a webContents URL to its configured server in `src/logging/index.ts`, and path-based collision concerns do not apply.

Applied to files:

  • src/servers/reducers.ts
  • src/servers/cache.ts
📚 Learning: 2026-03-11T06:38:40.426Z
Learnt from: Ram-sah19
Repo: RocketChat/Rocket.Chat.Electron PR: 3254
File: .github/workflows/build-release.yml:80-94
Timestamp: 2026-03-11T06:38:40.426Z
Learning: In the RocketChat/Rocket.Chat.Electron repository, the issues flagged in `.github/workflows/build-release.yml` (e.g., `node12` runtime in the release action and missing `snapcraft_token` input), i18n files, and `electron-builder.json` are pre-existing in the `develop` branch and are pulled in during merge conflict resolution. Do not flag these as new issues introduced by PRs that only modify `src/injected.ts` and `src/ui/main/rootWindow.ts`.

Applied to files:

  • src/servers/reducers.ts
🔇 Additional comments (3)
src/servers/cache.ts (2)

15-17: Scoped storage clearing looks correct for “keep login data”.

Limiting clearStorageData to IndexedDB, Service Workers, and CacheStorage while reloading ignoring cache matches the intended “preserve auth + user state” behavior.


26-26: Good consistency in post-clear recovery path.

Using guestWebContents.reloadIgnoringCache() here keeps the delete-login flow aligned with the keep-login flow after storage clear.

src/servers/reducers.ts (1)

25-25: Good action wiring and reducer typing alignment.

WEBVIEW_SERVER_BUILD_UPDATED is correctly imported and added to ServersActionTypes, so the reducer stays typed for this new action path.

Also applies to: 57-57

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.

1 participant