fix: harden the Electron 43 release runtime

Upgrade Electron, electron-builder, and electron-updater to patched versions, align the supported Node.js toolchain, and preserve secure file drops through the context-isolated webUtils bridge.

Cover the Electron 43 file-path migration with a real temporary-file drag-and-drop regression test and retain zero-vulnerability lockfile resolution.
This commit is contained in:
Sucukdeluxe
2026-08-10 13:01:21 +02:00
parent d599593966
commit 3f0c075859
8 changed files with 1234 additions and 1719 deletions
+1
View File
@@ -8,6 +8,7 @@
- Added searchable settings, synchronized section navigation and clearer empty, queue and busy states.
- Improved German and English localization, including locale-aware dates and accessibility labels.
- Hardened the release test suite with isolated application data, browser profiles, downloads and offline network fixtures.
- Updated the desktop runtime and Windows packaging stack with current security fixes.
## 1.0.1 - 2026-08-05
+1 -1
View File
@@ -26,7 +26,7 @@ The application stores its settings and local database on the computer where it
Requirements:
- Node.js 20 or newer
- Node.js 22.13 or newer
- Windows for building the NSIS installer
```powershell
+1157 -1707
View File
File diff suppressed because it is too large Load Diff
+6 -3
View File
@@ -5,6 +5,9 @@
"main": "dist/main.js",
"author": "Sucukdeluxe",
"license": "MIT",
"engines": {
"node": ">=22.13.0"
},
"scripts": {
"build": "tsc",
"start": "npm run build && electron .",
@@ -28,14 +31,14 @@
"dependencies": {
"axios": "^1.16.1",
"better-sqlite3": "^12.10.0",
"electron-updater": "^6.8.3"
"electron-updater": "^6.8.9"
},
"devDependencies": {
"@eslint/js": "^10.0.1",
"@types/better-sqlite3": "^7.6.13",
"@types/node": "^20.10.0",
"electron": "^28.0.0",
"electron-builder": "^24.9.0",
"electron": "^43.3.0",
"electron-builder": "^26.15.7",
"eslint": "^10.4.0",
"eslint-plugin-security": "^4.0.0",
"playwright": "^1.60.0",
+60
View File
@@ -80,6 +80,66 @@ async function run() {
check(shell.topNavigationItems === 7, `Expected 7 native primary navigation buttons, found ${shell.topNavigationItems}`);
check(shell.nonButtonNavigationItems === 0, `Expected only native primary navigation buttons, found ${shell.nonButtonNavigationItems} non-buttons`);
const cutterDropFixturePath = path.join(environment.mediaDir, 'electron-43-cutter-drop.mp4');
fs.writeFileSync(cutterDropFixturePath, 'electron-43-cutter-drop-fixture', 'utf8');
await app.evaluate(({ ipcMain }) => {
globalThis.__workspaceCutterDropPaths = { videoInfo: '', preview: '' };
ipcMain.removeHandler('get-video-info');
ipcMain.handle('get-video-info', (_, filePath) => {
globalThis.__workspaceCutterDropPaths.videoInfo = filePath;
return { duration: 120, width: 1920, height: 1080, fps: 60 };
});
ipcMain.removeHandler('extract-frame');
ipcMain.handle('extract-frame', (_, filePath) => {
globalThis.__workspaceCutterDropPaths.preview = filePath;
return null;
});
});
await win.evaluate(() => {
window.showTab('cutter');
const input = document.createElement('input');
input.type = 'file';
input.id = 'workspaceCutterDropInput';
document.body.appendChild(input);
});
await win.locator('#workspaceCutterDropInput').setInputFiles(cutterDropFixturePath);
const cutterFileObject = await win.evaluate(() => {
const input = document.getElementById('workspaceCutterDropInput');
const file = input instanceof HTMLInputElement ? input.files?.[0] : undefined;
if (!file) return { name: '', legacyPathType: 'missing', apiType: typeof window.api.getPathForFile };
const transfer = new DataTransfer();
transfer.items.add(file);
document.getElementById('cutterTab')?.dispatchEvent(new DragEvent('drop', {
bubbles: true,
cancelable: true,
dataTransfer: transfer
}));
return {
name: file.name,
legacyPathType: typeof file.path,
apiType: typeof window.api.getPathForFile
};
});
await win.waitForTimeout(250);
const cutterDropPaths = await app.evaluate(() => ({ ...globalThis.__workspaceCutterDropPaths }));
const cutterDropUi = await win.evaluate(() => ({
filePath: document.getElementById('cutterFilePath')?.value || '',
infoVisible: document.getElementById('cutterInfo')?.classList.contains('shown') || false,
cutEnabled: document.getElementById('btnCut')?.disabled === false
}));
checks.cutterDrop = {
electronVersion: await app.evaluate(() => process.versions.electron),
expectedPath: cutterDropFixturePath,
fileObject: cutterFileObject,
ipc: cutterDropPaths,
ui: cutterDropUi
};
check(cutterFileObject.legacyPathType === 'undefined', `Electron File.path is unexpectedly ${cutterFileObject.legacyPathType}`);
check(cutterDropUi.filePath === cutterDropFixturePath, `Cutter drop resolved "${cutterDropUi.filePath}" instead of the Electron file path`);
check(cutterDropPaths.videoInfo === cutterDropFixturePath, `Cutter drop sent "${cutterDropPaths.videoInfo}" to video info instead of the Electron file path`);
check(cutterDropPaths.preview === cutterDropFixturePath, `Cutter drop sent "${cutterDropPaths.preview}" to preview instead of the Electron file path`);
check(cutterDropUi.infoVisible && cutterDropUi.cutEnabled, 'Cutter drop did not populate the cutter controls');
const queueEmptyActions = await win.evaluate(() => ({
count: document.getElementById('queueCount')?.textContent?.trim() || '',
startDisabled: document.getElementById('btnStart')?.disabled === true,
+2 -1
View File
@@ -1,4 +1,4 @@
import { contextBridge, ipcRenderer } from 'electron';
import { contextBridge, ipcRenderer, webUtils } from 'electron';
import { CustomClip, MergeGroupItem, MergeGroup, QueueItem, DownloadProgress } from './types';
// Types
@@ -83,6 +83,7 @@ contextBridge.exposeInMainWorld('api', {
selectFolder: () => ipcRenderer.invoke('select-folder'),
selectVideoFile: () => ipcRenderer.invoke('select-video-file'),
selectMultipleVideos: () => ipcRenderer.invoke('select-multiple-videos'),
getPathForFile: (file: File): string => webUtils.getPathForFile(file),
saveVideoDialog: (defaultName: string) => ipcRenderer.invoke('save-video-dialog', defaultName),
openFolder: (path: string) => ipcRenderer.invoke('open-folder', path),
openFile: (path: string) => ipcRenderer.invoke('open-file', path),
+1
View File
@@ -336,6 +336,7 @@ interface ApiBridge {
selectFolder(): Promise<string | null>;
selectVideoFile(): Promise<string | null>;
selectMultipleVideos(): Promise<string[] | null>;
getPathForFile(file: File): string;
saveVideoDialog(defaultName: string): Promise<string | null>;
openFolder(path: string): Promise<void>;
openFile(path: string): Promise<boolean>;
+1 -2
View File
@@ -402,8 +402,7 @@ function initCutterDragDrop(): void {
// First video-ish file wins
const allowed = /\.(mp4|mkv|ts|mov|avi)$/i;
const file = files.find((f) => allowed.test(f.name)) || files[0];
// Electron extends File with .path even with contextIsolation:true
const filePath = (file as unknown as { path?: string }).path || '';
const filePath = window.api.getPathForFile(file);
if (!filePath) return;
const loader = (window as unknown as { loadCutterFromPath?: (p: string) => Promise<void> }).loadCutterFromPath;