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:
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Generated
+1157
-1707
File diff suppressed because it is too large
Load Diff
+6
-3
@@ -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",
|
||||
|
||||
@@ -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
@@ -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),
|
||||
|
||||
Vendored
+1
@@ -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>;
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user