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

35
preload.js Normal file
View File

@@ -0,0 +1,35 @@
const { contextBridge, ipcRenderer } = require('electron');
contextBridge.exposeInMainWorld('electron', {
createView: (name, url, imageUrl, zoom, useProxy) =>
ipcRenderer.send('create-view', name, url, imageUrl, zoom, useProxy),
confirm: (text, funcName) => ipcRenderer.send('confirm', text, funcName),
removeView: (name) => ipcRenderer.send('remove-view', name),
hideView: () => ipcRenderer.send('hide-view'),
showView: (name) => ipcRenderer.send('show-view', name),
adjustView: (expanded) => ipcRenderer.send('adjust-view', expanded),
on: (channel, func) => {
const listener = (_event, ...args) => func(...args);
ipcRenderer.on(channel, listener);
return () => ipcRenderer.removeListener(channel, listener);
},
handleAction: (action) => ipcRenderer.send('action', action),
setProxy: (host, port) => ipcRenderer.send('set-proxy', host, port),
expandWithHeader: () => ipcRenderer.send('expandWithHeader'),
collapseWithHeader: () => ipcRenderer.send('collapseWithHeader'),
backwardPage: () => ipcRenderer.send('backwardPage'),
forwardPage: () => ipcRenderer.send('forwardPage'),
refreshPage: () => ipcRenderer.send('refreshPage'),
getCurrentPage: () => ipcRenderer.invoke('get-current-page'),
getPageMeta: () => ipcRenderer.invoke('get-page-meta'),
installUpdate: () => ipcRenderer.invoke('install-update'),
checkUpdateNow: () => ipcRenderer.invoke('check-update-now'),
readConfig: () => ipcRenderer.invoke('read-config'),
writeConfig: (data) => ipcRenderer.send('write-config', data),
searchMovies: (query, sites) => ipcRenderer.invoke('search-movies', query, sites),
searchTmdb: (query, apiKey) => ipcRenderer.invoke('search-tmdb', query, apiKey),
discoverTmdb: (params) => ipcRenderer.invoke('discover-tmdb', params),
toggleKiosk: () => ipcRenderer.invoke('toggle-kiosk'),
isKiosk: () => ipcRenderer.invoke('is-kiosk'),
});