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.
This commit is contained in:
2025-08-13 11:15:54 +12:00
parent e0cdc9f0bb
commit da7f871d69
3 changed files with 76 additions and 30 deletions
+38 -19
View File
@@ -82,29 +82,33 @@ function createWindow(startUrl) {
const win = new BrowserWindow(windowOptions); 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.webContents.setWindowOpenHandler(({ url }) => {
win.loadURL(url); if (url.startsWith('http://') || url.startsWith('https://')) {
return { action: 'allow' };
}
return { action: 'deny' }; return { action: 'deny' };
}); });
// Intercept direct navigations (e.g., user clicks a link) load URL in this window // IMPORTANT: Do NOT intercept 'will-navigate' with preventDefault() because
win.webContents.on('will-navigate', (event, url) => { // that strips POST bodies (turning logins into GET requests). Let Chromium
event.preventDefault(); // Prevent navigation in the current window // perform the navigation normally. If you need to observe navigations, add
win.loadURL(url); // a listener without calling preventDefault().
}); // (Previous code here was causing login forms to fail.)
// Intercept legacy new-window events load URL in this window // Remove deprecated 'new-window' handler that forcibly loaded targets in the
win.webContents.on('new-window', (event, url) => { // same window; this also broke some auth popup flows. setWindowOpenHandler
event.preventDefault(); // Prevent new Electron window // above now governs popup behavior.
win.loadURL(url);
});
// ensure all embedded <webview> tags behave predictably without heavy injections // ensure all embedded <webview> tags behave predictably without heavy injections
win.webContents.on('did-attach-webview', (event, webContents) => { win.webContents.on('did-attach-webview', (event, webContents) => {
// Let the renderer/webview handle navigation; avoid extra JS injection that can stall // Allow popups inside <webview> as well (required for some login flows)
webContents.setWindowOpenHandler(({ url }) => { webContents.setWindowOpenHandler(({ url }) => {
webContents.loadURL(url); if (url.startsWith('http://') || url.startsWith('https://')) {
return { action: 'allow' };
}
return { action: 'deny' }; return { action: 'deny' };
}); });
}); });
@@ -162,8 +166,17 @@ app.whenReady().then(async () => {
} }
}); });
// Configure user agent for better compatibility // 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'); // 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 // Configure cookies for OAuth compatibility
ses.cookies.on('changed', (event, cookie, cause, removed) => { 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 // Optional: add headers only for OAuth flows; avoid forcing cache headers globally
ses.webRequest.onBeforeSendHeaders((details, callback) => { 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')) { if (details.url.includes('accounts.google.com') || details.url.includes('oauth')) {
details.requestHeaders['Referrer-Policy'] = 'strict-origin-when-cross-origin'; headers['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['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'); console.log('Session configured successfully for OAuth compatibility');
+37 -5
View File
@@ -170,7 +170,12 @@ function createTab(inputUrl) {
webview.setAttribute('preload', '../preload.js'); webview.setAttribute('preload', '../preload.js');
// Add attributes needed for Google OAuth and sign-in flows // Add attributes needed for Google OAuth and sign-in flows
webview.setAttribute('webpreferences', 'allowRunningInsecureContent=false,javascript=true,webSecurity=true'); 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 => { webview.addEventListener('page-favicon-updated', e => {
if (e.favicons.length > 0) updateTabMetadata(id, 'favicon', e.favicons[0]); if (e.favicons.length > 0) updateTabMetadata(id, 'favicon', e.favicons[0]);
@@ -194,6 +199,9 @@ function createTab(inputUrl) {
webview.addEventListener('did-navigate', e => { webview.addEventListener('did-navigate', e => {
handleNavigation(id, e.url); handleNavigation(id, e.url);
if (e.url.startsWith('http')) debug('[DEBUG] Recording navigation to:', 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 => { 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 // catch any target="_blank" or window.open() calls and open them as new tabs
webview.addEventListener('new-window', e => { webview.addEventListener('new-window', e => {
e.preventDefault(); // Allow auth / SSO popup windows (don't preventDefault) when target is http(s)
createTab(e.url); // 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: // After creating dynamic webview:
@@ -332,7 +350,10 @@ function convertHomeTabToWebview(tabId, inputUrl, resolvedUrl) {
webview.setAttribute('preload', '../preload.js'); webview.setAttribute('preload', '../preload.js');
// Add attributes needed for Google OAuth and sign-in flows // Add attributes needed for Google OAuth and sign-in flows
webview.setAttribute('webpreferences', 'allowRunningInsecureContent=false,javascript=true,webSecurity=true'); 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 // Add event listeners
webview.addEventListener('did-fail-load', handleLoadFail(tabId)); webview.addEventListener('did-fail-load', handleLoadFail(tabId));
@@ -343,6 +364,9 @@ function convertHomeTabToWebview(tabId, inputUrl, resolvedUrl) {
webview.addEventListener('did-navigate', e => { webview.addEventListener('did-navigate', e => {
handleNavigation(tabId, e.url); 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 => { webview.addEventListener('did-navigate-in-page', e => {
handleNavigation(tabId, e.url); handleNavigation(tabId, e.url);
@@ -352,7 +376,15 @@ function convertHomeTabToWebview(tabId, inputUrl, resolvedUrl) {
}); });
webview.addEventListener('new-window', e => { 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: // After creating dynamic webview:
+1 -6
View File
@@ -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/"
] ]