fix: vidmoly redirect loop, body leak, update error handling, submenu overflow

- Add max redirect depth (10) to Vidmoly _fetch to prevent stack overflow
- Drain undici response body on redirect to prevent connection leaks
- Fix installUpdate unhandled promise rejection in main.js
- Fix context menu submenu viewport overflow with flip-left CSS

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Administrator 2026-03-10 14:11:03 +01:00
parent 9c56fabce1
commit 52b2e0a1e4
4 changed files with 32 additions and 15 deletions

View File

@ -45,7 +45,8 @@ class VidmolyUploader {
/** /**
* Simple GET/POST using built-in fetch (handles redirects) * Simple GET/POST using built-in fetch (handles redirects)
*/ */
async _fetch(url, opts = {}) { async _fetch(url, opts = {}, _redirectCount = 0) {
const MAX_REDIRECTS = 10;
const headers = { const headers = {
'User-Agent': USER_AGENT, 'User-Agent': USER_AGENT,
...(opts.headers || {}) ...(opts.headers || {})
@ -64,10 +65,15 @@ class VidmolyUploader {
// Follow redirects manually (to capture cookies at each hop) // Follow redirects manually (to capture cookies at each hop)
if ([301, 302, 303, 307, 308].includes(res.status)) { if ([301, 302, 303, 307, 308].includes(res.status)) {
// Drain body to prevent connection leak
try { await res.text(); } catch {}
if (_redirectCount >= MAX_REDIRECTS) {
throw new Error('Zu viele Redirects');
}
const location = res.headers.get('location'); const location = res.headers.get('location');
if (location) { if (location) {
const nextUrl = new URL(location, url).href; const nextUrl = new URL(location, url).href;
return this._fetch(nextUrl, { ...opts, method: 'GET', body: undefined }); return this._fetch(nextUrl, { ...opts, method: 'GET', body: undefined }, _redirectCount + 1);
} }
} }
@ -240,11 +246,13 @@ class VidmolyUploader {
let resultHtml; let resultHtml;
if ([301, 302, 303].includes(statusCode)) { if ([301, 302, 303].includes(statusCode)) {
const location = headers && headers.location; const location = headers && headers.location;
// Always drain the original body to prevent connection leak
try { await body.text(); } catch {}
if (location) { if (location) {
const resultRes = await this._fetch(new URL(location, uploadUrl).href); const resultRes = await this._fetch(new URL(location, uploadUrl).href);
resultHtml = await resultRes.text(); resultHtml = await resultRes.text();
} else { } else {
resultHtml = await body.text(); resultHtml = '';
} }
} else { } else {
resultHtml = await body.text(); resultHtml = await body.text();

22
main.js
View File

@ -349,17 +349,17 @@ ipcMain.handle('app:check-updates', async () => {
} }
}); });
ipcMain.handle('app:install-update', async () => { ipcMain.handle('app:install-update', () => {
try { installUpdate((progress) => {
installUpdate((progress) => { if (mainWindow && !mainWindow.isDestroyed()) {
if (mainWindow && !mainWindow.isDestroyed()) { mainWindow.webContents.send('app:update-progress', progress);
mainWindow.webContents.send('app:update-progress', progress); }
} }).catch((err) => {
}); if (mainWindow && !mainWindow.isDestroyed()) {
return { started: true }; mainWindow.webContents.send('app:update-progress', { stage: 'error', error: err.message });
} catch (err) { }
return { error: err.message }; });
} return { started: true };
}); });
ipcMain.handle('app:abort-update', () => { ipcMain.handle('app:abort-update', () => {

View File

@ -342,8 +342,14 @@ function showContextMenu(x, y) {
if (aotItem) aotItem.textContent = alwaysOnTopState ? 'Immer im Vordergrund ✓' : 'Immer im Vordergrund'; if (aotItem) aotItem.textContent = alwaysOnTopState ? 'Immer im Vordergrund ✓' : 'Immer im Vordergrund';
menu.style.display = 'block'; menu.style.display = 'block';
menu.style.left = Math.min(x, window.innerWidth - menu.offsetWidth - 5) + 'px'; const menuX = Math.min(x, window.innerWidth - menu.offsetWidth - 5);
menu.style.left = menuX + 'px';
menu.style.top = Math.min(y, window.innerHeight - menu.offsetHeight - 5) + 'px'; menu.style.top = Math.min(y, window.innerHeight - menu.offsetHeight - 5) + 'px';
// Flip submenus if they would overflow the viewport right edge
menu.querySelectorAll('.ctx-submenu-items').forEach(sub => {
sub.classList.toggle('flip-left', menuX + menu.offsetWidth + sub.offsetWidth > window.innerWidth);
});
} }
function hideContextMenu() { function hideContextMenu() {

View File

@ -314,8 +314,11 @@ body {
border-radius: 6px; border-radius: 6px;
padding: 4px 0; padding: 4px 0;
min-width: 160px; min-width: 160px;
max-width: calc(100vw - 16px);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.6); box-shadow: 0 8px 24px rgba(0, 0, 0, 0.6);
} }
/* Flip submenu to the left when it would overflow the viewport */
.ctx-submenu-items.flip-left { left: auto; right: 100%; }
.ctx-submenu:hover .ctx-submenu-items { display: block; } .ctx-submenu:hover .ctx-submenu-items { display: block; }
/* Settings View */ /* Settings View */