Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 103c2d1e09 | |||
| 82f7fa7545 | |||
| b5e1296a7a |
119
main.js
119
main.js
@@ -40,6 +40,61 @@ const DEFAULT_CONFIG = { apps: [], proxy: { host: '127.0.0.1', port: '7890' }, t
|
||||
let blockerPromise = null;
|
||||
let cachedTrustedDomains = DEFAULT_TRUSTED_DOMAINS;
|
||||
|
||||
// chrome.* spoof: injected via executeJavaScript on every page's dom-ready.
|
||||
// Goal is to look like real Chrome to JS-based "embedded browser" detectors
|
||||
// (Google login, etc.). Cannot fix TLS-fingerprint detection — that's server-side.
|
||||
const CHROME_SPOOF_JS = `(function(){
|
||||
try {
|
||||
if (!window.chrome) window.chrome = {};
|
||||
var c = window.chrome;
|
||||
if (!c.app) c.app = {
|
||||
isInstalled: false,
|
||||
InstallState: { DISABLED: 'disabled', INSTALLED: 'installed', NOT_INSTALLED: 'not_installed' },
|
||||
RunningState: { CANNOT_RUN: 'cannot_run', READY_TO_RUN: 'ready_to_run', RUNNING: 'running' },
|
||||
getDetails: function(){ return null; },
|
||||
getIsInstalled: function(){ return false; },
|
||||
runningState: function(){ return 'cannot_run'; }
|
||||
};
|
||||
if (!c.runtime) c.runtime = {
|
||||
PlatformOs: { MAC:'mac', WIN:'win', ANDROID:'android', CROS:'cros', LINUX:'linux', OPENBSD:'openbsd' },
|
||||
PlatformArch: { ARM:'arm', X86_32:'x86-32', X86_64:'x86-64' },
|
||||
PlatformNaclArch: { ARM:'arm', X86_32:'x86-32', X86_64:'x86-64' },
|
||||
RequestUpdateCheckStatus: { NO_UPDATE:'no_update', THROTTLED:'throttled', UPDATE_AVAILABLE:'update_available' },
|
||||
OnInstalledReason: { CHROME_UPDATE:'chrome_update', INSTALL:'install', SHARED_MODULE_UPDATE:'shared_module_update', UPDATE:'update' },
|
||||
OnRestartRequiredReason: { APP_UPDATE:'app_update', OS_UPDATE:'os_update', PERIODIC:'periodic' },
|
||||
sendMessage: function(){},
|
||||
connect: function(){
|
||||
return {
|
||||
postMessage: function(){}, disconnect: function(){},
|
||||
onDisconnect: { addListener: function(){}, removeListener: function(){} },
|
||||
onMessage: { addListener: function(){}, removeListener: function(){} }
|
||||
};
|
||||
}
|
||||
};
|
||||
if (!c.csi) c.csi = function(){ return { startE: Date.now()-1000, onloadT: Date.now()-500, pageT: 1000, tran: 15 }; };
|
||||
if (!c.loadTimes) c.loadTimes = function(){
|
||||
var t = performance.timing;
|
||||
return {
|
||||
commitLoadTime: t.responseStart/1000, connectionInfo: 'http/1.1',
|
||||
finishDocumentLoadTime: t.domContentLoadedEventEnd/1000,
|
||||
finishLoadTime: (t.loadEventEnd/1000) || 0,
|
||||
firstPaintAfterLoadTime: 0, firstPaintTime: t.responseEnd/1000,
|
||||
navigationType: 'Other', npnNegotiatedProtocol: 'h2',
|
||||
requestTime: t.requestStart/1000, startLoadTime: t.fetchStart/1000,
|
||||
wasAlternateProtocolAvailable: false, wasFetchedViaSpdy: true, wasNpnNegotiated: true
|
||||
};
|
||||
};
|
||||
// navigator.permissions.query: Notification permission must agree with Notification.permission
|
||||
if (navigator.permissions && navigator.permissions.query) {
|
||||
var origQuery = navigator.permissions.query.bind(navigator.permissions);
|
||||
navigator.permissions.query = function(p){
|
||||
if (p && p.name === 'notifications') return Promise.resolve({ state: Notification.permission, onchange: null });
|
||||
return origQuery(p);
|
||||
};
|
||||
}
|
||||
} catch (_) {}
|
||||
})();`;
|
||||
|
||||
function loadTrustedDomainsFromDisk() {
|
||||
try {
|
||||
if (fs.existsSync(CONFIG_PATH)) {
|
||||
@@ -111,7 +166,27 @@ function getBlocker() {
|
||||
|
||||
function enableBlockingInSession(sess) {
|
||||
getBlocker()
|
||||
.then(b => { b.enableBlockingInSession(sess); console.log('[adblock] enabled for session'); })
|
||||
.then(b => {
|
||||
b.enableBlockingInSession(sess);
|
||||
// Remove the cliqz preload script that the blocker just registered on this
|
||||
// session. The preload injects inline <script> elements (via createTextNode +
|
||||
// appendChild) to neutralize anti-adblock scripts, but:
|
||||
// • Strict-CSP sites (kinogo via Cloudflare, etc.) reject inline scripts
|
||||
// without a matching nonce → "Refused to execute inline script".
|
||||
// • Trusted-Types sites (YouTube, Gmail) reject `script.appendChild(text)`
|
||||
// → "HTMLScriptElement was directly modified" (52 errors).
|
||||
// We keep the adblocker's network blocking and CSP filtering (via the still-
|
||||
// attached webRequest hooks), losing only the niche scriptlet/cosmetic-DOM
|
||||
// injection layer that breaks more sites than it helps.
|
||||
const before = sess.getPreloads();
|
||||
const after = before.filter(p => !/adblocker-electron-preload/i.test(p));
|
||||
if (after.length !== before.length) {
|
||||
sess.setPreloads(after);
|
||||
console.log('[adblock] enabled for session (preload script disabled)');
|
||||
} else {
|
||||
console.log('[adblock] enabled for session');
|
||||
}
|
||||
})
|
||||
.catch(e => console.warn('[adblock] failed to enable:', e.message));
|
||||
}
|
||||
|
||||
@@ -588,6 +663,17 @@ ipcMain.on('create-view', async (_event, name, url, imageUrl, _zoom, useProxy) =
|
||||
view.setBounds(getViewBounds());
|
||||
attachDevToolsShortcut(view.webContents);
|
||||
|
||||
// Experimental: spoof chrome.* JS objects on every page so Google's
|
||||
// "embedded browser" detector sees a real-Chrome-shaped global. Runs on
|
||||
// dom-ready which is AFTER <head> scripts, so detection scripts that ran
|
||||
// there have already seen the un-spoofed environment — this fix only
|
||||
// helps if Google's gate is re-checked on form submit / later events.
|
||||
// TLS fingerprint (JA3) is server-side and unaffected; if Google flags us
|
||||
// there, no client-side spoof helps. Best-effort attempt only.
|
||||
view.webContents.on('dom-ready', () => {
|
||||
view.webContents.executeJavaScript(CHROME_SPOOF_JS).catch(() => {});
|
||||
});
|
||||
|
||||
view.webContents.on('did-finish-load', () => {
|
||||
removeLoader();
|
||||
addChild(view);
|
||||
@@ -639,37 +725,30 @@ ipcMain.on('create-view', async (_event, name, url, imageUrl, _zoom, useProxy) =
|
||||
trackNavigation(newUrl);
|
||||
});
|
||||
view.webContents.on('will-redirect', (_e, u) => trackNavigation(u));
|
||||
view.webContents.setWindowOpenHandler(({ url: newUrl, frameName, features }) => {
|
||||
view.webContents.setWindowOpenHandler(({ url: newUrl }) => {
|
||||
let newHostname = '';
|
||||
try { newHostname = new URL(newUrl).hostname; } catch (_) {}
|
||||
|
||||
// Trusted domain → open as real popup BrowserWindow with same session.
|
||||
// This is what OAuth flows need: window.opener.postMessage() works,
|
||||
// popup can close itself when done, parent stays on the original page.
|
||||
// Trusted domain (Google, Yandex, etc.) → navigate IN-PLACE, no popup.
|
||||
// 1.0.1 tried opening a real popup BrowserWindow here for OAuth postMessage
|
||||
// flows — turns out Google specifically detects popup-style embedded
|
||||
// browsers and blocks OAuth ("Возможно, этот браузер небезопасны").
|
||||
// YouTube-style login uses standard redirect flow, so in-place navigation
|
||||
// works AND avoids the popup fingerprint. 1.0.0 behavior, restored.
|
||||
if (newHostname && isTrustedDomain(newHostname)) {
|
||||
return {
|
||||
action: 'allow',
|
||||
overrideBrowserWindowOptions: {
|
||||
width: 520, height: 640,
|
||||
parent: mainWindow,
|
||||
autoHideMenuBar: true,
|
||||
webPreferences: {
|
||||
session: view.webContents.session,
|
||||
contextIsolation: true,
|
||||
nodeIntegration: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
trackNavigation(newUrl);
|
||||
view.webContents.loadURL(newUrl);
|
||||
return { action: 'deny' };
|
||||
}
|
||||
|
||||
// Untrusted cross-domain → ask the user (original behavior).
|
||||
// Untrusted cross-domain → ask the user.
|
||||
if (origHostname && newHostname && newHostname !== origHostname) {
|
||||
pendingNavigate = { view, url: newUrl };
|
||||
setConfirm(`Перейти на "${newHostname}"?`, 'navigate-confirmed');
|
||||
return { action: 'deny' };
|
||||
}
|
||||
|
||||
// Same-origin popup → just navigate the current view.
|
||||
// Same-origin popup → navigate the current view.
|
||||
trackNavigation(newUrl);
|
||||
view.webContents.loadURL(newUrl);
|
||||
return { action: 'deny' };
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ESH-Media",
|
||||
"version": "1.0.7",
|
||||
"version": "1.0.10",
|
||||
"private": true,
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
|
||||
Reference in New Issue
Block a user