Add auto-update and display scale features

Integrates electron-updater for automatic app updates, including IPC and renderer APIs for update status and controls. Adds display scale (zoom) setting to settings UI, persists user preference in localStorage, and applies it on startup. Updates OAuth popup allowlist logic and upgrades Electron to v39.2.7.
This commit is contained in:
2025-12-27 22:23:14 +13:00
parent c864ca187c
commit 43ebed0ade
7 changed files with 320 additions and 29 deletions
+149 -15
View File
@@ -1,4 +1,5 @@
const { app, BrowserWindow, ipcMain, session, screen, shell, dialog, Menu, clipboard, webContents } = require('electron'); const { app, BrowserWindow, ipcMain, session, screen, shell, dialog, Menu, clipboard, webContents } = require('electron');
const { autoUpdater } = require('electron-updater');
const { pathToFileURL } = require('url'); const { pathToFileURL } = require('url');
const fs = require('fs'); const fs = require('fs');
const path = require('path'); const path = require('path');
@@ -140,14 +141,28 @@ function createWindow(startUrl) {
perfMarks.browserWindow_instantiated = performance.now(); perfMarks.browserWindow_instantiated = performance.now();
// Intercept window.open() requests and route them into the existing window as a new tab // Intercept window.open() requests and route them into the existing window as a new tab
// instead of spawning separate BrowserWindows. We still allow a small OAuth allowlist // instead of spawning separate BrowserWindows. We allow a small list of specific OAuth
// (accounts.google.com, login.microsoftonline.com, oauth, sso) to open real popups if // domains to open real popups if the flow depends on window.opener relationships.
// the flow depends on window.opener relationships. Everything else becomes a new tab. // Everything else becomes a new tab.
win.webContents.setWindowOpenHandler((details) => { win.webContents.setWindowOpenHandler((details) => {
const { url } = details; const { url } = details;
if (!/^https?:\/\//i.test(url)) return { action: 'deny' }; if (!/^https?:\/\//i.test(url)) return { action: 'deny' };
// OAuth / SSO allowlist heuristic // OAuth / SSO allowlist - only allow specific authentication provider domains
if (/accounts\.google\.com|microsoftonline\.com|oauth|login|signin|sso/i.test(url)) { // Be restrictive to prevent normal links from opening in new windows
const oauthDomains = [
'accounts.google.com',
'login.microsoftonline.com',
'appleid.apple.com',
'github.com/login',
'auth0.com',
'okta.com',
'login.live.com',
'facebook.com/dialog',
'api.twitter.com/oauth',
'discord.com/oauth2'
];
const isOAuthDomain = oauthDomains.some(domain => url.toLowerCase().includes(domain.toLowerCase()));
if (isOAuthDomain) {
return { action: 'allow' }; // preserve popup semantics for complex auth flows return { action: 'allow' }; // preserve popup semantics for complex auth flows
} }
// Forward to renderer to open as tab // Forward to renderer to open as tab
@@ -166,18 +181,31 @@ function createWindow(startUrl) {
// above now governs popup behavior. // above now governs popup behavior.
// 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, webviewContents) => {
// Route <webview> window.open() calls to tabs unless OAuth allowlist matched // Route <webview> window.open() calls to tabs unless OAuth allowlist matched
webContents.setWindowOpenHandler((details) => { webviewContents.setWindowOpenHandler((details) => {
const { url } = details; const { url } = details;
if (!/^https?:\/\//i.test(url)) return { action: 'deny' }; if (!/^https?:\/\//i.test(url)) return { action: 'deny' };
if (/accounts\.google\.com|microsoftonline\.com|oauth|login|signin|sso/i.test(url)) { // OAuth / SSO allowlist - only allow specific authentication provider domains
const oauthDomains = [
'accounts.google.com',
'login.microsoftonline.com',
'appleid.apple.com',
'github.com/login',
'auth0.com',
'okta.com',
'login.live.com',
'facebook.com/dialog',
'api.twitter.com/oauth',
'discord.com/oauth2'
];
const isOAuthDomain = oauthDomains.some(domain => url.toLowerCase().includes(domain.toLowerCase()));
if (isOAuthDomain) {
return { action: 'allow' }; // keep popup for auth return { action: 'allow' }; // keep popup for auth
} }
// Send to the owning window (embedder) to open a new tab // Send to main window's webContents to open a new tab
try { try {
const host = webContents.hostWebContents || webContents; win.webContents.send('open-url-new-tab', url);
host.send('open-url-new-tab', url);
} catch {} } catch {}
return { action: 'deny' }; return { action: 'deny' };
}); });
@@ -364,6 +392,61 @@ app.whenReady().then(() => {
app.on('activate', () => { app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) createWindow(); if (BrowserWindow.getAllWindows().length === 0) createWindow();
}); });
// --- Auto-Updater Setup ---
// Configure auto-updater logging
autoUpdater.logger = require('electron-updater').autoUpdater.logger;
if (autoUpdater.logger) autoUpdater.logger.transports.file.level = 'info';
// Check for updates after a short delay to not block startup
setTimeout(() => {
autoUpdater.checkForUpdatesAndNotify().catch(err => {
console.log('[AutoUpdater] Update check failed:', err.message);
});
}, 3000);
// Auto-updater event handlers
autoUpdater.on('checking-for-update', () => {
console.log('[AutoUpdater] Checking for updates...');
broadcastToAll('update-status', { status: 'checking' });
});
autoUpdater.on('update-available', (info) => {
console.log('[AutoUpdater] Update available:', info.version);
broadcastToAll('update-status', { status: 'available', version: info.version });
});
autoUpdater.on('update-not-available', (info) => {
console.log('[AutoUpdater] No update available. Current version:', app.getVersion());
broadcastToAll('update-status', { status: 'not-available', currentVersion: app.getVersion() });
});
autoUpdater.on('download-progress', (progress) => {
console.log(`[AutoUpdater] Download progress: ${progress.percent.toFixed(1)}%`);
broadcastToAll('update-status', { status: 'downloading', progress: progress.percent });
});
autoUpdater.on('update-downloaded', (info) => {
console.log('[AutoUpdater] Update downloaded:', info.version);
broadcastToAll('update-status', { status: 'downloaded', version: info.version });
// Optionally prompt user to restart
dialog.showMessageBox({
type: 'info',
title: 'Update Ready',
message: `Nebula ${info.version} has been downloaded.`,
detail: 'The update will be installed when you restart the app.',
buttons: ['Restart Now', 'Later']
}).then(result => {
if (result.response === 0) {
autoUpdater.quitAndInstall();
}
});
});
autoUpdater.on('error', (err) => {
console.error('[AutoUpdater] Error:', err.message);
broadcastToAll('update-status', { status: 'error', message: err.message });
});
}); });
// Quit when all windows are closed. // Quit when all windows are closed.
@@ -373,6 +456,33 @@ app.on('window-all-closed', () => {
// ipcMain handlers // ipcMain handlers
// --- Auto-Update IPC handlers ---
ipcMain.handle('check-for-updates', async () => {
try {
const result = await autoUpdater.checkForUpdates();
return { success: true, updateInfo: result?.updateInfo };
} catch (err) {
return { success: false, error: err.message };
}
});
ipcMain.handle('download-update', async () => {
try {
await autoUpdater.downloadUpdate();
return { success: true };
} catch (err) {
return { success: false, error: err.message };
}
});
ipcMain.handle('install-update', () => {
autoUpdater.quitAndInstall();
});
ipcMain.handle('get-app-version', () => {
return app.getVersion();
});
// --- window control handlers (only registered once now) // --- window control handlers (only registered once now)
ipcMain.handle('window-minimize', event => { ipcMain.handle('window-minimize', event => {
BrowserWindow.fromWebContents(event.sender).minimize(); BrowserWindow.fromWebContents(event.sender).minimize();
@@ -560,6 +670,30 @@ ipcMain.handle('zoom-out', event => {
return z; return z;
}); });
ipcMain.handle('get-display-scale', async (event) => {
// Try to read from localStorage data (user data path)
const userDataPath = app.getPath('userData');
const storageFile = path.join(userDataPath, 'localStorage');
try {
// Try to get from electron store or persistent storage
// For now, we'll just return a default and let the app set it
// The display scale is stored in localStorage on the client side
return 100; // Default to 100%
} catch (err) {
return 100; // Default to 100%
}
});
ipcMain.handle('set-zoom-factor', (event, zoomFactor) => {
const wc = BrowserWindow.fromWebContents(event.sender).webContents;
if (wc && typeof wc.setZoomFactor === 'function') {
wc.setZoomFactor(zoomFactor);
return true;
}
return false;
});
// allow renderer to pop a tab into its own window // allow renderer to pop a tab into its own window
ipcMain.handle('open-tab-in-new-window', (event, url) => { ipcMain.handle('open-tab-in-new-window', (event, url) => {
createWindow(url); createWindow(url);
@@ -750,7 +884,7 @@ ipcMain.handle('get-electron-versions', async (event, buildType = 'stable') => {
}); });
ipcMain.handle('upgrade-electron', async (event, buildType = 'stable') => { ipcMain.handle('upgrade-electron', async (event, buildType = 'stable') => {
const { execFile } = require('child_process'); const { exec } = require('child_process');
const packageName = buildType === 'nightly' ? 'electron-nightly' : 'electron'; const packageName = buildType === 'nightly' ? 'electron-nightly' : 'electron';
return new Promise((resolve) => { return new Promise((resolve) => {
@@ -758,10 +892,10 @@ ipcMain.handle('upgrade-electron', async (event, buildType = 'stable') => {
const otherPackage = buildType === 'nightly' ? 'electron' : 'electron-nightly'; const otherPackage = buildType === 'nightly' ? 'electron' : 'electron-nightly';
// Run npm install to upgrade the package // Run npm install to upgrade the package
const args = ['install', '--save-dev', packageName + '@latest']; const command = `npm install --save-dev ${packageName}@latest`;
execFile('npm', args, exec(command,
{ cwd: __dirname, shell: true, maxBuffer: 10 * 1024 * 1024 }, { cwd: __dirname, maxBuffer: 10 * 1024 * 1024 },
(error, stdout, stderr) => { (error, stdout, stderr) => {
if (error) { if (error) {
console.error('Upgrade failed:', error); console.error('Upgrade failed:', error);
+100 -12
View File
@@ -10,11 +10,12 @@
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"dompurify": "^3.1.6", "dompurify": "^3.1.6",
"electron-updater": "^6.6.2",
"highlight.js": "^11.9.0", "highlight.js": "^11.9.0",
"marked": "^12.0.2" "marked": "^12.0.2"
}, },
"devDependencies": { "devDependencies": {
"electron": "^37.3.1", "electron": "^39.2.7",
"electron-builder": "^23.0.0", "electron-builder": "^23.0.0",
"electron-nightly": "^39.0.0-nightly.20250811" "electron-nightly": "^39.0.0-nightly.20250811"
} }
@@ -419,7 +420,6 @@
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"fast-deep-equal": "^3.1.1", "fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0", "fast-json-stable-stringify": "^2.0.0",
@@ -567,7 +567,6 @@
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
"dev": true,
"license": "Python-2.0" "license": "Python-2.0"
}, },
"node_modules/asar": { "node_modules/asar": {
@@ -1117,7 +1116,6 @@
"version": "4.4.1", "version": "4.4.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
"integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"ms": "^2.1.3" "ms": "^2.1.3"
@@ -1409,9 +1407,9 @@
} }
}, },
"node_modules/electron": { "node_modules/electron": {
"version": "37.3.1", "version": "39.2.7",
"resolved": "https://registry.npmjs.org/electron/-/electron-37.3.1.tgz", "resolved": "https://registry.npmjs.org/electron/-/electron-39.2.7.tgz",
"integrity": "sha512-7DhktRLqhe6OJh/Bo75bTI0puUYEmIwSzMinocgO63mx3MVjtIn2tYMzLmAleNIlud2htkjpsMG2zT4PiTCloA==", "integrity": "sha512-KU0uFS6LSTh4aOIC3miolcbizOFP7N1M46VTYVfqIgFiuA2ilfNaOHLDS9tCMvwwHRowAsvqBrh9NgMXcTOHCQ==",
"dev": true, "dev": true,
"hasInstallScript": true, "hasInstallScript": true,
"license": "MIT", "license": "MIT",
@@ -1619,6 +1617,82 @@
"node": ">= 10.0.0" "node": ">= 10.0.0"
} }
}, },
"node_modules/electron-updater": {
"version": "6.6.2",
"resolved": "https://registry.npmjs.org/electron-updater/-/electron-updater-6.6.2.tgz",
"integrity": "sha512-Cr4GDOkbAUqRHP5/oeOmH/L2Bn6+FQPxVLZtPbcmKZC63a1F3uu5EefYOssgZXG3u/zBlubbJ5PJdITdMVggbw==",
"license": "MIT",
"dependencies": {
"builder-util-runtime": "9.3.1",
"fs-extra": "^10.1.0",
"js-yaml": "^4.1.0",
"lazy-val": "^1.0.5",
"lodash.escaperegexp": "^4.1.2",
"lodash.isequal": "^4.5.0",
"semver": "^7.6.3",
"tiny-typed-emitter": "^2.1.0"
}
},
"node_modules/electron-updater/node_modules/builder-util-runtime": {
"version": "9.3.1",
"resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.3.1.tgz",
"integrity": "sha512-2/egrNDDnRaxVwK3A+cJq6UOlqOdedGA7JPqCeJjN2Zjk1/QB/6QUi3b714ScIGS7HafFXTyzJEOr5b44I3kvQ==",
"license": "MIT",
"dependencies": {
"debug": "^4.3.4",
"sax": "^1.2.4"
},
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/electron-updater/node_modules/fs-extra": {
"version": "10.1.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz",
"integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==",
"license": "MIT",
"dependencies": {
"graceful-fs": "^4.2.0",
"jsonfile": "^6.0.1",
"universalify": "^2.0.0"
},
"engines": {
"node": ">=12"
}
},
"node_modules/electron-updater/node_modules/jsonfile": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz",
"integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==",
"license": "MIT",
"dependencies": {
"universalify": "^2.0.0"
},
"optionalDependencies": {
"graceful-fs": "^4.1.6"
}
},
"node_modules/electron-updater/node_modules/semver": {
"version": "7.7.3",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz",
"integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/electron-updater/node_modules/universalify": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
"integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
"license": "MIT",
"engines": {
"node": ">= 10.0.0"
}
},
"node_modules/emoji-regex": { "node_modules/emoji-regex": {
"version": "8.0.0", "version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
@@ -2072,7 +2146,6 @@
"version": "4.2.11", "version": "4.2.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
"dev": true,
"license": "ISC" "license": "ISC"
}, },
"node_modules/graceful-readlink": { "node_modules/graceful-readlink": {
@@ -2358,7 +2431,6 @@
"version": "4.1.0", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"argparse": "^2.0.1" "argparse": "^2.0.1"
@@ -2426,7 +2498,6 @@
"version": "1.0.5", "version": "1.0.5",
"resolved": "https://registry.npmjs.org/lazy-val/-/lazy-val-1.0.5.tgz", "resolved": "https://registry.npmjs.org/lazy-val/-/lazy-val-1.0.5.tgz",
"integrity": "sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q==", "integrity": "sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/lodash": { "node_modules/lodash": {
@@ -2436,6 +2507,19 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/lodash.escaperegexp": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz",
"integrity": "sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==",
"license": "MIT"
},
"node_modules/lodash.isequal": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
"integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==",
"deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead.",
"license": "MIT"
},
"node_modules/lowercase-keys": { "node_modules/lowercase-keys": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz",
@@ -2618,7 +2702,6 @@
"version": "2.1.3", "version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/node-addon-api": { "node_modules/node-addon-api": {
@@ -2846,7 +2929,6 @@
"version": "1.4.1", "version": "1.4.1",
"resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz",
"integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==", "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==",
"dev": true,
"license": "ISC" "license": "ISC"
}, },
"node_modules/semver": { "node_modules/semver": {
@@ -3118,6 +3200,12 @@
"node": ">= 10.0.0" "node": ">= 10.0.0"
} }
}, },
"node_modules/tiny-typed-emitter": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/tiny-typed-emitter/-/tiny-typed-emitter-2.1.0.tgz",
"integrity": "sha512-qVtvMxeXbVej0cQWKqVSSAHmKZEHAvxdF8HEUBFWts8h+xEo5m/lEiPakuyZ3BnCBjOD8i24kzNOiOLLgsSxhA==",
"license": "MIT"
},
"node_modules/tmp": { "node_modules/tmp": {
"version": "0.2.3", "version": "0.2.3",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz",
+10 -2
View File
@@ -1,7 +1,7 @@
{ {
"name": "nebula", "name": "nebula",
"productName": "Nebula", "productName": "Nebula",
"version": "1.0.0", "version": "1.3.2",
"main": "main.js", "main": "main.js",
"scripts": { "scripts": {
"start": "electron .", "start": "electron .",
@@ -14,16 +14,24 @@
"description": "", "description": "",
"dependencies": { "dependencies": {
"dompurify": "^3.1.6", "dompurify": "^3.1.6",
"electron-updater": "^6.6.2",
"highlight.js": "^11.9.0", "highlight.js": "^11.9.0",
"marked": "^12.0.2" "marked": "^12.0.2"
}, },
"devDependencies": { "devDependencies": {
"electron": "^37.3.1", "electron": "^39.2.7",
"electron-builder": "^23.0.0", "electron-builder": "^23.0.0",
"electron-nightly": "^39.0.0-nightly.20250811" "electron-nightly": "^39.0.0-nightly.20250811"
}, },
"build": { "build": {
"appId": "com.andrewzambazos.nebula", "appId": "com.andrewzambazos.nebula",
"publish": [
{
"provider": "github",
"owner": "Bobbybear007",
"repo": "NebulaBrowser"
}
],
"mac": { "mac": {
"category": "public.app-category.productivity", "category": "public.app-category.productivity",
"icon": "assets/images/Logos/Nebula-Favicon.icns" "icon": "assets/images/Logos/Nebula-Favicon.icns"
+9
View File
@@ -134,6 +134,15 @@ contextBridge.exposeInMainWorld('downloadsAPI', {
onScanResult: (handler) => ipcRenderer.on('downloads-scan-result', (_e, payload) => handler(payload)) onScanResult: (handler) => ipcRenderer.on('downloads-scan-result', (_e, payload) => handler(payload))
}); });
// Auto-Updater API exposed to renderer
contextBridge.exposeInMainWorld('updaterAPI', {
checkForUpdates: () => ipcRenderer.invoke('check-for-updates'),
downloadUpdate: () => ipcRenderer.invoke('download-update'),
installUpdate: () => ipcRenderer.invoke('install-update'),
getAppVersion: () => ipcRenderer.invoke('get-app-version'),
onUpdateStatus: (handler) => ipcRenderer.on('update-status', (_e, payload) => handler(payload))
});
// ---------------------------------------- // ----------------------------------------
// Plugin renderer preloads // Plugin renderer preloads
// ---------------------------------------- // ----------------------------------------
+18
View File
@@ -1098,6 +1098,24 @@ window.addEventListener('DOMContentLoaded', () => {
console.error('Error applying saved theme:', err); console.error('Error applying saved theme:', err);
} }
} }
// Initialize display scale (zoom) from localStorage
const savedDisplayScale = localStorage.getItem('nebula-display-scale');
if (savedDisplayScale) {
try {
const scale = Number(savedDisplayScale);
if (scale > 0 && scale <= 300) {
const zoomFactor = scale / 100;
if (ipcRenderer && typeof ipcRenderer.invoke === 'function') {
ipcRenderer.invoke('set-zoom-factor', zoomFactor).catch(err => {
console.error('Error setting zoom factor:', err);
});
}
}
} catch (err) {
console.error('Error applying saved display scale:', err);
}
}
// Initial boot // Initial boot
createTab(); createTab();
+12
View File
@@ -105,6 +105,18 @@
</div> </div>
</div> </div>
<!-- Display Scale -->
<div class="customization-group">
<h3>Display Scale</h3>
<div style="display: flex; flex-direction: column; gap: 12px;">
<div style="display: flex; align-items: center; gap: 12px;">
<input type="range" id="display-scale-slider" min="50" max="300" value="100" step="10" style="flex: 1; cursor: pointer;">
<span id="display-scale-value" style="min-width: 50px; text-align: right; font-weight: bold;">100%</span>
</div>
<p class="note">Adjust the default display scale (zoom) when opening the browser. Requires reload to take effect.</p>
</div>
</div>
<!-- Color Customization --> <!-- Color Customization -->
<div class="customization-group"> <div class="customization-group">
<h3>Custom Colors</h3> <h3>Custom Colors</h3>
+22
View File
@@ -11,6 +11,7 @@ const WEATHER_UNIT_KEY = 'nebula-weather-unit'; // 'auto' | 'c' | 'f'
const HOME_SEARCH_Y_KEY = 'nebula-home-search-y'; // number (vh) const HOME_SEARCH_Y_KEY = 'nebula-home-search-y'; // number (vh)
const HOME_BOOKMARKS_Y_KEY = 'nebula-home-bookmarks-y'; // number (vh) const HOME_BOOKMARKS_Y_KEY = 'nebula-home-bookmarks-y'; // number (vh)
const HOME_GLANCE_CORNER_KEY = 'nebula-home-glance-corner'; // 'br'|'bl'|'tr'|'tl' const HOME_GLANCE_CORNER_KEY = 'nebula-home-glance-corner'; // 'br'|'bl'|'tr'|'tl'
const DISPLAY_SCALE_KEY = 'nebula-display-scale'; // number (50-300)
function showStatus(message) { function showStatus(message) {
if (statusText && statusDiv) { if (statusText && statusDiv) {
@@ -167,6 +168,27 @@ window.addEventListener('DOMContentLoaded', () => {
notify(); notify();
})); }));
} catch (e) { console.warn('Home layout control setup failed', e); } } catch (e) { console.warn('Home layout control setup failed', e); }
// Display scale controls
try {
const scaleSlider = document.getElementById('display-scale-slider');
const scaleValue = document.getElementById('display-scale-value');
const initScale = Number(localStorage.getItem(DISPLAY_SCALE_KEY) || 100);
if (scaleSlider) {
scaleSlider.value = String(initScale);
if (scaleValue) scaleValue.textContent = initScale + '%';
}
if (scaleSlider) {
scaleSlider.addEventListener('input', () => {
const val = Number(scaleSlider.value);
if (scaleValue) scaleValue.textContent = val + '%';
localStorage.setItem(DISPLAY_SCALE_KEY, String(val));
showStatus(`Display scale set to ${val}%`);
});
}
} catch (e) { console.warn('Display scale setup failed', e); }
}); });
// Tabs: simple controller // Tabs: simple controller