Support Phantom account switching and add sign-out

Wires Phantom's own accountChanged/disconnect provider events
(inject.ts) so switching accounts or disconnecting directly in
Phantom's UI is detected, not just our own connect/sign calls --
relayed as unsolicited postMessage events (relay.ts's onWalletEvent)
since they aren't a response to any request we made.

Adds a "Sign out" button in the popup (nexa:sign-out) that clears the
stored session, force-closes the WS connection via a new
ws-client.ts disconnect() (distinct from reconnectNow() -- it also
suppresses auto-reconnect until a new wallet connects), and asks the
content script to call provider.disconnect(), which revokes
Phantom's trust for the origin so the next silent connect correctly
fails until the user reconnects.

Fixes a real bug this surfaced: the existing "skip re-auth if a
session token exists" check in background.ts only checked for *any*
token, so switching Phantom accounts would have silently kept
authenticating as the old wallet. Session storage now tracks which
wallet it belongs to (backend-client.ts's storeSession(token,
walletAddress)) so the handler can tell "already signed in" apart
from "signed in as a different wallet than the one that just
connected."

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01YXiHuScXrjxBh7yFGAPq3B
This commit is contained in:
2026-09-07 13:48:05 +02:00
co-authored by claude
parent 9f06a83b64
commit b93a9c6f10
11 changed files with 206 additions and 47 deletions
+32 -1
View File
@@ -1,6 +1,6 @@
import { browser } from 'wxt/browser';
import type { NexaMessage } from '@/shared/messaging';
import { callWallet } from './wallet-bridge/relay';
import { callWallet, onWalletEvent } from './wallet-bridge/relay';
import { hideConnectBanner, setConnectBannerError, showConnectBanner } from './wallet-bridge/banner';
interface ConnectResult {
@@ -19,6 +19,15 @@ async function reportConnected(walletAddress: string): Promise<void> {
.catch((err) => console.debug('[nexa/wallet-connect] failed to notify background:', err));
}
/** Wallet disconnected or switched to no account — clears the backend session (tied to the old wallet) and re-shows the connect prompt. */
async function reportDisconnected(): Promise<void> {
console.debug('[nexa/wallet-connect] disconnected');
await browser.runtime
.sendMessage({ type: 'nexa:wallet-disconnected' } satisfies NexaMessage)
.catch((err) => console.debug('[nexa/wallet-connect] failed to notify background:', err));
showConnectBanner(() => void connectWithGesture());
}
/** Real user gesture (banner button click) — required for Phantom to show its connect approval popup on a first-ever connect. */
async function connectWithGesture(): Promise<void> {
console.debug('[nexa/wallet-connect] banner clicked, calling connect()');
@@ -52,6 +61,18 @@ export function initWalletConnect(): void {
console.debug('[nexa/wallet-connect] init on', window.location.href);
void attemptSilentConnect();
// Phantom's own account-switch/disconnect events — not initiated by us, so
// this catches the user changing accounts (or disconnecting) directly in
// Phantom's UI, not just our own sign-out flow below.
onWalletEvent((name, walletAddress) => {
console.debug('[nexa/wallet-connect] wallet event', name, walletAddress);
if (walletAddress) {
void reportConnected(walletAddress); // switched to a different account — re-auth as it
} else {
void reportDisconnected(); // accountChanged(null) or a 'disconnect' event
}
});
browser.runtime.onMessage.addListener((message: NexaMessage, _sender, sendResponse) => {
if (message?.type === 'nexa:wallet-sign-request') {
callWallet<SignResult>('signMessage', { message: message.nonce })
@@ -65,6 +86,16 @@ export function initWalletConnect(): void {
return false;
}
if (message?.type === 'nexa:wallet-disconnect-request') {
// Sign-out, initiated from the popup (see background/wallet-auth.ts).
// Phantom's disconnect() revokes this origin's trust, so the next
// onlyIfTrusted attempt correctly fails until the user connects again.
callWallet('disconnect', {})
.catch((err) => console.debug('[nexa/wallet-connect] disconnect() failed:', err))
.finally(() => void reportDisconnected());
return false;
}
return undefined;
});
}