ESH-Media v1.0.11 — kiosk media browser for elderly users

Electron-based kiosk desktop app: large-tile launcher for YouTube, RuTube,
movie sites and Google services, designed for low-tech grandparent use.

Features:
  - WebContentsView-per-app tabbed browsing with session persistence
  - per-app proxy routing (Clash/V2Ray friendly, useProxy flag)
  - cliqz-electron adblocker with whitelist for OAuth/integrity domains
  - TMDB-backed movie search across kinogo / hdrezka / filmix
  - bookmark posters auto-fetched from og:image / JSON-LD
  - electron-updater wired to Gitea releases API (latest.yml + .blockmap)
  - cross-domain navigation confirms via custom WebContentsView dialogs
  - kiosk window with hidden menu, Ctrl+Shift+I devtools shortcut
  - Trusted Types disabled engine-wide so adblocker scriptlets work on YouTube

Google OAuth handling (the hard-won part):
  Google's anti-abuse JS rejects WebContentsView + custom session settings
  as "embedded browser". So accounts.google.com opens in a top-level
  BrowserWindow popup in a dedicated persist:google-login partition that
  we never call setProxy/setUserAgent on — it inherits Windows system
  proxy and the default Electron-tagged UA, both of which Google accepts.
  After login, .google.com/.youtube.com cookies migrate into the parent
  view's session and the view reloads to pick up the logged-in state.

Session restore: only the last-active tab attaches to the window; other
tabs load silently in the background and become instantly visible when
the user clicks them in the sidebar.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-17 00:46:02 +03:00
commit 1c7bb75a05
69 changed files with 12035 additions and 0 deletions

38
scripts/start-electron.js Normal file
View File

@@ -0,0 +1,38 @@
// Waits for Vite dev server, then launches Electron with ELECTRON_RUN_AS_NODE
// removed from environment (any value of that variable causes Electron to run
// as plain Node.js instead of a GUI app).
const { spawn } = require('child_process');
const http = require('http');
const electronPath = require('electron');
const VITE_URL = 'http://localhost:5173';
const POLL_INTERVAL = 500;
const MAX_WAIT_MS = 60000;
function waitForVite(url, timeout) {
return new Promise((resolve, reject) => {
const deadline = Date.now() + timeout;
const check = () => {
http.get(url, (res) => {
res.resume();
resolve();
}).on('error', () => {
if (Date.now() >= deadline) return reject(new Error('Timed out waiting for ' + url));
setTimeout(check, POLL_INTERVAL);
});
};
check();
});
}
console.log('Waiting for Vite at', VITE_URL, '...');
waitForVite(VITE_URL, MAX_WAIT_MS).then(() => {
console.log('Vite ready — starting Electron');
const env = { ...process.env };
delete env.ELECTRON_RUN_AS_NODE;
const child = spawn(electronPath, ['.'], { stdio: 'inherit', env });
child.on('close', code => process.exit(code ?? 0));
}).catch(err => {
console.error(err.message);
process.exit(1);
});