fix(web): lock the OAuth clear so a double click cannot run it twice (#2144)
Copilot review round 11. The web counterpart of the TUI lock added in round 9. Neither clear control was guarded while the operation was pending, and revocation can take up to five seconds — so a double click started concurrent RFC 7009 requests, concurrent store writes and two contradictory toasts against the same client. Both controls now share one ref-backed in-flight lock in `runClear`, since they drive the same client and the same store entry. A ref rather than state, for the reason the TUI one is: a double click delivers both events before React re-renders, so a state-based guard lets both through. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0177mPHAdECD18nLLCTwR5rh Signed-off-by: cliffhall <cliff@futurescale.com>
This commit is contained in:
parent
1661f09d0a
commit
e2cbf0de6d
1 changed files with 26 additions and 9 deletions
|
|
@ -1320,6 +1320,11 @@ function App() {
|
|||
// target isn't resolvable.
|
||||
const settingsModalIsStdio = settingsModalServerType === "stdio";
|
||||
|
||||
/**
|
||||
* In-flight lock shared by both clear controls (#2144). See `runClear`.
|
||||
*/
|
||||
const clearOAuthInFlightRef = useRef(false);
|
||||
|
||||
/**
|
||||
* Run a clear from a key/click handler, which cannot await.
|
||||
*
|
||||
|
|
@ -1331,16 +1336,28 @@ function App() {
|
|||
*/
|
||||
const runClear = useCallback(
|
||||
(server: Parameters<typeof clearServerOAuthAndDisconnect>[0]) => {
|
||||
clearServerOAuthAndDisconnect(server).catch((err: unknown) => {
|
||||
notifications.show({
|
||||
title: "Could not clear the stored OAuth state",
|
||||
message: err instanceof Error ? err.message : String(err),
|
||||
color: "red",
|
||||
// The tokens may still be on disk and the session may still be up,
|
||||
// so this is not a notice to let time out.
|
||||
autoClose: false,
|
||||
// Shared by BOTH clear controls (Connection Info and Server Settings),
|
||||
// because they drive the same client and the same store entry. A ref
|
||||
// rather than state: a double click delivers both events before React
|
||||
// re-renders, so a state-based guard would let both through — and with
|
||||
// revocation taking up to five seconds, that means concurrent RFC 7009
|
||||
// requests, concurrent store writes, and two contradictory toasts.
|
||||
if (clearOAuthInFlightRef.current) return;
|
||||
clearOAuthInFlightRef.current = true;
|
||||
clearServerOAuthAndDisconnect(server)
|
||||
.finally(() => {
|
||||
clearOAuthInFlightRef.current = false;
|
||||
})
|
||||
.catch((err: unknown) => {
|
||||
notifications.show({
|
||||
title: "Could not clear the stored OAuth state",
|
||||
message: err instanceof Error ? err.message : String(err),
|
||||
color: "red",
|
||||
// The tokens may still be on disk and the session may still be up,
|
||||
// so this is not a notice to let time out.
|
||||
autoClose: false,
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
[clearServerOAuthAndDisconnect],
|
||||
);
|
||||
|
|
|
|||
Loading…
Reference in a new issue