mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-27 21:08:00 +00:00
fix(oauth): extend loopback listener lifetime 60s -> 300s (#3145)
Co-authored-by: Cyrus Gray <cyrus@tinyhumans.ai>
This commit is contained in:
@@ -25,8 +25,11 @@ interface OAuthProviderButtonProps {
|
||||
|
||||
// Reset the loading state if the OAuth round-trip never completes — covers
|
||||
// the case where the user cancels in the system browser, or the backend
|
||||
// redirect fails so the `openhuman://` deep link never fires.
|
||||
const OAUTH_LOADING_TIMEOUT_MS = 90_000;
|
||||
// redirect fails so the `openhuman://` deep link never fires. Kept >= the
|
||||
// loopback listener lifetime (`DEFAULT_TIMEOUT_SECS`, 300s) so the button
|
||||
// never re-enables while the loopback server is still legitimately waiting
|
||||
// for a slow (2FA / consent) sign-in to redirect back.
|
||||
const OAUTH_LOADING_TIMEOUT_MS = 300_000;
|
||||
|
||||
// Pre-flight budget for `/health` before we open the system browser. Kept
|
||||
// short so a healthy backend adds barely any perceptible click→browser delay,
|
||||
|
||||
@@ -166,7 +166,7 @@ describe('OAuthProviderButton', () => {
|
||||
expect(screen.getByRole('button', { name: 'Google' })).toBeEnabled();
|
||||
});
|
||||
|
||||
it('resets isLoading after the 90s safety timeout', async () => {
|
||||
it('resets isLoading after the 300s safety timeout', async () => {
|
||||
render(<OAuthProviderButton provider={stubProvider} />);
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Google' }));
|
||||
@@ -178,9 +178,16 @@ describe('OAuthProviderButton', () => {
|
||||
|
||||
expect(screen.getByText('Connecting...')).toBeInTheDocument();
|
||||
|
||||
// Loading must persist past the old 90s mark — the loopback listener now
|
||||
// legitimately waits up to 300s for a slow (2FA / consent) sign-in.
|
||||
await act(async () => {
|
||||
vi.advanceTimersByTime(90_000);
|
||||
});
|
||||
expect(screen.getByText('Connecting...')).toBeInTheDocument();
|
||||
|
||||
await act(async () => {
|
||||
vi.advanceTimersByTime(210_000);
|
||||
});
|
||||
|
||||
expect(screen.queryByText('Connecting...')).not.toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: 'Google' })).toBeEnabled();
|
||||
@@ -367,7 +374,7 @@ describe('OAuthProviderButton', () => {
|
||||
expect(screen.queryByRole('alert')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows the unavailable banner if the 90s timeout fires while the backend is down', async () => {
|
||||
it('shows the unavailable banner if the 300s timeout fires while the backend is down', async () => {
|
||||
vi.mocked(checkBackendHealthy)
|
||||
.mockResolvedValueOnce(healthyResult)
|
||||
.mockResolvedValueOnce({ healthy: false, reason: 'timeout', latencyMs: 6000 });
|
||||
@@ -380,7 +387,7 @@ describe('OAuthProviderButton', () => {
|
||||
expect(openUrl).toHaveBeenCalledTimes(1);
|
||||
|
||||
await act(async () => {
|
||||
vi.advanceTimersByTime(90_000);
|
||||
vi.advanceTimersByTime(300_000);
|
||||
// After the safety timer fires we kick off probeBackendOnReturn().
|
||||
// Drain enough microtasks for that async probe to resolve and its
|
||||
// .then() to flush the alert into the DOM.
|
||||
|
||||
@@ -28,7 +28,7 @@ describe('startLoopbackOauthListener', () => {
|
||||
expect(handle).toBeNull();
|
||||
expect(mockInvoke).toHaveBeenCalledWith('start_loopback_oauth_listener', {
|
||||
port: 53824,
|
||||
timeoutSecs: 60,
|
||||
timeoutSecs: 300,
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -20,7 +20,14 @@ import { isTauri } from './tauriCommands/common';
|
||||
*/
|
||||
|
||||
const DEFAULT_PORT = 53824;
|
||||
const DEFAULT_TIMEOUT_SECS = 60;
|
||||
// The listener lifetime starts at the button click — before the system browser
|
||||
// even opens — and the Rust shell hard-kills the loopback server when it
|
||||
// elapses (`loopback_oauth.rs` `timeout(lifetime, run)`). A real GitHub/Google
|
||||
// sign-in (account chooser, password manager, 2FA/OTP, first-time consent)
|
||||
// routinely exceeds 60s; if the server dies first, the post-auth redirect lands
|
||||
// on a dead port and the browser shows "127.0.0.1:<port> not reached" even
|
||||
// though OAuth succeeded. 5 minutes comfortably covers an interactive sign-in.
|
||||
const DEFAULT_TIMEOUT_SECS = 300;
|
||||
const CALLBACK_EVENT = 'loopback-oauth-callback';
|
||||
|
||||
export interface LoopbackHandle {
|
||||
|
||||
Reference in New Issue
Block a user