fix: harden production electron app behavior

- No developer tools or zoom
- No refresh window
- Open links externally
This commit is contained in:
talksik
2026-04-12 13:30:18 -07:00
parent e1d2bf11da
commit 5a6db9bd48
+67 -6
View File
@@ -5,12 +5,14 @@ import { updateElectronApp, UpdateSourceType } from 'update-electron-app';
import type { LinkMetadata } from './lib/link-metadata';
updateElectronApp({
updateSource: {
type: UpdateSourceType.StaticStorage,
baseUrl: `https://storage.googleapis.com/flowy-releases/llink/${process.platform}/${process.arch}`
}
});
if (app.isPackaged) {
updateElectronApp({
updateSource: {
type: UpdateSourceType.StaticStorage,
baseUrl: `https://storage.googleapis.com/flowy-releases/llink/${process.platform}/${process.arch}`
}
});
}
// Handle creating/removing shortcuts on Windows when installing/uninstalling.
if (started) {
@@ -27,6 +29,61 @@ let autoplayWindow: BrowserWindow | null = null;
let huddleWindow: BrowserWindow | null = null;
let screenRecordWindow: BrowserWindow | null = null;
function hardenWindow(win: BrowserWindow) {
const wc = win.webContents;
// Disable pinch / Cmd+scroll zoom; our layouts assume DPR=1.
wc.setVisualZoomLevelLimits(1, 1);
// Block devtools, reload, and zoom keyboard shortcuts in packaged builds.
wc.on('before-input-event', (event, input) => {
if (input.type !== 'keyDown') return;
const key = input.key.toLowerCase();
const cmdOrCtrl = input.meta || input.control;
const isDevtools =
key === 'f12' ||
(cmdOrCtrl && input.alt && key === 'i') ||
(cmdOrCtrl && input.shift && key === 'i') ||
(cmdOrCtrl && input.shift && key === 'c') ||
(cmdOrCtrl && input.shift && key === 'j');
const isReload = cmdOrCtrl && (key === 'r' || key === 'f5');
const isZoom = cmdOrCtrl && (key === '=' || key === '+' || key === '-' || key === '0');
if (app.isPackaged && (isDevtools || isReload)) {
event.preventDefault();
}
if (isZoom) {
event.preventDefault();
}
});
// Route window.open / target=_blank through the OS browser instead of a new BrowserWindow.
wc.setWindowOpenHandler(({ url }) => {
if (url.startsWith('http://') || url.startsWith('https://')) {
shell.openExternal(url);
}
return { action: 'deny' };
});
// Freeze the window to its initial origin — block any full-page navigation.
// In-app routing uses pushState / hash which don't trigger will-navigate.
wc.on('will-navigate', (event, url) => {
const currentUrl = wc.getURL();
if (!currentUrl) return;
try {
if (new URL(url).origin !== new URL(currentUrl).origin) {
event.preventDefault();
if (url.startsWith('http://') || url.startsWith('https://')) {
shell.openExternal(url);
}
}
} catch {
event.preventDefault();
}
});
}
const createWindow = () => {
mainWindow = new BrowserWindow({
width: 800,
@@ -39,6 +96,7 @@ const createWindow = () => {
backgroundThrottling: false,
},
});
hardenWindow(mainWindow);
if (MAIN_WINDOW_VITE_DEV_SERVER_URL) {
mainWindow.loadURL(MAIN_WINDOW_VITE_DEV_SERVER_URL);
@@ -72,6 +130,7 @@ const createAutoplayWindow = () => {
},
});
autoplayWindow.setVisibleOnAllWorkspaces(true);
hardenWindow(autoplayWindow);
if (AUTOPLAY_WINDOW_VITE_DEV_SERVER_URL) {
autoplayWindow.loadURL(AUTOPLAY_WINDOW_VITE_DEV_SERVER_URL);
@@ -103,6 +162,7 @@ const createHuddleWindow = () => {
preload: path.join(__dirname, 'preload.js'),
},
});
hardenWindow(huddleWindow);
if (HUDDLE_WINDOW_VITE_DEV_SERVER_URL) {
huddleWindow.loadURL(HUDDLE_WINDOW_VITE_DEV_SERVER_URL);
@@ -147,6 +207,7 @@ const createScreenRecordWindow = () => {
},
});
screenRecordWindow.setVisibleOnAllWorkspaces(true);
hardenWindow(screenRecordWindow);
if (SCREEN_RECORD_WINDOW_VITE_DEV_SERVER_URL) {
screenRecordWindow.loadURL(SCREEN_RECORD_WINDOW_VITE_DEV_SERVER_URL);