Написание кастомных расширений для антидетект-браузеров: Расширяем функционал

· 13 мин чтения
расширения браузера manifest v3 антидетект автоматизация разработка chrome extension
Написание кастомных расширений для антидетект-браузеров: Расширяем функционал

Готовы защитить свою цифровую личность?

Выберите тариф и запускайте незаметные профили уже сегодня.

Начать

Браузерные расширения — мощный инструмент автоматизации, который часто недооценивают в контексте антидетект-браузеров. В отличие от внешних скриптов автоматизации (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% пожизненную комиссию с каждого реферала.

Стать партнёром →