[client] Show the SSO login popup and open the browser from Go

The browser-login popup was created hidden and relied on its own webview
to size and show itself and to launch the external browser. On macOS a
hidden WKWebView gets throttled or suspended (App Nap / hidden-window
throttling), so on the first-use path nothing appeared and the browser
never opened, leaving the session-expiration dialog disabled until the
PKCE flow timed out. Reproduced by freezing the popup's WebContent
process: the old code showed nothing, the new code shows the popup and
opens the browser within 30 ms regardless of the webview state.

Show and focus the popup from Go right after creation and launch the
browser from Go on both the create and reuse paths. The popup's frontend
no longer shows or focuses itself, so the browser keeps the foreground
once it activates. This also fixes the reuse path, where a fragment-only
SetURL kept the mounted React tree and the once-only guard skipped
opening the browser for the new URI. Browser launch failures surface in
the error dialog instead of being swallowed.
This commit is contained in:
Zoltan Papp
2026-09-03 13:23:14 +02:00
parent 7c1253004b
commit 954c90f9e5
4 changed files with 45 additions and 27 deletions
@@ -5,7 +5,11 @@ import { isLinux } from "@/lib/platform";
// Sizes the current Wails window to the measured content height (keeping `width`),
// then shows it. Re-applies on content resize and language change.
export function useAutoSizeWindow<T extends HTMLElement>(width: number, ready: boolean = true) {
export function useAutoSizeWindow<T extends HTMLElement>(
width: number,
ready: boolean = true,
showWhenSized: boolean = true,
) {
const ref = useRef<T | null>(null);
useLayoutEffect(() => {
const el = ref.current;
@@ -33,7 +37,7 @@ export function useAutoSizeWindow<T extends HTMLElement>(width: number, ready: b
await Window.SetMaxSize(width, targetH);
}
await Window.SetSize(width, targetH);
showOnce();
if (showWhenSized) showOnce();
} catch {
// window gone / not ready — ignore
}
@@ -55,6 +59,6 @@ export function useAutoSizeWindow<T extends HTMLElement>(width: number, ready: b
cancelAnimationFrame(raf2);
i18next.off("languageChanged", scheduleApply);
};
}, [width, ready]);
}, [width, ready, showWhenSized]);
return ref;
}
@@ -1,4 +1,4 @@
import { useCallback, useEffect, useRef } from "react";
import { useCallback } from "react";
import { useTranslation } from "react-i18next";
import { useSearchParams } from "react-router-dom";
import { Events } from "@wailsio/runtime";
@@ -20,8 +20,7 @@ export default function LoginWaitingForBrowserDialog() {
const { t } = useTranslation();
const [params] = useSearchParams();
const uri = params.get("uri") ?? "";
const contentRef = useAutoSizeWindow<HTMLDivElement>(WINDOW_WIDTH);
const openedRef = useRef(false);
const contentRef = useAutoSizeWindow<HTMLDivElement>(WINDOW_WIDTH, true, false);
const reportOpenFailure = useCallback(
(e: unknown) => {
@@ -33,13 +32,6 @@ export default function LoginWaitingForBrowserDialog() {
[t],
);
// Open the browser only after mount, or it lands on top of the still-hidden popup.
useEffect(() => {
if (!uri || openedRef.current) return;
openedRef.current = true;
Connection.OpenURL(uri).catch(reportOpenFailure);
}, [uri, reportOpenFailure]);
const tryAgain = useCallback(() => {
if (!uri) return;
Connection.OpenURL(uri).catch(reportOpenFailure);
+17 -13
View File
@@ -205,19 +205,7 @@ func (s *Connection) Down(ctx context.Context) error {
// window.open, so the SSO verification page can't pop inline. Honors $BROWSER
// before the platform default.
func (s *Connection) OpenURL(url string) error {
if browser := os.Getenv("BROWSER"); browser != "" {
return exec.Command(browser, url).Start()
}
switch runtime.GOOS {
case "windows":
return exec.Command("rundll32", "url.dll,FileProtocolHandler", url).Start()
case "darwin":
return exec.Command("open", url).Start()
case "linux":
return exec.Command("xdg-open", url).Start()
default:
return fmt.Errorf("unsupported platform")
}
return openURL(url)
}
func (s *Connection) Logout(ctx context.Context, p LogoutParams) error {
@@ -288,3 +276,19 @@ func (s *Connection) waitSSOLogin(ctx context.Context, p WaitSSOParams) (string,
func (s *Connection) classifyDaemonError(err error) *ClientError {
return s.classifier.classify(err)
}
func openURL(url string) error {
if browser := os.Getenv("BROWSER"); browser != "" {
return exec.Command(browser, url).Start()
}
switch runtime.GOOS {
case "windows":
return exec.Command("rundll32", "url.dll,FileProtocolHandler", url).Start()
case "darwin":
return exec.Command("open", url).Start()
case "linux":
return exec.Command("xdg-open", url).Start()
default:
return fmt.Errorf("unsupported platform")
}
}
+19 -1
View File
@@ -245,7 +245,11 @@ func (s *WindowManager) OpenBrowserLogin(uri string) {
s.app.Event.Emit(EventBrowserLoginCancel)
}
})
s.centerOnCursorScreen(s.browserLogin)
s.centerOnCursorScreen(bl)
bl.Show()
bl.Focus()
log.Debugf("browser-login popup created and shown")
s.openBrowser(uri)
return
}
if uri != "" {
@@ -254,6 +258,20 @@ func (s *WindowManager) OpenBrowserLogin(uri string) {
s.centerOnCursorScreen(s.browserLogin)
s.browserLogin.Show()
s.browserLogin.Focus()
log.Debugf("browser-login popup reused")
s.openBrowser(uri)
}
func (s *WindowManager) openBrowser(uri string) {
if uri == "" {
return
}
go func() {
if err := openURL(uri); err != nil {
log.Errorf("open browser for SSO login: %v", err)
s.OpenError(s.title("browserLogin.openFailedTitle"), err.Error(), "")
}
}()
}
// BrowserLoginWindow returns the live SSO popup, or nil. While non-nil it is the