diff --git a/main.js b/main.js index f105f43..148242c 100644 --- a/main.js +++ b/main.js @@ -334,6 +334,20 @@ ipcMain.handle('check-update-now', async () => { // --- Window --- +function attachDevToolsShortcut(webContents) { + // Ctrl+Shift+I / F12 open DevTools on this webContents. + // Always available so a kiosk machine can be debugged without un-kiosking. + webContents.on('before-input-event', (_e, input) => { + if (input.type !== 'keyDown') return; + const isDevToolsCombo = + (input.control && input.shift && (input.key === 'I' || input.key === 'i')) || + input.key === 'F12'; + if (isDevToolsCombo) { + try { webContents.openDevTools({ mode: 'detach' }); } catch (_) {} + } + }); +} + async function createWindow() { mainWindow = new BrowserWindow({ width: 1280, @@ -347,6 +361,8 @@ async function createWindow() { }, }); + attachDevToolsShortcut(mainWindow.webContents); + if (isDev) { mainWindow.loadURL(RENDERER_URL); } else { @@ -506,19 +522,22 @@ async function restoreSession() { const sess = cfg.openedSession; if (!sess || !Array.isArray(sess.tabs) || !sess.tabs.length) return; console.log(`[session] restoring ${sess.tabs.length} tab(s), active=${sess.activeName}`); - // Spawn each saved tab by replaying create-view. ipcMain.emit triggers the handler - // synchronously; the view's loadURL is fire-and-forget. We chain via setTimeout to - // avoid stacking N loaders simultaneously. + // Spawn each saved tab by replaying create-view, sequentially with a small delay. + // Concurrent create-view calls in v1.0.3 caused races: multiple setLoader/addChild + // interleaved → some views ended up unmounted (white screen). Spacing them out + // gives each view time to mount before the next steals currentView. + const fakeEvent = { sender: mainWindow.webContents }; for (const tab of sess.tabs) { if (!tab?.name || !tab?.url) continue; - ipcMain.emit('create-view', { sender: mainWindow.webContents }, tab.name, tab.url, tab.imageUrl || '', 1.0, !!tab.useProxy); + ipcMain.emit('create-view', fakeEvent, tab.name, tab.url, tab.imageUrl || '', 1.0, !!tab.useProxy); + await new Promise(r => setTimeout(r, 150)); } - // After all spawned, the last one is `currentView`. Switch to the saved active if different. + // Switch to saved active if it isn't already the last-spawned (currentView). if (sess.activeName === 'home') { - ipcMain.emit('hide-view', { sender: mainWindow.webContents }); + ipcMain.emit('hide-view', fakeEvent); sendOpenedApps('home'); } else if (sess.activeName && sess.activeName !== currentView?.name) { - ipcMain.emit('show-view', { sender: mainWindow.webContents }, sess.activeName); + ipcMain.emit('show-view', fakeEvent, sess.activeName); } } catch (e) { console.warn('[session] restore failed:', e.message); @@ -557,6 +576,7 @@ ipcMain.on('create-view', async (_event, name, url, imageUrl, _zoom, useProxy) = openedApps.push(appEntry); currentView = appEntry; view.setBounds(getViewBounds()); + attachDevToolsShortcut(view.webContents); view.webContents.on('did-finish-load', () => { removeLoader(); @@ -1190,37 +1210,23 @@ app.whenReady().then(async () => { app.userAgentFallback = cleanUserAgent; session.defaultSession.setUserAgent(cleanUserAgent); - // Chrome version from the cleaned UA — used for client hints below - const chromeVerMatch = cleanUserAgent.match(/Chrome\/(\d+)/); - const chromeMajor = chromeVerMatch ? chromeVerMatch[1] : '128'; - const secChUa = `"Not_A Brand";v="8", "Chromium";v="${chromeMajor}", "Google Chrome";v="${chromeMajor}"`; - - const installRequestHooks = (sess) => { - sess.webRequest.onBeforeSendHeaders( - { urls: ['https://*/*', 'http://*/*'] }, - (details, callback) => { - const headers = details.requestHeaders; - // Spoof Sec-CH-UA so embedded-browser detectors (Google login, etc.) see - // a real-Chrome brand list. Electron normally injects the app name as - // the brand which is how Google fingerprints us as "embedded/unsafe". - headers['sec-ch-ua'] = secChUa; - headers['sec-ch-ua-mobile'] = '?0'; - headers['sec-ch-ua-platform'] = '"Windows"'; - // Add Referer to image requests so hotlink protection doesn't block them - if (details.resourceType === 'image' && !headers['Referer'] && !headers['referer']) { - try { - const u = new URL(details.url); - headers['Referer'] = `${u.protocol}//${u.hostname}/`; - } catch (_) {} - } - callback({ requestHeaders: headers }); + // Add Referer to image requests so hotlink protection doesn't block them. + // (Sec-CH-UA spoofing was tried in 1.0.4 and caused white pages — reverted. + // Google embedded-browser detection is now mitigated only via adblock whitelist + // of gstatic/google-analytics/etc., which previously was being eaten silently.) + session.defaultSession.webRequest.onBeforeSendHeaders( + { urls: ['https://*/*', 'http://*/*'] }, + (details, callback) => { + const headers = details.requestHeaders; + if (details.resourceType === 'image' && !headers['Referer'] && !headers['referer']) { + try { + const u = new URL(details.url); + headers['Referer'] = `${u.protocol}//${u.hostname}/`; + } catch (_) {} } - ); - }; - - installRequestHooks(session.defaultSession); - installRequestHooks(getProxySession()); - installRequestHooks(getDirectSession()); + callback({ requestHeaders: headers }); + } + ); // Apply proxy from config before blocker tries to download filter lists loadTrustedDomainsFromDisk(); diff --git a/package.json b/package.json index ac8ad4a..703328c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ESH-Media", - "version": "1.0.4", + "version": "1.0.5", "private": true, "main": "main.js", "scripts": { diff --git a/src/components/Header.tsx b/src/components/Header.tsx index 9cff4ce..a3540da 100644 --- a/src/components/Header.tsx +++ b/src/components/Header.tsx @@ -290,14 +290,22 @@ const Header: React.FC = ({ activeApp, setActiveApp, onAppsChange, )} - {updateStatus && updateStatus.state !== 'error' && ( -
+ {updateStatus && ( +
{updateStatus.state === 'available' && ( <> Загружается обновление {updateStatus.version}{updateStatus.currentVersion ? ` (текущая ${updateStatus.currentVersion})` : ''}… )} + {updateStatus.state === 'error' && ( + <> + Ошибка обновления: {updateStatus.message} + + + )} {updateStatus.state === 'downloading' && ( <> Скачивается {updateStatus.version || 'обновление'}: {updateStatus.percent}% diff --git a/src/styles/main.css b/src/styles/main.css index 48ef3df..3b344b1 100644 --- a/src/styles/main.css +++ b/src/styles/main.css @@ -1480,6 +1480,11 @@ body { } .update-banner-close:hover { color: #fff; } +.update-banner.error { + border-color: rgba(229,9,20,0.5); + background: rgba(40,10,12,0.95); +} + .update-banner-progress { flex: 1; height: 4px;