fix(1.0.5): revert Sec-CH-UA spoof (white pages), add DevTools shortcut
- Sec-CH-UA / Sec-CH-UA-Mobile / Sec-CH-UA-Platform header overrides on every request in 1.0.4 broke page rendering (all views white). Reverted to image-Referer-only behavior from 1.0.3. The Google "embedded browser" fix in 1.0.4 came primarily from the adblock whitelist (which IS kept) — Sec-CH-UA spoofing was the suspect for the regression. - Ctrl+Shift+I and F12 now open DevTools on the main shell and on every in-app browser view. Always-on so kiosk machines can be debugged without leaving kiosk mode. - Restore session sequenced (await 150ms between tabs) to avoid concurrent create-view races where multiple setLoader/addChild interleaved. - Update banner now shows error state with a "Повторить" button instead of hiding it, so install-update failures are visible to the user. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
58
main.js
58
main.js
@@ -334,6 +334,20 @@ ipcMain.handle('check-update-now', async () => {
|
|||||||
|
|
||||||
// --- Window ---
|
// --- 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() {
|
async function createWindow() {
|
||||||
mainWindow = new BrowserWindow({
|
mainWindow = new BrowserWindow({
|
||||||
width: 1280,
|
width: 1280,
|
||||||
@@ -347,6 +361,8 @@ async function createWindow() {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
attachDevToolsShortcut(mainWindow.webContents);
|
||||||
|
|
||||||
if (isDev) {
|
if (isDev) {
|
||||||
mainWindow.loadURL(RENDERER_URL);
|
mainWindow.loadURL(RENDERER_URL);
|
||||||
} else {
|
} else {
|
||||||
@@ -506,19 +522,22 @@ async function restoreSession() {
|
|||||||
const sess = cfg.openedSession;
|
const sess = cfg.openedSession;
|
||||||
if (!sess || !Array.isArray(sess.tabs) || !sess.tabs.length) return;
|
if (!sess || !Array.isArray(sess.tabs) || !sess.tabs.length) return;
|
||||||
console.log(`[session] restoring ${sess.tabs.length} tab(s), active=${sess.activeName}`);
|
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
|
// Spawn each saved tab by replaying create-view, sequentially with a small delay.
|
||||||
// synchronously; the view's loadURL is fire-and-forget. We chain via setTimeout to
|
// Concurrent create-view calls in v1.0.3 caused races: multiple setLoader/addChild
|
||||||
// avoid stacking N loaders simultaneously.
|
// 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) {
|
for (const tab of sess.tabs) {
|
||||||
if (!tab?.name || !tab?.url) continue;
|
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') {
|
if (sess.activeName === 'home') {
|
||||||
ipcMain.emit('hide-view', { sender: mainWindow.webContents });
|
ipcMain.emit('hide-view', fakeEvent);
|
||||||
sendOpenedApps('home');
|
sendOpenedApps('home');
|
||||||
} else if (sess.activeName && sess.activeName !== currentView?.name) {
|
} 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) {
|
} catch (e) {
|
||||||
console.warn('[session] restore failed:', e.message);
|
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);
|
openedApps.push(appEntry);
|
||||||
currentView = appEntry;
|
currentView = appEntry;
|
||||||
view.setBounds(getViewBounds());
|
view.setBounds(getViewBounds());
|
||||||
|
attachDevToolsShortcut(view.webContents);
|
||||||
|
|
||||||
view.webContents.on('did-finish-load', () => {
|
view.webContents.on('did-finish-load', () => {
|
||||||
removeLoader();
|
removeLoader();
|
||||||
@@ -1190,23 +1210,14 @@ app.whenReady().then(async () => {
|
|||||||
app.userAgentFallback = cleanUserAgent;
|
app.userAgentFallback = cleanUserAgent;
|
||||||
session.defaultSession.setUserAgent(cleanUserAgent);
|
session.defaultSession.setUserAgent(cleanUserAgent);
|
||||||
|
|
||||||
// Chrome version from the cleaned UA — used for client hints below
|
// Add Referer to image requests so hotlink protection doesn't block them.
|
||||||
const chromeVerMatch = cleanUserAgent.match(/Chrome\/(\d+)/);
|
// (Sec-CH-UA spoofing was tried in 1.0.4 and caused white pages — reverted.
|
||||||
const chromeMajor = chromeVerMatch ? chromeVerMatch[1] : '128';
|
// Google embedded-browser detection is now mitigated only via adblock whitelist
|
||||||
const secChUa = `"Not_A Brand";v="8", "Chromium";v="${chromeMajor}", "Google Chrome";v="${chromeMajor}"`;
|
// of gstatic/google-analytics/etc., which previously was being eaten silently.)
|
||||||
|
session.defaultSession.webRequest.onBeforeSendHeaders(
|
||||||
const installRequestHooks = (sess) => {
|
|
||||||
sess.webRequest.onBeforeSendHeaders(
|
|
||||||
{ urls: ['https://*/*', 'http://*/*'] },
|
{ urls: ['https://*/*', 'http://*/*'] },
|
||||||
(details, callback) => {
|
(details, callback) => {
|
||||||
const headers = details.requestHeaders;
|
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']) {
|
if (details.resourceType === 'image' && !headers['Referer'] && !headers['referer']) {
|
||||||
try {
|
try {
|
||||||
const u = new URL(details.url);
|
const u = new URL(details.url);
|
||||||
@@ -1216,11 +1227,6 @@ app.whenReady().then(async () => {
|
|||||||
callback({ requestHeaders: headers });
|
callback({ requestHeaders: headers });
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
};
|
|
||||||
|
|
||||||
installRequestHooks(session.defaultSession);
|
|
||||||
installRequestHooks(getProxySession());
|
|
||||||
installRequestHooks(getDirectSession());
|
|
||||||
|
|
||||||
// Apply proxy from config before blocker tries to download filter lists
|
// Apply proxy from config before blocker tries to download filter lists
|
||||||
loadTrustedDomainsFromDisk();
|
loadTrustedDomainsFromDisk();
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "ESH-Media",
|
"name": "ESH-Media",
|
||||||
"version": "1.0.4",
|
"version": "1.0.5",
|
||||||
"private": true,
|
"private": true,
|
||||||
"main": "main.js",
|
"main": "main.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -290,14 +290,22 @@ const Header: React.FC<HeaderProps> = ({ activeApp, setActiveApp, onAppsChange,
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{updateStatus && updateStatus.state !== 'error' && (
|
{updateStatus && (
|
||||||
<div className="update-banner">
|
<div className={`update-banner${updateStatus.state === 'error' ? ' error' : ''}`}>
|
||||||
{updateStatus.state === 'available' && (
|
{updateStatus.state === 'available' && (
|
||||||
<>
|
<>
|
||||||
<span className="update-banner-spinner" />
|
<span className="update-banner-spinner" />
|
||||||
<span>Загружается обновление {updateStatus.version}{updateStatus.currentVersion ? ` (текущая ${updateStatus.currentVersion})` : ''}…</span>
|
<span>Загружается обновление {updateStatus.version}{updateStatus.currentVersion ? ` (текущая ${updateStatus.currentVersion})` : ''}…</span>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
{updateStatus.state === 'error' && (
|
||||||
|
<>
|
||||||
|
<span>Ошибка обновления: {updateStatus.message}</span>
|
||||||
|
<button className="update-banner-btn" onClick={() => window.electron?.checkUpdateNow?.()}>
|
||||||
|
Повторить
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
{updateStatus.state === 'downloading' && (
|
{updateStatus.state === 'downloading' && (
|
||||||
<>
|
<>
|
||||||
<span>Скачивается {updateStatus.version || 'обновление'}: {updateStatus.percent}%</span>
|
<span>Скачивается {updateStatus.version || 'обновление'}: {updateStatus.percent}%</span>
|
||||||
|
|||||||
@@ -1480,6 +1480,11 @@ body {
|
|||||||
}
|
}
|
||||||
.update-banner-close:hover { color: #fff; }
|
.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 {
|
.update-banner-progress {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
height: 4px;
|
height: 4px;
|
||||||
|
|||||||
Reference in New Issue
Block a user