From 50f295f83392925ddfff7abf04f051f8a08e8e45 Mon Sep 17 00:00:00 2001 From: pjh4993 Date: Mon, 8 Jun 2026 00:43:27 +0900 Subject: [PATCH 1/2] fix(webview): support OAuth/SSO auth flows in web blocks Two related fixes so login flows can complete inside Wave web blocks instead of dead-ending with ERR_BLOCKED_BY_RESPONSE: 1. Don't treat sub-frame load failures as fatal (webview.tsx). Web apps load hidden iframes that are expected to fail - notably OAuth/OIDC silent-auth probes (response_mode=web_message, prompt=none), which the embedding page's Cross-Origin-Embedder-Policy blocks. A normal browser surfaces these only to the page's auth SDK (which then falls back to interactive login); Wave painted a fatal error overlay over the whole block on ANY did-fail-load, killing the flow before the fallback could run. Only show the overlay for main-frame failures. 2. Allow genuine popups for window.open with features (emain-tabview.ts). Popup-based SSO (e.g. GitHub/Microsoft) calls window.open(url, name, 'width=...') which Electron reports as disposition 'new-window'; these need a real popup window that preserves window.opener and the COOP browsing context. Rerouting them into a new web block severs both. Allow a real popup for that disposition while keeping ordinary link clicks routed to web blocks. Verified end-to-end with the DuckDB local UI MotherDuck (Auth0) sign-in. Refs wavetermdev/waveterm#2764 Co-Authored-By: Claude Opus 4.8 --- emain/emain-tabview.ts | 15 +++++++++++++++ frontend/app/view/webview/webview.tsx | 8 ++++++++ 2 files changed, 23 insertions(+) diff --git a/emain/emain-tabview.ts b/emain/emain-tabview.ts index 753a53adec..8b223b1594 100644 --- a/emain/emain-tabview.ts +++ b/emain/emain-tabview.ts @@ -319,9 +319,24 @@ export async function getOrCreateWebViewForTab(waveWindowId: string, tabId: stri if (wc == null || wc.isDestroyed() || tabView.webContents == null || tabView.webContents.isDestroyed()) { return { action: "deny" }; } + // OAuth/SSO flows call window.open(url, name, "width=...,height=...") which Electron + // reports as disposition "new-window". They require a real popup that preserves the + // window.opener relationship (postMessage token return) and a valid Cross-Origin- + // Opener-Policy browsing context. Rerouting them into a new web block breaks both + // (results in ERR_BLOCKED_BY_RESPONSE). Allow a genuine popup window for these; it + // shares wc's session so any auth cookies/tokens remain visible to the webview. + if (details.disposition === "new-window") { + return { + action: "allow", + overrideBrowserWindowOptions: { width: 600, height: 700, autoHideMenuBar: true }, + }; + } + // Plain link clicks (target=_blank, foreground/background tab) keep existing behavior: + // open in a new Wave web block. tabView.webContents.send("webview-new-window", wc.id, details); return { action: "deny" }; }); + wc.on("did-create-window", (popup) => popup.setMenuBarVisibility(false)); }); tabView.webContents.on("before-input-event", (e, input) => { const waveEvent = adaptFromElectronKeyEvent(input); diff --git a/frontend/app/view/webview/webview.tsx b/frontend/app/view/webview/webview.tsx index f6d98b8f22..554c935827 100644 --- a/frontend/app/view/webview/webview.tsx +++ b/frontend/app/view/webview/webview.tsx @@ -1044,6 +1044,14 @@ const WebView = memo(({ model, onFailLoad, blockRef, initialSrc }: WebViewProps) const failLoadHandler = (e: any) => { if (e.errorCode === -3) { console.warn("Suppressed ERR_ABORTED error", e); + } else if (e.isMainFrame === false) { + // Sub-frame load failures are non-fatal. Web apps routinely load hidden iframes + // that are expected to fail — e.g. OAuth/OIDC silent-auth probes + // (response_mode=web_message, prompt=none), which get blocked by the embedder's + // Cross-Origin-Embedder-Policy and are then handled by the app's auth SDK (it + // falls back to interactive login). A normal browser surfaces these only to the + // SDK, not the user, so don't paint a fatal error over the whole block. + console.warn("Suppressed sub-frame load failure", e.validatedURL, e.errorDescription); } else { const errorMessage = `Failed to load ${e.validatedURL}: ${e.errorDescription}`; console.error(errorMessage); From f4b032a2b0a51ccce8242651be7603aba0dc9bc9 Mon Sep 17 00:00:00 2001 From: pjh4993 Date: Mon, 8 Jun 2026 02:00:52 +0900 Subject: [PATCH 2/2] fix(webview): gate real popups on window features, not disposition alone Electron's setWindowOpenHandler reports disposition "new-window" not only for scripted window.open(...) popups but also for shift+clicked links. Keying the allow case on disposition alone meant a shift-clicked link in a web block opened an unmanaged BrowserWindow instead of a Wave web block. OAuth/SSO popups always pass a window-features string (width/height); plain links carry none, so also require a non-empty details.features to allow a real popup. Co-Authored-By: Claude Opus 4.8 --- emain/emain-tabview.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/emain/emain-tabview.ts b/emain/emain-tabview.ts index 8b223b1594..b4746483e0 100644 --- a/emain/emain-tabview.ts +++ b/emain/emain-tabview.ts @@ -325,7 +325,9 @@ export async function getOrCreateWebViewForTab(waveWindowId: string, tabId: stri // Opener-Policy browsing context. Rerouting them into a new web block breaks both // (results in ERR_BLOCKED_BY_RESPONSE). Allow a genuine popup window for these; it // shares wc's session so any auth cookies/tokens remain visible to the webview. - if (details.disposition === "new-window") { + // Gate on a non-empty features string: a shift+click on a plain link also reports + // disposition "new-window" but carries no features, and must keep routing to a web block. + if (details.disposition === "new-window" && details.features !== "") { return { action: "allow", overrideBrowserWindowOptions: { width: 600, height: 700, autoHideMenuBar: true },