629 lines
24 KiB
JavaScript
629 lines
24 KiB
JavaScript
// =============================================================================
|
||
// Stealth Script — инжектируется в MAIN world на каждой странице ДО загрузки
|
||
// Подменяет все известные маркеры автоматизации браузера
|
||
// =============================================================================
|
||
|
||
(function() {
|
||
'use strict';
|
||
|
||
// -------------------------------------------------------------------------
|
||
// 1. navigator.webdriver = false (подстраховка к Chrome-флагу)
|
||
// -------------------------------------------------------------------------
|
||
try {
|
||
Object.defineProperty(navigator, 'webdriver', {
|
||
get: () => false,
|
||
configurable: true,
|
||
});
|
||
} catch (e) {}
|
||
|
||
// Также удаляем из prototype
|
||
try {
|
||
const proto = Object.getPrototypeOf(navigator);
|
||
if (proto) {
|
||
Object.defineProperty(proto, 'webdriver', {
|
||
get: () => false,
|
||
configurable: true,
|
||
});
|
||
}
|
||
} catch (e) {}
|
||
|
||
// -------------------------------------------------------------------------
|
||
// 2. navigator.plugins — эмулируем стандартные плагины Chrome
|
||
// -------------------------------------------------------------------------
|
||
try {
|
||
const makeMimeType = (type, suffixes, description, plugin) => {
|
||
const mt = Object.create(MimeType.prototype);
|
||
Object.defineProperties(mt, {
|
||
type: { get: () => type },
|
||
suffixes: { get: () => suffixes },
|
||
description: { get: () => description },
|
||
enabledPlugin: { get: () => plugin },
|
||
});
|
||
return mt;
|
||
};
|
||
|
||
const makePlugin = (name, description, filename, mimeTypes) => {
|
||
const p = Object.create(Plugin.prototype);
|
||
const mimes = [];
|
||
Object.defineProperties(p, {
|
||
name: { get: () => name },
|
||
description: { get: () => description },
|
||
filename: { get: () => filename },
|
||
length: { get: () => mimes.length },
|
||
});
|
||
mimeTypes.forEach((mt, i) => {
|
||
const mime = makeMimeType(mt.type, mt.suffixes, mt.description, p);
|
||
mimes.push(mime);
|
||
Object.defineProperty(p, i, { get: () => mime });
|
||
Object.defineProperty(p, mt.type, { get: () => mime });
|
||
});
|
||
return p;
|
||
};
|
||
|
||
const pluginData = [
|
||
{
|
||
name: 'Chrome PDF Plugin',
|
||
description: 'Portable Document Format',
|
||
filename: 'internal-pdf-viewer',
|
||
mimeTypes: [
|
||
{ type: 'application/x-google-chrome-pdf', suffixes: 'pdf', description: 'Portable Document Format' },
|
||
],
|
||
},
|
||
{
|
||
name: 'Chrome PDF Viewer',
|
||
description: '',
|
||
filename: 'mhjfbmdgcfjbbpaeojofohoefgiehjai',
|
||
mimeTypes: [
|
||
{ type: 'application/pdf', suffixes: 'pdf', description: '' },
|
||
],
|
||
},
|
||
{
|
||
name: 'Native Client',
|
||
description: '',
|
||
filename: 'internal-nacl-plugin',
|
||
mimeTypes: [
|
||
{ type: 'application/x-nacl', suffixes: '', description: 'Native Client Executable' },
|
||
{ type: 'application/x-pnacl', suffixes: '', description: 'Portable Native Client Executable' },
|
||
],
|
||
},
|
||
];
|
||
|
||
const plugins = pluginData.map(pd =>
|
||
makePlugin(pd.name, pd.description, pd.filename, pd.mimeTypes)
|
||
);
|
||
|
||
const pluginArray = Object.create(PluginArray.prototype);
|
||
Object.defineProperty(pluginArray, 'length', { get: () => plugins.length });
|
||
plugins.forEach((p, i) => {
|
||
Object.defineProperty(pluginArray, i, { get: () => p });
|
||
Object.defineProperty(pluginArray, p.name, { get: () => p });
|
||
});
|
||
Object.defineProperty(pluginArray, 'refresh', { value: () => {} });
|
||
Object.defineProperty(pluginArray, 'item', {
|
||
value: (index) => plugins[index] || null,
|
||
});
|
||
Object.defineProperty(pluginArray, 'namedItem', {
|
||
value: (name) => plugins.find(p => p.name === name) || null,
|
||
});
|
||
// Итератор
|
||
Object.defineProperty(pluginArray, Symbol.iterator, {
|
||
value: function* () { for (const p of plugins) yield p; },
|
||
});
|
||
|
||
Object.defineProperty(navigator, 'plugins', {
|
||
get: () => pluginArray,
|
||
configurable: true,
|
||
});
|
||
} catch (e) {}
|
||
|
||
// -------------------------------------------------------------------------
|
||
// 3. navigator.mimeTypes — эмулируем
|
||
// -------------------------------------------------------------------------
|
||
try {
|
||
const mimeData = [
|
||
{ type: 'application/pdf', suffixes: 'pdf', description: '' },
|
||
{ type: 'application/x-google-chrome-pdf', suffixes: 'pdf', description: 'Portable Document Format' },
|
||
{ type: 'application/x-nacl', suffixes: '', description: 'Native Client Executable' },
|
||
{ type: 'application/x-pnacl', suffixes: '', description: 'Portable Native Client Executable' },
|
||
];
|
||
|
||
const mimeTypeArray = Object.create(MimeTypeArray.prototype);
|
||
const mimes = mimeData.map(md => {
|
||
const mt = Object.create(MimeType.prototype);
|
||
Object.defineProperties(mt, {
|
||
type: { get: () => md.type },
|
||
suffixes: { get: () => md.suffixes },
|
||
description: { get: () => md.description },
|
||
enabledPlugin: { get: () => navigator.plugins[0] },
|
||
});
|
||
return mt;
|
||
});
|
||
|
||
Object.defineProperty(mimeTypeArray, 'length', { get: () => mimes.length });
|
||
mimes.forEach((m, i) => {
|
||
Object.defineProperty(mimeTypeArray, i, { get: () => m });
|
||
Object.defineProperty(mimeTypeArray, m.type, { get: () => m });
|
||
});
|
||
Object.defineProperty(mimeTypeArray, 'item', {
|
||
value: (index) => mimes[index] || null,
|
||
});
|
||
Object.defineProperty(mimeTypeArray, 'namedItem', {
|
||
value: (name) => mimes.find(m => m.type === name) || null,
|
||
});
|
||
Object.defineProperty(mimeTypeArray, Symbol.iterator, {
|
||
value: function* () { for (const m of mimes) yield m; },
|
||
});
|
||
|
||
Object.defineProperty(navigator, 'mimeTypes', {
|
||
get: () => mimeTypeArray,
|
||
configurable: true,
|
||
});
|
||
} catch (e) {}
|
||
|
||
// -------------------------------------------------------------------------
|
||
// 4. navigator.languages
|
||
// -------------------------------------------------------------------------
|
||
try {
|
||
Object.defineProperty(navigator, 'languages', {
|
||
get: () => ['ru-RU', 'ru', 'en-US', 'en'],
|
||
configurable: true,
|
||
});
|
||
} catch (e) {}
|
||
|
||
// -------------------------------------------------------------------------
|
||
// 5. navigator.pdfViewerEnabled
|
||
// -------------------------------------------------------------------------
|
||
try {
|
||
Object.defineProperty(navigator, 'pdfViewerEnabled', {
|
||
get: () => true,
|
||
configurable: true,
|
||
});
|
||
} catch (e) {}
|
||
|
||
// -------------------------------------------------------------------------
|
||
// 6. window.chrome — эмулируем объект chrome
|
||
// -------------------------------------------------------------------------
|
||
try {
|
||
if (!window.chrome) {
|
||
window.chrome = {};
|
||
}
|
||
if (!window.chrome.runtime) {
|
||
window.chrome.runtime = {
|
||
connect: function() { return { onMessage: { addListener: function() {} }, postMessage: function() {}, onDisconnect: { addListener: function() {} } }; },
|
||
sendMessage: function() {},
|
||
onMessage: { addListener: function() {}, removeListener: function() {} },
|
||
onConnect: { addListener: function() {}, removeListener: function() {} },
|
||
id: undefined,
|
||
};
|
||
}
|
||
// chrome.app
|
||
if (!window.chrome.app) {
|
||
window.chrome.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'; },
|
||
};
|
||
}
|
||
// chrome.csi
|
||
if (!window.chrome.csi) {
|
||
window.chrome.csi = function() {
|
||
return {
|
||
startE: Date.now(),
|
||
onloadT: Date.now(),
|
||
pageT: Math.random() * 1000 + 500,
|
||
tran: 15,
|
||
};
|
||
};
|
||
}
|
||
// chrome.loadTimes
|
||
if (!window.chrome.loadTimes) {
|
||
window.chrome.loadTimes = function() {
|
||
return {
|
||
commitLoadTime: Date.now() / 1000,
|
||
connectionInfo: 'h2',
|
||
finishDocumentLoadTime: Date.now() / 1000,
|
||
finishLoadTime: Date.now() / 1000,
|
||
firstPaintAfterLoadTime: 0,
|
||
firstPaintTime: Date.now() / 1000,
|
||
navigationType: 'Other',
|
||
npnNegotiatedProtocol: 'h2',
|
||
requestTime: Date.now() / 1000 - 0.16,
|
||
startLoadTime: Date.now() / 1000 - 0.16,
|
||
wasAlternateProtocolAvailable: false,
|
||
wasFetchedViaSpdy: true,
|
||
wasNpnNegotiated: true,
|
||
};
|
||
};
|
||
}
|
||
} catch (e) {}
|
||
|
||
// -------------------------------------------------------------------------
|
||
// 7. Permissions.query — подменяем результат для notifications
|
||
// -------------------------------------------------------------------------
|
||
try {
|
||
const originalQuery = window.Permissions.prototype.query;
|
||
window.Permissions.prototype.query = function(parameters) {
|
||
if (parameters.name === 'notifications') {
|
||
return Promise.resolve({ state: Notification.permission });
|
||
}
|
||
return originalQuery.call(this, parameters);
|
||
};
|
||
} catch (e) {}
|
||
|
||
// -------------------------------------------------------------------------
|
||
// 8. navigator.connection — эмулируем NetworkInformation
|
||
// -------------------------------------------------------------------------
|
||
try {
|
||
if (!navigator.connection) {
|
||
Object.defineProperty(navigator, 'connection', {
|
||
get: () => ({
|
||
effectiveType: '4g',
|
||
rtt: 50,
|
||
downlink: 10,
|
||
saveData: false,
|
||
onchange: null,
|
||
addEventListener: function() {},
|
||
removeEventListener: function() {},
|
||
dispatchEvent: function() { return true; },
|
||
}),
|
||
configurable: true,
|
||
});
|
||
}
|
||
} catch (e) {}
|
||
|
||
// -------------------------------------------------------------------------
|
||
// 9. navigator.hardwareConcurrency — реалистичное значение
|
||
// -------------------------------------------------------------------------
|
||
try {
|
||
Object.defineProperty(navigator, 'hardwareConcurrency', {
|
||
get: () => 8,
|
||
configurable: true,
|
||
});
|
||
} catch (e) {}
|
||
|
||
// -------------------------------------------------------------------------
|
||
// 10. navigator.deviceMemory — реалистичное значение
|
||
// -------------------------------------------------------------------------
|
||
try {
|
||
Object.defineProperty(navigator, 'deviceMemory', {
|
||
get: () => 8,
|
||
configurable: true,
|
||
});
|
||
} catch (e) {}
|
||
|
||
// -------------------------------------------------------------------------
|
||
// 11. navigator.maxTouchPoints — десктоп = 0
|
||
// -------------------------------------------------------------------------
|
||
try {
|
||
Object.defineProperty(navigator, 'maxTouchPoints', {
|
||
get: () => 0,
|
||
configurable: true,
|
||
});
|
||
} catch (e) {}
|
||
|
||
// -------------------------------------------------------------------------
|
||
// 12. navigator.platform — Windows
|
||
// -------------------------------------------------------------------------
|
||
try {
|
||
Object.defineProperty(navigator, 'platform', {
|
||
get: () => 'Win32',
|
||
configurable: true,
|
||
});
|
||
} catch (e) {}
|
||
|
||
// -------------------------------------------------------------------------
|
||
// 13. navigator.vendor — Google Inc.
|
||
// -------------------------------------------------------------------------
|
||
try {
|
||
Object.defineProperty(navigator, 'vendor', {
|
||
get: () => 'Google Inc.',
|
||
configurable: true,
|
||
});
|
||
} catch (e) {}
|
||
|
||
// -------------------------------------------------------------------------
|
||
// 14. navigator.appVersion — совпадает с User-Agent
|
||
// -------------------------------------------------------------------------
|
||
try {
|
||
Object.defineProperty(navigator, 'appVersion', {
|
||
get: () => '5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36',
|
||
configurable: true,
|
||
});
|
||
} catch (e) {}
|
||
|
||
// -------------------------------------------------------------------------
|
||
// 15. navigator.userAgent — подстраховка
|
||
// -------------------------------------------------------------------------
|
||
try {
|
||
Object.defineProperty(navigator, 'userAgent', {
|
||
get: () => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36',
|
||
configurable: true,
|
||
});
|
||
} catch (e) {}
|
||
|
||
// -------------------------------------------------------------------------
|
||
// 16. WebGL — подменяем vendor и renderer
|
||
// -------------------------------------------------------------------------
|
||
try {
|
||
const getParameterOrig = WebGLRenderingContext.prototype.getParameter;
|
||
WebGLRenderingContext.prototype.getParameter = function(param) {
|
||
// UNMASKED_VENDOR_WEBGL
|
||
if (param === 0x9245) return 'Google Inc. (NVIDIA)';
|
||
// UNMASKED_RENDERER_WEBGL
|
||
if (param === 0x9246) return 'ANGLE (NVIDIA, NVIDIA GeForce GTX 1660 SUPER Direct3D11 vs_5_0 ps_5_0, D3D11)';
|
||
return getParameterOrig.call(this, param);
|
||
};
|
||
|
||
const getParameter2Orig = WebGL2RenderingContext.prototype.getParameter;
|
||
WebGL2RenderingContext.prototype.getParameter = function(param) {
|
||
if (param === 0x9245) return 'Google Inc. (NVIDIA)';
|
||
if (param === 0x9246) return 'ANGLE (NVIDIA, NVIDIA GeForce GTX 1660 SUPER Direct3D11 vs_5_0 ps_5_0, D3D11)';
|
||
return getParameter2Orig.call(this, param);
|
||
};
|
||
} catch (e) {}
|
||
|
||
// -------------------------------------------------------------------------
|
||
// 17. Скрываем CDP (Chrome DevTools Protocol) следы
|
||
// -------------------------------------------------------------------------
|
||
try {
|
||
// Удаляем __playwright_target__ если есть
|
||
delete window.__playwright_target__;
|
||
delete window.__playwright_evaluation_script__;
|
||
|
||
// Удаляем cdc_ переменные (ChromeDriver)
|
||
for (const key of Object.keys(window)) {
|
||
if (key.match(/^cdc_/) || key.match(/^__cdc_/) || key.match(/^__selenium_/) || key.match(/^__webdriver_/)) {
|
||
try { delete window[key]; } catch (e) {}
|
||
}
|
||
}
|
||
|
||
// Удаляем document.$cdc_ переменные
|
||
for (const key of Object.keys(document)) {
|
||
if (key.match(/^\$cdc_/) || key.match(/^cdc_/)) {
|
||
try { delete document[key]; } catch (e) {}
|
||
}
|
||
}
|
||
} catch (e) {}
|
||
|
||
// -------------------------------------------------------------------------
|
||
// 18. Скрываем Headless-маркеры
|
||
// -------------------------------------------------------------------------
|
||
try {
|
||
// window.outerWidth/outerHeight должны быть > 0
|
||
if (window.outerWidth === 0) {
|
||
Object.defineProperty(window, 'outerWidth', { get: () => 1920 });
|
||
}
|
||
if (window.outerHeight === 0) {
|
||
Object.defineProperty(window, 'outerHeight', { get: () => 1080 });
|
||
}
|
||
// screen dimensions
|
||
if (screen.width === 0 || screen.width < 800) {
|
||
Object.defineProperty(screen, 'width', { get: () => 1920 });
|
||
Object.defineProperty(screen, 'height', { get: () => 1080 });
|
||
Object.defineProperty(screen, 'availWidth', { get: () => 1920 });
|
||
Object.defineProperty(screen, 'availHeight', { get: () => 1040 });
|
||
Object.defineProperty(screen, 'colorDepth', { get: () => 24 });
|
||
Object.defineProperty(screen, 'pixelDepth', { get: () => 24 });
|
||
}
|
||
} catch (e) {}
|
||
|
||
// -------------------------------------------------------------------------
|
||
// 19. Скрываем iframe-детекцию
|
||
// -------------------------------------------------------------------------
|
||
try {
|
||
// Перехватываем создание iframe и патчим contentWindow
|
||
const originalCreateElement = document.createElement.bind(document);
|
||
document.createElement = function(tagName, options) {
|
||
const el = originalCreateElement(tagName, options);
|
||
if (tagName.toLowerCase() === 'iframe') {
|
||
const originalContentWindow = Object.getOwnPropertyDescriptor(HTMLIFrameElement.prototype, 'contentWindow');
|
||
// Не переопределяем, просто убеждаемся что chrome объект есть в iframe
|
||
}
|
||
return el;
|
||
};
|
||
// Сохраняем toString
|
||
document.createElement.toString = () => 'function createElement() { [native code] }';
|
||
} catch (e) {}
|
||
|
||
// -------------------------------------------------------------------------
|
||
// 20. Защита toString() — все подменённые функции должны выглядеть нативно
|
||
// -------------------------------------------------------------------------
|
||
try {
|
||
const nativeToString = Function.prototype.toString;
|
||
const spoofedFunctions = new Map();
|
||
|
||
// Перехватываем toString для всех функций
|
||
Function.prototype.toString = function() {
|
||
if (spoofedFunctions.has(this)) {
|
||
return spoofedFunctions.get(this);
|
||
}
|
||
return nativeToString.call(this);
|
||
};
|
||
|
||
// Регистрируем подменённые функции
|
||
spoofedFunctions.set(Function.prototype.toString, 'function toString() { [native code] }');
|
||
|
||
if (window.Permissions && window.Permissions.prototype.query) {
|
||
spoofedFunctions.set(window.Permissions.prototype.query, 'function query() { [native code] }');
|
||
}
|
||
} catch (e) {}
|
||
|
||
// -------------------------------------------------------------------------
|
||
// 21. Notification.permission — подстраховка
|
||
// -------------------------------------------------------------------------
|
||
try {
|
||
if (Notification.permission === 'denied') {
|
||
Object.defineProperty(Notification, 'permission', {
|
||
get: () => 'default',
|
||
configurable: true,
|
||
});
|
||
}
|
||
} catch (e) {}
|
||
|
||
// -------------------------------------------------------------------------
|
||
// 22. Скрываем автоматизацию в User-Agent Client Hints
|
||
// -------------------------------------------------------------------------
|
||
try {
|
||
if (navigator.userAgentData) {
|
||
Object.defineProperty(navigator, 'userAgentData', {
|
||
get: () => ({
|
||
brands: [
|
||
{ brand: 'Google Chrome', version: '131' },
|
||
{ brand: 'Chromium', version: '131' },
|
||
{ brand: 'Not_A Brand', version: '24' },
|
||
],
|
||
mobile: false,
|
||
platform: 'Windows',
|
||
getHighEntropyValues: function(hints) {
|
||
return Promise.resolve({
|
||
architecture: 'x86',
|
||
bitness: '64',
|
||
brands: [
|
||
{ brand: 'Google Chrome', version: '131' },
|
||
{ brand: 'Chromium', version: '131' },
|
||
{ brand: 'Not_A Brand', version: '24' },
|
||
],
|
||
fullVersionList: [
|
||
{ brand: 'Google Chrome', version: '131.0.6778.86' },
|
||
{ brand: 'Chromium', version: '131.0.6778.86' },
|
||
{ brand: 'Not_A Brand', version: '24.0.0.0' },
|
||
],
|
||
mobile: false,
|
||
model: '',
|
||
platform: 'Windows',
|
||
platformVersion: '15.0.0',
|
||
uaFullVersion: '131.0.6778.86',
|
||
wow64: false,
|
||
});
|
||
},
|
||
toJSON: function() {
|
||
return {
|
||
brands: this.brands,
|
||
mobile: this.mobile,
|
||
platform: this.platform,
|
||
};
|
||
},
|
||
}),
|
||
configurable: true,
|
||
});
|
||
}
|
||
} catch (e) {}
|
||
|
||
// -------------------------------------------------------------------------
|
||
// 23. Скрываем Brave/automation-специфичные API
|
||
// -------------------------------------------------------------------------
|
||
try {
|
||
// Убеждаемся что нет window.domAutomation и window.domAutomationController
|
||
delete window.domAutomation;
|
||
delete window.domAutomationController;
|
||
delete window._phantom;
|
||
delete window.__nightmare;
|
||
delete window._selenium;
|
||
delete window.callPhantom;
|
||
delete window.callSelenium;
|
||
delete window._Recaptcha;
|
||
} catch (e) {}
|
||
|
||
// -------------------------------------------------------------------------
|
||
// 24. Эмуляция Battery API
|
||
// -------------------------------------------------------------------------
|
||
try {
|
||
if (navigator.getBattery) {
|
||
navigator.getBattery = function() {
|
||
return Promise.resolve({
|
||
charging: true,
|
||
chargingTime: 0,
|
||
dischargingTime: Infinity,
|
||
level: 1.0,
|
||
onchargingchange: null,
|
||
onchargingtimechange: null,
|
||
ondischargingtimechange: null,
|
||
onlevelchange: null,
|
||
addEventListener: function() {},
|
||
removeEventListener: function() {},
|
||
dispatchEvent: function() { return true; },
|
||
});
|
||
};
|
||
}
|
||
} catch (e) {}
|
||
|
||
// -------------------------------------------------------------------------
|
||
// 25. Скрываем Automation flags в navigator
|
||
// -------------------------------------------------------------------------
|
||
try {
|
||
// navigator.brave не должен существовать (мы не Brave)
|
||
if (navigator.brave) {
|
||
delete navigator.brave;
|
||
}
|
||
} catch (e) {}
|
||
|
||
// -------------------------------------------------------------------------
|
||
// 26. AudioContext — автоматический resume для обхода autoplay policy
|
||
// -------------------------------------------------------------------------
|
||
try {
|
||
const OriginalAudioContext = window.AudioContext || window.webkitAudioContext;
|
||
if (OriginalAudioContext) {
|
||
const originalResume = OriginalAudioContext.prototype.resume;
|
||
const originalConstructor = OriginalAudioContext;
|
||
|
||
// Патчим конструктор чтобы автоматически resume
|
||
window.AudioContext = function(...args) {
|
||
const ctx = new originalConstructor(...args);
|
||
// Автоматически resume если suspended
|
||
if (ctx.state === 'suspended') {
|
||
try { ctx.resume(); } catch(e) {}
|
||
}
|
||
return ctx;
|
||
};
|
||
window.AudioContext.prototype = originalConstructor.prototype;
|
||
|
||
if (window.webkitAudioContext) {
|
||
window.webkitAudioContext = window.AudioContext;
|
||
}
|
||
}
|
||
} catch (e) {}
|
||
|
||
// -------------------------------------------------------------------------
|
||
// 27. Скрываем факт использования расширений от content scripts detection
|
||
// -------------------------------------------------------------------------
|
||
try {
|
||
// Некоторые антифроды проверяют chrome.runtime.id — если он есть, значит расширение
|
||
// Мы уже определили chrome.runtime выше, но убедимся что id = undefined
|
||
if (window.chrome && window.chrome.runtime) {
|
||
Object.defineProperty(window.chrome.runtime, 'id', {
|
||
get: () => undefined,
|
||
configurable: true,
|
||
});
|
||
}
|
||
} catch (e) {}
|
||
|
||
// -------------------------------------------------------------------------
|
||
// 28. Скрываем sourceURL в стеке вызовов (anti-extension detection)
|
||
// -------------------------------------------------------------------------
|
||
try {
|
||
const originalError = Error;
|
||
const originalPrepareStackTrace = Error.prepareStackTrace;
|
||
|
||
Error.prepareStackTrace = function(error, stack) {
|
||
// Фильтруем фреймы, содержащие chrome-extension://
|
||
const filteredStack = stack.filter(frame => {
|
||
const fileName = frame.getFileName();
|
||
return !fileName || !fileName.startsWith('chrome-extension://');
|
||
});
|
||
|
||
if (originalPrepareStackTrace) {
|
||
return originalPrepareStackTrace(error, filteredStack);
|
||
}
|
||
|
||
return filteredStack.map(frame => ` at ${frame}`).join('\n');
|
||
};
|
||
} catch (e) {}
|
||
|
||
// -------------------------------------------------------------------------
|
||
// Готово — все патчи применены
|
||
// -------------------------------------------------------------------------
|
||
})();
|