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:
@@ -1,7 +1,7 @@
|
||||
import { clearSessionToken, getSessionToken } from '@/background/backend-client';
|
||||
import { clearSessionToken, getSessionToken, getStoredSession } from '@/background/backend-client';
|
||||
import { getConnectionStatus, setConnectionStatus } from '@/background/connection-status';
|
||||
import { applyLockState, getLockState } from '@/background/lock-state';
|
||||
import { handleWalletConnected, requestWalletReconnect } from '@/background/wallet-auth';
|
||||
import { handleWalletConnected, requestWalletDisconnect, requestWalletReconnect } from '@/background/wallet-auth';
|
||||
import { connectWsClient } from '@/background/ws-client';
|
||||
import type { NexaMessage } from '@/shared/messaging';
|
||||
|
||||
@@ -15,6 +15,12 @@ export default defineBackground(() => {
|
||||
},
|
||||
});
|
||||
|
||||
async function signOut(): Promise<void> {
|
||||
await clearSessionToken();
|
||||
ws.disconnect();
|
||||
await requestWalletDisconnect();
|
||||
}
|
||||
|
||||
browser.runtime.onMessage.addListener((message: NexaMessage, sender, sendResponse) => {
|
||||
switch (message?.type) {
|
||||
case 'nexa:get-lock-state':
|
||||
@@ -33,17 +39,28 @@ export default defineBackground(() => {
|
||||
|
||||
case 'nexa:wallet-connected':
|
||||
// A content script reports this on every page load (it always tries
|
||||
// a silent onlyIfTrusted connect first) — but re-authenticating
|
||||
// means asking Phantom to sign a fresh nonce, which shows its own
|
||||
// approval popup every time, unlike a silent connect. Only pay that
|
||||
// cost when we don't already have a usable session.
|
||||
getSessionToken()
|
||||
.then(() => undefined) // already authenticated — nothing to do
|
||||
.catch(() =>
|
||||
handleWalletConnected(sender.tab?.id, message.walletAddress)
|
||||
.then(() => ws.reconnectNow())
|
||||
.catch((err) => console.debug('[nexa/background] wallet auth failed:', err)), // ws-client's own retry loop keeps trying regardless
|
||||
);
|
||||
// a silent onlyIfTrusted connect first), AND whenever the user
|
||||
// switches accounts in Phantom's own UI. Only re-authenticate (which
|
||||
// means asking Phantom to sign a fresh nonce — a popup every time,
|
||||
// unlike a silent connect) when this isn't the wallet we're already
|
||||
// signed in as; a bare "do we have a token" check can't tell those
|
||||
// apart from an account switch.
|
||||
getStoredSession()
|
||||
.then((session) => {
|
||||
if (session?.walletAddress === message.walletAddress) return;
|
||||
return handleWalletConnected(sender.tab?.id, message.walletAddress).then(() => ws.reconnectNow());
|
||||
})
|
||||
.catch((err) => console.debug('[nexa/background] wallet auth failed:', err)); // ws-client's own retry loop keeps trying regardless
|
||||
return false;
|
||||
|
||||
case 'nexa:wallet-disconnected':
|
||||
// Disconnected directly in Phantom's UI (not via our own sign-out
|
||||
// flow, which already clears/disconnects itself) — treat the same way.
|
||||
void clearSessionToken().then(() => ws.disconnect());
|
||||
return false;
|
||||
|
||||
case 'nexa:sign-out':
|
||||
void signOut();
|
||||
return false;
|
||||
|
||||
default:
|
||||
|
||||
@@ -34,6 +34,10 @@ export function App() {
|
||||
return () => browser.runtime.onMessage.removeListener(listener);
|
||||
}, []);
|
||||
|
||||
function signOut(): void {
|
||||
void browser.runtime.sendMessage({ type: 'nexa:sign-out' } satisfies NexaMessage);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<header>
|
||||
@@ -59,6 +63,12 @@ export function App() {
|
||||
)}
|
||||
</section>
|
||||
|
||||
{connectionStatus === 'connected' && (
|
||||
<button type="button" className="sign-out-button" onClick={signOut}>
|
||||
Sign out
|
||||
</button>
|
||||
)}
|
||||
|
||||
<a
|
||||
className="thresholds-link"
|
||||
href="#"
|
||||
|
||||
@@ -79,6 +79,24 @@ header .subtitle {
|
||||
opacity: 0.65;
|
||||
}
|
||||
|
||||
.sign-out-button {
|
||||
display: block;
|
||||
width: 100%;
|
||||
margin-top: 4px;
|
||||
padding: 6px 10px;
|
||||
border: 1px solid rgba(128, 128, 128, 0.4);
|
||||
border-radius: 6px;
|
||||
background: transparent;
|
||||
color: inherit;
|
||||
font: inherit;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.sign-out-button:hover {
|
||||
background: rgba(128, 128, 128, 0.12);
|
||||
}
|
||||
|
||||
.thresholds-link {
|
||||
display: block;
|
||||
margin-top: 14px;
|
||||
|
||||
Reference in New Issue
Block a user