Написание кастомных расширений для антидетект-браузеров: Расширяем функционал
Готовы защитить свою цифровую личность?
Выберите тариф и запускайте незаметные профили уже сегодня.
Браузерные расширения — мощный инструмент автоматизации, который часто недооценивают в контексте антидетект-браузеров. В отличие от внешних скриптов автоматизации (Playwright, Selenium), расширение работает изнутри браузера с полным доступом к DOM, сети и браузерным API. Это открывает возможности, недоступные через CDP: перехват WebSocket соединений, модификация fetch на уровне extension, доступ к cookies без обхода CORS, и работа с браузерными уведомлениями.
Это руководство для разработчиков, которые хотят создавать кастомные расширения для своих антидетект-инструментов — от базовой структуры до продвинутых техник.
Основы: архитектура современного расширения (Manifest V3)
Chrome перешёл на Manifest V3 (MV3), и большинство антидетект-браузеров на базе Chromium его поддерживают. Firefox имеет собственную реализацию WebExtensions с незначительными отличиями — расширения, написанные для MV3, как правило работают в обоих с минимальными правками.
Структура минимального расширения:
my-extension/
├── manifest.json
├── background/
│ └── service-worker.js # Background script (MV3 использует Service Worker)
├── content-scripts/
│ └── main.js # Контент-скрипт для работы с DOM страниц
├── popup/
│ ├── popup.html # UI всплывающего окна
│ └── popup.js
└── icons/
└── icon128.png
// manifest.json — минимальная конфигурация
{
"manifest_version": 3,
"name": "Antidetect Helper",
"version": "1.0.0",
"description": "Custom automation helper for antidetect browser",
"permissions": [
"storage",
"tabs",
"activeTab",
"scripting",
"webRequest",
"cookies"
],
"host_permissions": [
"<all_urls>"
],
"background": {
"service_worker": "background/service-worker.js"
},
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["content-scripts/main.js"],
"run_at": "document_start" // Важно: запуск до рендеринга
}
],
"action": {
"default_popup": "popup/popup.html"
}
}
Контент-скрипты: работа с DOM страниц
Контент-скрипты исполняются в контексте веб-страницы, но в изолированном мире — они видят DOM, но не JavaScript переменные страницы (window.myVar будет недоступна).
// content-scripts/main.js
// Запускается при каждой загрузке страницы
(function() {
'use strict';
// Отправляем сообщение background script с информацией о странице
chrome.runtime.sendMessage({
type: 'PAGE_LOADED',
url: window.location.href,
title: document.title,
timestamp: Date.now()
});
// Слушаем команды от background script
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
switch (message.type) {
case 'FILL_FORM':
fillForm(message.data);
sendResponse({ success: true });
break;
case 'EXTRACT_DATA':
const data = extractData(message.selector);
sendResponse({ data });
break;
case 'CLICK_ELEMENT':
clickElement(message.selector);
sendResponse({ success: true });
break;
}
return true; // Для асинхронных ответов
});
})();
function fillForm(formData) {
for (const [selector, value] of Object.entries(formData)) {
const element = document.querySelector(selector);
if (!element) continue;
// Имитируем реальный ввод через events (важно для React/Angular форм)
const nativeInputValueSetter = Object.getOwnPropertyDescriptor(
window.HTMLInputElement.prototype, 'value'
).set;
nativeInputValueSetter.call(element, value);
// Dispatch событий, которые ожидают React/Vue компоненты
element.dispatchEvent(new Event('input', { bubbles: true }));
element.dispatchEvent(new Event('change', { bubbles: true }));
}
}
function clickElement(selector) {
const element = document.querySelector(selector);
if (!element) return;
// Scroll в видимость перед кликом
element.scrollIntoView({ behavior: 'smooth', block: 'center' });
// Небольшая задержка после scroll
setTimeout(() => {
element.click();
}, 300 + Math.random() * 200); // Случайная задержка для естественности
}
function extractData(selector) {
const elements = document.querySelectorAll(selector);
return Array.from(elements).map(el => ({
text: el.textContent.trim(),
html: el.innerHTML,
attributes: Object.fromEntries(
Array.from(el.attributes).map(a => [a.name, a.value])
)
}));
}
Background Service Worker: оркестрация и хранение состояния
Service Worker — центральный координатор расширения. Он управляет состоянием, координирует контент-скрипты и взаимодействует с внешними сервисами.
// background/service-worker.js
// Хранилище задач для разных табов
const taskQueue = new Map();
// При установке расширения
chrome.runtime.onInstalled.addListener(() => {
console.log('Antidetect Helper extension installed');
// Инициализируем хранилище
chrome.storage.local.set({
settings: {
autoFillEnabled: true,
captchaSolverEnabled: false,
proxyRotation: false
},
stats: {
pagesProcessed: 0,
formsFilledOut: 0
}
});
});
// Обработка сообщений от контент-скриптов и popup
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
handleMessage(message, sender, sendResponse);
return true; // Асинхронные ответы
});
async function handleMessage(message, sender, sendResponse) {
switch (message.type) {
case 'PAGE_LOADED':
await onPageLoaded(message, sender.tab);
sendResponse({ ack: true });
break;
case 'GET_SETTINGS':
const settings = await chrome.storage.local.get('settings');
sendResponse(settings);
break;
case 'EXECUTE_TASK':
const result = await executeTaskOnTab(sender.tab.id, message.task);
sendResponse(result);
break;
case 'NOTIFY_DAEMON':
// Отправляем данные в антидетект daemon через native messaging
const response = await sendToDaemon(message.data);
sendResponse(response);
break;
}
}
async function onPageLoaded(pageInfo, tab) {
// Обновляем статистику
const stats = await chrome.storage.local.get('stats');
await chrome.storage.local.set({
stats: { ...stats, pagesProcessed: stats.pagesProcessed + 1 }
});
// Проверяем, есть ли задачи для этой страницы
const pendingTask = taskQueue.get(tab.id);
if (pendingTask && urlMatchesPattern(pageInfo.url, pendingTask.urlPattern)) {
await executeTask(tab.id, pendingTask);
}
}
async function executeTaskOnTab(tabId, task) {
// Инъектируем скрипт напрямую через scripting API
const results = await chrome.scripting.executeScript({
target: { tabId },
func: (taskData) => {
// Этот код выполняется в контексте страницы
return window.__executeTask?.(taskData) || { error: 'Task handler not found' };
},
args: [task]
});
return results[0]?.result;
}
function urlMatchesPattern(url, pattern) {
const regex = new RegExp(pattern.replace(/\*/g, '.*'));
return regex.test(url);
}
Перехват сетевых запросов
Одна из мощнейших возможностей расширений — перехват и модификация HTTP-запросов:
// background/service-worker.js
// Перехват запросов с модификацией заголовков
chrome.webRequest.onBeforeSendHeaders.addListener(
(details) => {
const headers = details.requestHeaders;
// Удаляем подозрительные заголовки, которые могут выдать автоматизацию
const headersToRemove = ['X-Automation', 'X-Playwright', 'X-Puppeteer'];
const filteredHeaders = headers.filter(
h => !headersToRemove.includes(h.name)
);
// Модифицируем Accept-Language для соответствия профилю
const acceptLangIndex = filteredHeaders.findIndex(
h => h.name.toLowerCase() === 'accept-language'
);
if (acceptLangIndex !== -1) {
filteredHeaders[acceptLangIndex].value = 'en-US,en;q=0.9';
}
return { requestHeaders: filteredHeaders };
},
{ urls: ['<all_urls>'] },
['blocking', 'requestHeaders', 'extraHeaders']
);
// Логирование ответов для анализа
chrome.webRequest.onCompleted.addListener(
async (details) => {
// Логируем API-запросы к целевым платформам
const targetDomains = ['facebook.com', 'google.com', 'amazon.com'];
const isTarget = targetDomains.some(d => details.url.includes(d));
if (isTarget && details.statusCode >= 400) {
console.warn(`Request failed: ${details.url} → ${details.statusCode}`);
// Уведомляем daemon о проблеме через native messaging
await sendToDaemon({
type: 'REQUEST_FAILED',
url: details.url,
status: details.statusCode,
tabId: details.tabId
});
}
},
{ urls: ['<all_urls>'] }
);
Native Messaging: связь с антидетект daemon
Native Messaging позволяет расширению общаться с нативным приложением (daemon) на компьютере. Это мощный механизм для двустороннего обмена данными между расширением и Santiago Browser daemon.
Конфигурация на стороне хоста (daemon):
// ~/Library/Application Support/Google/Chrome/NativeMessagingHosts/com.santiago.helper.json
{
"name": "com.santiago.helper",
"description": "Santiago Browser Helper",
"path": "/path/to/santiago-daemon",
"type": "stdio",
"allowed_origins": [
"chrome-extension://YOUR_EXTENSION_ID/"
]
}
В расширении:
// background/service-worker.js
let nativePort = null;
function connectToDaemon() {
nativePort = chrome.runtime.connectNative('com.santiago.helper');
nativePort.onMessage.addListener((message) => {
console.log('Message from daemon:', message);
handleDaemonMessage(message);
});
nativePort.onDisconnect.addListener(() => {
console.log('Disconnected from daemon');
nativePort = null;
// Переподключаемся через 5 секунд
setTimeout(connectToDaemon, 5000);
});
}
async function sendToDaemon(data) {
if (!nativePort) {
connectToDaemon();
await new Promise(resolve => setTimeout(resolve, 1000));
}
return new Promise((resolve, reject) => {
const messageId = Date.now().toString();
const listener = (response) => {
if (response.id === messageId) {
nativePort.onMessage.removeListener(listener);
resolve(response.data);
}
};
nativePort.onMessage.addListener(listener);
nativePort.postMessage({ id: messageId, ...data });
// Timeout
setTimeout(() => {
nativePort.onMessage.removeListener(listener);
reject(new Error('Daemon response timeout'));
}, 10000);
});
}
function handleDaemonMessage(message) {
switch (message.type) {
case 'EXECUTE_ACTION':
// Daemon просит расширение что-то сделать на текущей странице
executeActionInActiveTab(message.action);
break;
case 'GET_PAGE_DATA':
// Daemon запрашивает данные со страницы
extractAndSendPageData(message.tabId, message.selectors);
break;
}
}
// Инициализируем соединение при старте
connectToDaemon();
Обход ограничений Content Security Policy
Многие сайты используют строгие CSP, которые блокируют выполнение inline скриптов. Расширения имеют особый статус и могут обходить ряд CSP ограничений:
// content-scripts/csp-bypass.js
// Расширения могут инъектировать скрипты через chrome.scripting API
// даже на сайтах с строгим CSP
// Вместо inline скриптов используем scripting.executeScript
chrome.scripting.executeScript({
target: { tabId: currentTabId },
func: () => {
// Этот код выполняется в контексте страницы
// CSP страницы применяется к inline скриптам, но не к extension scripts
// Можем взаимодействовать с window объектом
window.dispatchEvent(new CustomEvent('extension-ready', {
detail: { version: '1.0.0' }
}));
}
});
// Инъекция через файл (более надёжно при строгом CSP)
chrome.scripting.executeScript({
target: { tabId: currentTabId },
files: ['injected-scripts/page-helper.js']
});
Автоматизация конкретных сценариев
Автоматическое заполнение форм регистрации
// automation/form-filler.js
class FormFiller {
constructor(profileData) {
this.profile = profileData;
this.typingDelay = { min: 50, max: 150 }; // мс между символами
}
async fillRegistrationForm() {
const fieldMappings = {
// Распространённые селекторы полей регистрации
name: [
'input[name="name"]',
'input[name="full_name"]',
'#name',
'input[placeholder*="name" i]'
],
email: [
'input[type="email"]',
'input[name="email"]',
'#email'
],
password: [
'input[type="password"]',
'input[name="password"]'
]
};
for (const [field, selectors] of Object.entries(fieldMappings)) {
const element = this.findElement(selectors);
if (element && this.profile[field]) {
await this.typeInField(element, this.profile[field]);
}
}
}
findElement(selectors) {
for (const selector of selectors) {
const el = document.querySelector(selector);
if (el && this.isVisible(el)) return el;
}
return null;
}
isVisible(element) {
const style = window.getComputedStyle(element);
return style.display !== 'none' &&
style.visibility !== 'hidden' &&
element.offsetParent !== null;
}
async typeInField(element, value) {
element.focus();
element.click();
// Очищаем текущее значение
element.select();
document.execCommand('delete');
// Вводим посимвольно с задержками
for (const char of value) {
await this.randomDelay(this.typingDelay.min, this.typingDelay.max);
// Имитируем keyboard events
element.dispatchEvent(new KeyboardEvent('keydown', { key: char, bubbles: true }));
element.dispatchEvent(new KeyboardEvent('keypress', { key: char, bubbles: true }));
// Добавляем символ
const nativeSetter = Object.getOwnPropertyDescriptor(
window.HTMLInputElement.prototype, 'value'
).set;
nativeSetter.call(element, element.value + char);
element.dispatchEvent(new Event('input', { bubbles: true }));
element.dispatchEvent(new KeyboardEvent('keyup', { key: char, bubbles: true }));
}
element.dispatchEvent(new Event('change', { bubbles: true }));
element.blur();
}
randomDelay(min, max) {
const delay = min + Math.random() * (max - min);
return new Promise(resolve => setTimeout(resolve, delay));
}
}
Мониторинг изменений страницы
// content-scripts/page-monitor.js
class PageMonitor {
constructor(config) {
this.config = config;
this.observers = [];
}
watchForElement(selector, callback, timeout = 30000) {
return new Promise((resolve, reject) => {
const timer = setTimeout(() => {
observer.disconnect();
reject(new Error(`Element ${selector} not found within ${timeout}ms`));
}, timeout);
// Сначала проверяем, есть ли уже элемент
const existing = document.querySelector(selector);
if (existing) {
clearTimeout(timer);
resolve(existing);
return;
}
// Иначе наблюдаем за DOM
const observer = new MutationObserver((mutations) => {
const element = document.querySelector(selector);
if (element) {
clearTimeout(timer);
observer.disconnect();
callback?.(element);
resolve(element);
}
});
observer.observe(document.body, {
childList: true,
subtree: true
});
this.observers.push(observer);
});
}
watchForNavigation(urlPattern, callback) {
let lastUrl = window.location.href;
const observer = new MutationObserver(() => {
if (window.location.href !== lastUrl) {
lastUrl = window.location.href;
if (urlPattern.test(lastUrl)) {
callback(lastUrl);
}
}
});
observer.observe(document, { subtree: true, childList: true });
this.observers.push(observer);
}
cleanup() {
this.observers.forEach(obs => obs.disconnect());
this.observers = [];
}
}
// Использование
const monitor = new PageMonitor({});
monitor.watchForElement('#checkout-complete', () => {
chrome.runtime.sendMessage({
type: 'CHECKOUT_COMPLETED',
url: window.location.href
});
});
Установка расширения в антидетект-профиль
Разные антидетект-браузеры имеют разные методы установки кастомных расширений в профили.
Для браузеров на базе Chromium — расширение устанавливается в unpacked режиме через директорию профиля:
// Через CDP (DevTools Protocol) после подключения к профилю
const { Protocol } = require('devtools-protocol');
async function installExtension(cdpClient, extensionPath) {
// Включаем Extensions domain
await cdpClient.Extensions.enable();
// Загружаем расширение
const { id } = await cdpClient.Extensions.loadUnpacked({
path: extensionPath
});
console.log(`Extension installed with ID: ${id}`);
return id;
}
Для постоянной установки — скопируйте папку расширения в директорию профиля:
# Структура Chrome профиля
# ~/.config/google-chrome/Profile 1/Extensions/{extension-id}/{version}/
# Копируем наше расширение
cp -r ./my-extension ~/.config/your-antidetect/profiles/profile-123/Extensions/your-extension-id/1.0.0/
# Перезапускаем профиль для применения
Отладка расширений
При разработке кастомных расширений отладка — ежедневная задача.
Доступ к DevTools расширения: в Chrome/Chromium откройте chrome://extensions/, включите “Developer mode”, нажмите “background page” для доступа к DevTools Service Worker.
Логирование в contend script видно в DevTools страницы (F12), а не в DevTools расширения.
// Утилита для унифицированного логирования
const ExtLogger = {
context: 'content-script', // или 'service-worker', 'popup'
log(message, data = {}) {
const timestamp = new Date().toISOString();
console.log(`[${this.context}][${timestamp}] ${message}`, data);
// Отправляем в background для централизованного логирования
if (this.context !== 'service-worker') {
chrome.runtime.sendMessage({
type: 'LOG',
context: this.context,
message,
data,
timestamp
}).catch(() => {}); // Игнорируем ошибки если background не доступен
}
},
error(message, error) {
console.error(`[${this.context}] ${message}`, error);
chrome.runtime.sendMessage({
type: 'ERROR',
context: this.context,
message,
error: error?.message,
stack: error?.stack
}).catch(() => {});
}
};
Итог
Кастомные расширения — самый нативный способ автоматизации браузерных задач. Они работают с теми же API, что и реальный пользователь, что делает их значительно сложнее для детектирования, чем внешняя автоматизация через CDP.
Инвестиция в разработку кастомного расширения оправдана, когда: у вас есть повторяющиеся сложные задачи в браузере, внешняя автоматизация детектируется целевыми платформами, или вам нужно глубокое взаимодействие с браузерными API (cookies, notifications, native messaging).
Современный Manifest V3, при всех его ограничениях по сравнению с MV2, всё ещё даёт достаточно возможностей для построения мощной автоматизации. Понимание архитектуры (контент-скрипт, service worker, popup) и правильное использование messaging API позволяет строить расширения любой сложности.
Готовы защитить свою цифровую личность?
Выберите тариф и запускайте незаметные профили уже сегодня.
Получайте 15% пожизненную комиссию с каждого реферала.
Стать партнёром →