From da7f871d69d503a23bdbe8b99efe78828c68d464 Mon Sep 17 00:00:00 2001 From: Andrew Date: Wed, 13 Aug 2025 11:15:54 +1200 Subject: [PATCH] Improve popup, navigation, and user agent handling Refactored window.open and new-window logic to better support OAuth/SSO popups and preserve POST bodies, removing deprecated event handlers that broke login flows. Updated user agent handling to append Nebula branding to the real Chromium UA for improved compatibility with anti-bot systems. Enhanced webview event handling for Cloudflare challenge detection and refined tab/popup heuristics. Cleaned up site-history.json. --- main.js | 57 ++++++++++++++++++++++++++++++---------------- renderer/script.js | 42 ++++++++++++++++++++++++++++++---- site-history.json | 7 +----- 3 files changed, 76 insertions(+), 30 deletions(-) diff --git a/main.js b/main.js index b5de314..3102000 100644 --- a/main.js +++ b/main.js @@ -82,29 +82,33 @@ function createWindow(startUrl) { const win = new BrowserWindow(windowOptions); - // Handle window.open() calls – load URL in this window + // Allow window.open() popups (e.g. OAuth / SSO / school portals) so that + // POST form submissions and opener relationships are preserved. + // We still restrict to http/https for safety; everything else is denied. win.webContents.setWindowOpenHandler(({ url }) => { - win.loadURL(url); + if (url.startsWith('http://') || url.startsWith('https://')) { + return { action: 'allow' }; + } return { action: 'deny' }; }); - // Intercept direct navigations (e.g., user clicks a link) – load URL in this window - win.webContents.on('will-navigate', (event, url) => { - event.preventDefault(); // Prevent navigation in the current window - win.loadURL(url); - }); + // IMPORTANT: Do NOT intercept 'will-navigate' with preventDefault() because + // that strips POST bodies (turning logins into GET requests). Let Chromium + // perform the navigation normally. If you need to observe navigations, add + // a listener without calling preventDefault(). + // (Previous code here was causing login forms to fail.) - // Intercept legacy new-window events – load URL in this window - win.webContents.on('new-window', (event, url) => { - event.preventDefault(); // Prevent new Electron window - win.loadURL(url); - }); + // Remove deprecated 'new-window' handler that forcibly loaded targets in the + // same window; this also broke some auth popup flows. setWindowOpenHandler + // above now governs popup behavior. // ensure all embedded tags behave predictably without heavy injections win.webContents.on('did-attach-webview', (event, webContents) => { - // Let the renderer/webview handle navigation; avoid extra JS injection that can stall + // Allow popups inside as well (required for some login flows) webContents.setWindowOpenHandler(({ url }) => { - webContents.loadURL(url); + if (url.startsWith('http://') || url.startsWith('https://')) { + return { action: 'allow' }; + } return { action: 'deny' }; }); }); @@ -162,8 +166,17 @@ app.whenReady().then(async () => { } }); - // Configure user agent for better compatibility - ses.setUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 Nebula/1.0.0'); + // Configure user agent for better compatibility: + // Previously this forced Chrome/120 which is stale and can cause anti-bot / Cloudflare challenges + // to fail due to UA / feature mismatch fingerprinting. Use the real Chromium UA then append branding. + try { + const realUA = ses.getUserAgent(); + if (realUA && !realUA.includes('Nebula/')) { + ses.setUserAgent(realUA + ' Nebula/1.0.0'); + } + } catch (e) { + console.warn('Failed to read real user agent, keeping default:', e); + } // Configure cookies for OAuth compatibility ses.cookies.on('changed', (event, cookie, cause, removed) => { @@ -175,11 +188,17 @@ app.whenReady().then(async () => { // Optional: add headers only for OAuth flows; avoid forcing cache headers globally ses.webRequest.onBeforeSendHeaders((details, callback) => { + const headers = details.requestHeaders; + // Add richer headers for sensitive flows (OAuth / login) to look like a real browser if (details.url.includes('accounts.google.com') || details.url.includes('oauth')) { - details.requestHeaders['Referrer-Policy'] = 'strict-origin-when-cross-origin'; - details.requestHeaders['Accept'] = 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8'; + headers['Referrer-Policy'] = 'strict-origin-when-cross-origin'; + headers['Accept'] = headers['Accept'] || 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8'; } - callback({ requestHeaders: details.requestHeaders }); + // Ensure Accept-Language is present (Cloudflare & some WAFs use this in heuristics) + if (!headers['Accept-Language'] && !headers['accept-language']) { + headers['Accept-Language'] = 'en-US,en;q=0.9'; + } + callback({ requestHeaders: headers }); }); } console.log('Session configured successfully for OAuth compatibility'); diff --git a/renderer/script.js b/renderer/script.js index 92fbe76..7c4dec6 100644 --- a/renderer/script.js +++ b/renderer/script.js @@ -170,7 +170,12 @@ function createTab(inputUrl) { webview.setAttribute('preload', '../preload.js'); // Add attributes needed for Google OAuth and sign-in flows webview.setAttribute('webpreferences', 'allowRunningInsecureContent=false,javascript=true,webSecurity=true'); - webview.setAttribute('useragent', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 Nebula/1.0.0'); + try { + const baseUA = navigator.userAgent.includes('Nebula/') ? navigator.userAgent : navigator.userAgent + ' Nebula/1.0.0'; + webview.setAttribute('useragent', baseUA); + } catch { + // fallback: let Electron supply default UA + } webview.addEventListener('page-favicon-updated', e => { if (e.favicons.length > 0) updateTabMetadata(id, 'favicon', e.favicons[0]); @@ -194,6 +199,9 @@ function createTab(inputUrl) { webview.addEventListener('did-navigate', e => { handleNavigation(id, e.url); if (e.url.startsWith('http')) debug('[DEBUG] Recording navigation to:', e.url); + if (/\/cdn-cgi\//.test(e.url) || /challenge/i.test(e.url)) { + console.log('[Nebula] Cloudflare challenge detected at', e.url); + } }); webview.addEventListener('did-navigate-in-page', e => { @@ -208,8 +216,18 @@ function createTab(inputUrl) { // catch any target="_blank" or window.open() calls and open them as new tabs webview.addEventListener('new-window', e => { - e.preventDefault(); - createTab(e.url); + // Allow auth / SSO popup windows (don't preventDefault) when target is http(s) + // so form POST + redirect chains stay intact. For simple links attempting to + // open a new tab, we create an in-app tab instead. Heuristic: if disposition + // is 'foreground-tab' or 'background-tab', treat as tab; otherwise allow popup. + if (e.url && (e.url.startsWith('http://') || e.url.startsWith('https://'))) { + if (e.disposition && e.disposition.includes('tab')) { + e.preventDefault(); + createTab(e.url); + } // else let Electron create a real popup window + } else { + e.preventDefault(); + } }); // After creating dynamic webview: @@ -332,7 +350,10 @@ function convertHomeTabToWebview(tabId, inputUrl, resolvedUrl) { webview.setAttribute('preload', '../preload.js'); // Add attributes needed for Google OAuth and sign-in flows webview.setAttribute('webpreferences', 'allowRunningInsecureContent=false,javascript=true,webSecurity=true'); - webview.setAttribute('useragent', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 Nebula/1.0.0'); + try { + const baseUA2 = navigator.userAgent.includes('Nebula/') ? navigator.userAgent : navigator.userAgent + ' Nebula/1.0.0'; + webview.setAttribute('useragent', baseUA2); + } catch {} // Add event listeners webview.addEventListener('did-fail-load', handleLoadFail(tabId)); @@ -343,6 +364,9 @@ function convertHomeTabToWebview(tabId, inputUrl, resolvedUrl) { webview.addEventListener('did-navigate', e => { handleNavigation(tabId, e.url); + if (/\/cdn-cgi\//.test(e.url) || /challenge/i.test(e.url)) { + console.log('[Nebula] Cloudflare challenge detected at', e.url); + } }); webview.addEventListener('did-navigate-in-page', e => { handleNavigation(tabId, e.url); @@ -352,7 +376,15 @@ function convertHomeTabToWebview(tabId, inputUrl, resolvedUrl) { }); webview.addEventListener('new-window', e => { - createTab(e.url); + if (e.url && (e.url.startsWith('http://') || e.url.startsWith('https://'))) { + if (e.disposition && e.disposition.includes('tab')) { + e.preventDefault(); + createTab(e.url); + } + // otherwise allow popup for auth + } else { + e.preventDefault(); + } }); // After creating dynamic webview: diff --git a/site-history.json b/site-history.json index 46ab1e1..c44dc44 100644 --- a/site-history.json +++ b/site-history.json @@ -1,8 +1,3 @@ [ - "https://nebula.zambazosmedia.group/roadmap.html", - "https://nebula.zambazosmedia.group/index.html", - "https://nebula.zambazosmedia.group/wiki/index.html", - "https://nebula.zambazosmedia.group/", - "https://github.com/Bobbybear007/NebulaBrowser", - "https://www.youtube.com/" + ] \ No newline at end of file