Skip to content

fix(sidebar): refresh popup allowList after grantAccess succeeds#2850

Open
piyalbasu wants to merge 4 commits into
masterfrom
fix/sidebar-allowlist-refresh-after-grant
Open

fix(sidebar): refresh popup allowList after grantAccess succeeds#2850
piyalbasu wants to merge 4 commits into
masterfrom
fix/sidebar-allowlist-refresh-after-grant

Conversation

@piyalbasu

Copy link
Copy Markdown
Contributor

TL;DR

In sidebar mode, granting access to a dApp for the first time leaves the SignMessage / SignAuthEntry follow-up showing "domain not in allowlist" even though the backend just wrote the new entry. The bug is unrelated to auto-lock (uncovered while manually testing #2802 but reproduces independently) — the sidebar reuses the same React tree across consecutive signing flows, so the popup-side allowList stays stale.

This PR makes the grantAccess thunk refresh popup-side settings after the backend write succeeds. Sidebar SignMessage / SignAuthEntry continuations now see "connected" as intended. Popup-window flows already worked correctly because each browser.windows.create call mounts a fresh redux store; this just makes the sidebar surface behave the same way.

Details for future readers / agents

The bug

@stellar/freighter-api's signMessage and signAuthEntry both follow requestAllowedStatus → requestAccess → submit*. Each of those external calls invokes openSigningWindow in extension/src/background/messageListener/freighterApiMessageListener.ts:90-126, which has two code paths:

  • No sidebar open: browser.windows.create({...}) opens a new popup window. The new window mounts a fresh redux store, runs useGetAppData → loadSettings, picks up the just-granted allowList entry, and useIsDomainListedAllowed returns true. ✅
  • Sidebar open: posts a SIDEBAR_NAVIGATE message over the existing port and routes the same sidebar window to the next hash route. Same window, same redux store. No loadSettings refetch happens between requestAccess and submitMessage/submitAuthEntry, so state.settings.allowList still holds the pre-grant snapshot. useIsDomainListedAllowed (extension/src/popup/helpers/useIsDomainListedAllowed.ts) returns false → "domain not in allowlist" banner + disabled Sign button. ❌

Why it surfaced now

The auto-lock work in #2802 made it easy to hit by introducing a long pause between a requestAccess confirm and the next view. But the same bug reproduces on a fast, unlocked sidebar flow — it's just less likely because the user is typically already moving on. Confirmed by manual testing.

signTransaction is unaffected: its freighter-api flow goes straight to submitTransaction without a requestAccess precheck, and the SignTransaction view's "not in allowlist" banner is the intentional review-before-connect UX. Out of scope here.

The fix

extension/src/popup/ducks/access.ts grantAccess thunk now:

  1. Awaits internalGrantAccess (unchanged).
  2. Calls loadSettings() and dispatches saveSettingsAction(fresh) to refresh popup-side redux.
  3. Wraps the refresh in try/catch so a failed reload doesn't reject the grant — the backend write already succeeded, and a stale popup allowList is strictly better than failing the whole grant.

Other thunks that mutate backend state without refreshing popup-redux (saveAllowList, rejectAccess, setAllowed) have similar shapes but don't manifest the same UX bug today:

  • saveAllowList.fulfilled already updates state.allowList from its return payload (see popup/ducks/settings.ts:429-443).
  • rejectAccess doesn't modify the allowList — only clears the pending request.
  • setAllowed (Manage Connected Apps) re-renders on mount.

Out of scope for this PR. Worth a follow-up audit if more sidebar staleness bugs surface.

Tests

Two new tests in extension/src/popup/ducks/__tests__/access.test.ts:

  1. "refreshes popup-side settings (and allowList) after a successful grantAccess" — asserts the order of operations (grant before reload) and verifies state.settings.allowList reflects the just-granted domain.
  2. "swallows a loadSettings error so the grant still fulfills" — confirms the best-effort semantics; a transient loadSettings failure doesn't reject the thunk.

All related suites pass:

  • extension/src/popup/ducks/__tests__/ (4 suites, all green)
  • extension/src/popup/views/__tests__/GrantAccess.test.tsx
  • extension/src/popup/components/__tests__/SessionLockListener.test.tsx

Manual verification

Sidebar mode, fresh install on testnet:

  1. Open the sidebar
  2. Trigger a signMessage from a never-connected dApp (e.g. account-viewer-v2)
  3. Confirm the GrantAccess prompt → routed to SignMessage view in the same sidebar
  4. Before: "domain not in allowlist" banner, Sign disabled. After: shows the message details, Sign enabled.

Same flow for signAuthEntry works equivalently.

Test plan

  • CI lint + jest
  • Manual: sidebar signMessage against unconnected dApp → confirm SignMessage view shows the dApp as connected
  • Manual: sidebar signAuthEntry against unconnected dApp → same
  • Manual: popup-mode signMessage continues to work (no regression on the path that already worked)
  • Manual: signTransaction warning banner behaviour unchanged for unconnected dApps

🤖 Generated with Claude Code

In sidebar mode, `openSigningWindow` reuses the existing sidebar
window across consecutive signing flows (e.g. `signMessage` →
`requestAccess` → `signMessage` continuation) via `SIDEBAR_NAVIGATE`
over the long-lived port. The popup React tree and its redux store
are preserved, so `useIsDomainListedAllowed` keeps reading the
pre-grant `state.settings.allowList` even though the backend just
wrote the new domain to `localStore`.

Result: after granting access for the first time from a sidebar,
SignMessage / SignAuthEntry views render "domain not in allowlist"
even though the dApp is now connected.

Have the `grantAccess` thunk refresh settings after the backend
write so popup-side redux picks up the new allowList entry. Popup
windows opened via `browser.windows.create` already mount fresh
redux and refetch — this just makes the sidebar surface behave the
same way. Best-effort: a failed `loadSettings` doesn't reject the
grant.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings June 18, 2026 17:58
@github-actions

github-actions Bot commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

PR Preview build is ready: https://github.com/stellar/freighter/releases/tag/untagged-7649f3df12d039d32d1a (SDF collaborators only — install instructions in the release description)

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Fixes a sidebar-only UX bug where consecutive signing flows reuse the same popup React tree (and Redux store), leaving state.settings.allowList stale after a first-time grantAccess—which incorrectly shows “domain not in allowlist” on the follow-up signMessage / signAuthEntry continuation.

Changes:

  • Update the grantAccess thunk to refresh popup-side settings (via loadSettingssaveSettingsAction) after a successful grant, with best-effort error swallowing.
  • Add Jest coverage to verify (1) refresh occurs after grant and updates allowList, and (2) refresh failures don’t cause the thunk to reject.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
extension/src/popup/ducks/access.ts Refreshes popup-side Redux settings after grantAccess to prevent sidebar allowList staleness.
extension/src/popup/ducks/__tests__/access.test.ts Adds regression tests for the post-grant refresh and best-effort error handling.

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

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: cce2f34c13

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread extension/src/popup/ducks/access.ts Outdated
piyalbasu and others added 2 commits June 26, 2026 10:59
The best-effort settings refresh in grantAccess logged failures with
console.error, which never reaches Sentry. Use captureException so these
transient failures are observable, passing the error object to preserve
the stack trace.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Awaiting the settings reload before the grantAccess thunk resolved
delayed GrantAccess's window.close()/route-close. In sidebar mode the
reused popup tree then sat on /grant-access long enough for the dApp's
follow-up signing request to be treated as interrupting an active
signing route (interstitial) and clobbered by the deferred close.

Run the refresh fire-and-forget instead: the thunk resolves right after
the grant, and redux still updates when the reload lands, re-rendering
the next view with the fresh allowList. Sentry reporting of reload
failures is preserved.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.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.

3 participants