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.
|
- 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.
|
- 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.
|
- 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.0.1 - 2026-08-05
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ The application stores its settings and local database on the computer where it
|
|||||||
|
|
||||||
Requirements:
|
Requirements:
|
||||||
|
|
||||||
- Node.js 20 or newer
|
- Node.js 22.13 or newer
|
||||||
- Windows for building the NSIS installer
|
- Windows for building the NSIS installer
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
|
|||||||
Generated
+1157
-1707
File diff suppressed because it is too large
Load Diff
+6
-3
@@ -5,6 +5,9 @@
|
|||||||
"main": "dist/main.js",
|
"main": "dist/main.js",
|
||||||
"author": "Sucukdeluxe",
|
"author": "Sucukdeluxe",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=22.13.0"
|
||||||
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsc",
|
"build": "tsc",
|
||||||
"start": "npm run build && electron .",
|
"start": "npm run build && electron .",
|
||||||
@@ -28,14 +31,14 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^1.16.1",
|
"axios": "^1.16.1",
|
||||||
"better-sqlite3": "^12.10.0",
|
"better-sqlite3": "^12.10.0",
|
||||||
"electron-updater": "^6.8.3"
|
"electron-updater": "^6.8.9"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^10.0.1",
|
"@eslint/js": "^10.0.1",
|
||||||
"@types/better-sqlite3": "^7.6.13",
|
"@types/better-sqlite3": "^7.6.13",
|
||||||
"@types/node": "^20.10.0",
|
"@types/node": "^20.10.0",
|
||||||
"electron": "^28.0.0",
|
"electron": "^43.3.0",
|
||||||
"electron-builder": "^24.9.0",
|
"electron-builder": "^26.15.7",
|
||||||
"eslint": "^10.4.0",
|
"eslint": "^10.4.0",
|
||||||
"eslint-plugin-security": "^4.0.0",
|
"eslint-plugin-security": "^4.0.0",
|
||||||
"playwright": "^1.60.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.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`);
|
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(() => ({
|
const queueEmptyActions = await win.evaluate(() => ({
|
||||||
count: document.getElementById('queueCount')?.textContent?.trim() || '',
|
count: document.getElementById('queueCount')?.textContent?.trim() || '',
|
||||||
startDisabled: document.getElementById('btnStart')?.disabled === true,
|
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';
|
import { CustomClip, MergeGroupItem, MergeGroup, QueueItem, DownloadProgress } from './types';
|
||||||
|
|
||||||
// Types
|
// Types
|
||||||
@@ -83,6 +83,7 @@ contextBridge.exposeInMainWorld('api', {
|
|||||||
selectFolder: () => ipcRenderer.invoke('select-folder'),
|
selectFolder: () => ipcRenderer.invoke('select-folder'),
|
||||||
selectVideoFile: () => ipcRenderer.invoke('select-video-file'),
|
selectVideoFile: () => ipcRenderer.invoke('select-video-file'),
|
||||||
selectMultipleVideos: () => ipcRenderer.invoke('select-multiple-videos'),
|
selectMultipleVideos: () => ipcRenderer.invoke('select-multiple-videos'),
|
||||||
|
getPathForFile: (file: File): string => webUtils.getPathForFile(file),
|
||||||
saveVideoDialog: (defaultName: string) => ipcRenderer.invoke('save-video-dialog', defaultName),
|
saveVideoDialog: (defaultName: string) => ipcRenderer.invoke('save-video-dialog', defaultName),
|
||||||
openFolder: (path: string) => ipcRenderer.invoke('open-folder', path),
|
openFolder: (path: string) => ipcRenderer.invoke('open-folder', path),
|
||||||
openFile: (path: string) => ipcRenderer.invoke('open-file', path),
|
openFile: (path: string) => ipcRenderer.invoke('open-file', path),
|
||||||
|
|||||||
Vendored
+1
@@ -336,6 +336,7 @@ interface ApiBridge {
|
|||||||
selectFolder(): Promise<string | null>;
|
selectFolder(): Promise<string | null>;
|
||||||
selectVideoFile(): Promise<string | null>;
|
selectVideoFile(): Promise<string | null>;
|
||||||
selectMultipleVideos(): Promise<string[] | null>;
|
selectMultipleVideos(): Promise<string[] | null>;
|
||||||
|
getPathForFile(file: File): string;
|
||||||
saveVideoDialog(defaultName: string): Promise<string | null>;
|
saveVideoDialog(defaultName: string): Promise<string | null>;
|
||||||
openFolder(path: string): Promise<void>;
|
openFolder(path: string): Promise<void>;
|
||||||
openFile(path: string): Promise<boolean>;
|
openFile(path: string): Promise<boolean>;
|
||||||
|
|||||||
@@ -402,8 +402,7 @@ function initCutterDragDrop(): void {
|
|||||||
// First video-ish file wins
|
// First video-ish file wins
|
||||||
const allowed = /\.(mp4|mkv|ts|mov|avi)$/i;
|
const allowed = /\.(mp4|mkv|ts|mov|avi)$/i;
|
||||||
const file = files.find((f) => allowed.test(f.name)) || files[0];
|
const file = files.find((f) => allowed.test(f.name)) || files[0];
|
||||||
// Electron extends File with .path even with contextIsolation:true
|
const filePath = window.api.getPathForFile(file);
|
||||||
const filePath = (file as unknown as { path?: string }).path || '';
|
|
||||||
if (!filePath) return;
|
if (!filePath) return;
|
||||||
|
|
||||||
const loader = (window as unknown as { loadCutterFromPath?: (p: string) => Promise<void> }).loadCutterFromPath;
|
const loader = (window as unknown as { loadCutterFromPath?: (p: string) => Promise<void> }).loadCutterFromPath;
|
||||||
|
|||||||
Reference in New Issue
Block a user