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

· 14 хв читання
розширення браузера manifest v3 антидетект автоматизація javascript chrome extension
Написання кастомних розширень для антидетект-браузерів: Розширюємо функціонал

Готові захистити свою цифрову особистість?

Оберіть тариф і запускайте непомітні профілі вже сьогодні.

Почати

Вбудований функціонал антидетект-браузера завжди має межі. Автоматизація специфічних UI-сценаріїв, захоплення даних зі сторінок, модифікація запитів у реальному часі, обхід перевірок — все це можна реалізувати через кастомні браузерні розширення, що встановлюються в кожен профіль.

На відміну від зовнішніх скриптів автоматизації (Playwright, Puppeteer), розширення виконуються всередині браузерного контексту, що дає доступ до API, недоступних ззовні. При цьому правильно написане розширення не виявляється системами захисту як “бот” — воно поводиться як частина самого браузера.

Коли потрібне кастомне розширення

Розширення виправдане, коли:

Потрібен доступ до API, заблокованих ззовніwindow.crypto, IndexedDB, ServiceWorker, WebAuthn — ці API недоступні з Playwright через CDP або доступні з обмеженнями. Розширення має повний доступ до них із privileged контексту.

Потрібна інжекція скриптів до завантаження сторінки — Playwright може інжектувати скрипти через page.addInitScript(), але це відбувається в main_world і виявляється деякими системами. Розширення з run_at: document_start виконується раніше, у privileged контексті.

Потрібна модифікація запитівchrome.webRequest або chrome.declarativeNetRequest дозволяють перехоплювати, модифікувати або блокувати мережеві запити прямо в браузері. Це ефективніше, ніж проксі-рівень перехоплення.

Потрібна постійна присутність у фоні — service worker розширення живе незалежно від активних вкладок і може реагувати на події браузера.

Потрібна автоматизація UI, що виглядає людяно — деякі сайти виявляють автоматизацію через CDP-телеметрію. JavaScript, виконаний із контексту розширення, залишає інший слід, ніж CDP-команди.

Manifest V3: сучасний стандарт

Всі нові розширення мають використовувати Manifest V3 (MV3). MV3 прийшов на зміну MV2 і відрізняється:

  • Service Worker замість фонових сторінок (Background Pages)
  • declarativeNetRequest замість webRequest з блокуванням (але webRequest для читання залишився)
  • Строгіший CSP
  • Обмеження на динамічне виконання коду (eval, new Function)

Базова структура manifest.json:

{
  "manifest_version": 3,
  "name": "My Antidetect Helper",
  "version": "1.0.0",
  "description": "Custom automation helper",
  
  "permissions": [
    "storage",
    "activeTab",
    "scripting",
    "webRequest",
    "cookies"
  ],
  
  "host_permissions": [
    "https://*.target-site.com/*",
    "https://api.target-site.com/*"
  ],
  
  "background": {
    "service_worker": "background.js",
    "type": "module"
  },
  
  "content_scripts": [
    {
      "matches": ["https://*.target-site.com/*"],
      "js": ["content.js"],
      "run_at": "document_start",
      "world": "MAIN"
    }
  ],
  
  "action": {
    "default_popup": "popup.html"
  }
}

Content Scripts: інжекція в сторінку

Content scripts — основний інструмент взаємодії зі сторінкою. Вони виконуються у контексті сторінки, але з ізольованим JavaScript scope.

Два “world”:

ISOLATED (default) — скрипт виконується в ізольованому scope. Має доступ до DOM, але не може змінювати window об’єкт, що бачить сама сторінка. Безпечний для операцій з DOM.

MAIN — скрипт виконується в тому самому scope, що і сторінковий JavaScript. Може читати і змінювати window об’єкт, бачить змінні сторінки. Потрібний для патчингу API або читання сторінкових даних.

// content.js (world: MAIN, run_at: document_start)

// Патч API до завантаження сторінки - сайт не побачить "оригінальний" API
const originalFetch = window.fetch;
window.fetch = async function(resource, options = {}) {
  // Додаємо заголовок до кожного запиту
  options.headers = {
    ...options.headers,
    'X-Custom-Header': 'value'
  };
  
  const response = await originalFetch(resource, options);
  
  // Перехоплюємо відповідь якщо потрібно
  if (resource.toString().includes('/api/check')) {
    const body = await response.clone().json();
    // Відправляємо дані в background script
    chrome.runtime.sendMessage({ type: 'api_response', data: body });
  }
  
  return response;
};

Background Service Worker: управляючий центр

Service worker обробляє повідомлення від content scripts, виконує запити від імені розширення та управляє станом.

// background.js

// Обробка повідомлень від content scripts
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  if (message.type === 'api_response') {
    handleApiResponse(message.data, sender.tab.id);
    sendResponse({ received: true });
  }
  
  if (message.type === 'execute_action') {
    executeAction(message.action, sender.tab.id)
      .then(result => sendResponse({ success: true, result }))
      .catch(err => sendResponse({ success: false, error: err.message }));
    return true; // Async response
  }
});

// Відповідь на команди від зовнішніх скриптів через Native Messaging або CDP
chrome.runtime.onConnectExternal?.addListener((port) => {
  port.onMessage.addListener((message) => {
    // Обробка зовнішніх команд
  });
});

async function executeAction(action, tabId) {
  switch (action.type) {
    case 'click':
      await chrome.scripting.executeScript({
        target: { tabId },
        func: (selector) => document.querySelector(selector)?.click(),
        args: [action.selector]
      });
      break;
      
    case 'fill':
      await chrome.scripting.executeScript({
        target: { tabId },
        func: (selector, value) => {
          const el = document.querySelector(selector);
          if (el) {
            el.value = value;
            el.dispatchEvent(new Event('input', { bubbles: true }));
            el.dispatchEvent(new Event('change', { bubbles: true }));
          }
        },
        args: [action.selector, action.value]
      });
      break;
  }
}

Перехоплення мережевих запитів

Один із найпотужніших випадків використання — модифікація запитів у реальному часі.

// background.js - перехоплення запитів

// Читання тіла запиту (webRequest може лише читати заголовки)
// Для повного перехоплення тіла потрібен ServiceWorker або content script

chrome.webRequest.onBeforeSendHeaders.addListener(
  (details) => {
    const headers = details.requestHeaders;
    
    // Видаляємо підозрілі заголовки
    const filteredHeaders = headers.filter(h => 
      !['x-automation', 'x-playwright'].includes(h.name.toLowerCase())
    );
    
    // Додаємо або замінюємо заголовки
    filteredHeaders.push({
      name: 'Accept-Language',
      value: 'en-US,en;q=0.9'
    });
    
    return { requestHeaders: filteredHeaders };
  },
  { urls: ["https://target-site.com/*"] },
  ["blocking", "requestHeaders", "extraHeaders"]
);

// Читання відповіді (тільки заголовки без blocking)
chrome.webRequest.onHeadersReceived.addListener(
  (details) => {
    // Аналіз заголовків відповіді
    const setCookie = details.responseHeaders?.find(h => 
      h.name.toLowerCase() === 'set-cookie'
    );
    if (setCookie) {
      console.log('Cookie set:', setCookie.value);
    }
  },
  { urls: ["https://target-site.com/api/*"] },
  ["responseHeaders"]
);

Обмін даними між профілями через extension storage

Для координованої роботи кількох профілів розширення може зберігати стан.

// Збереження стану профілю
async function saveProfileState(profileId, state) {
  await chrome.storage.local.set({
    [`profile_${profileId}`]: {
      ...state,
      updatedAt: Date.now()
    }
  });
}

// Читання стану
async function getProfileState(profileId) {
  const result = await chrome.storage.local.get(`profile_${profileId}`);
  return result[`profile_${profileId}`] || null;
}

// Синхронізоване сховище (якщо Chrome Account прив'язаний - синхронізується між пристроями)
// Для анонімних профілів використовуйте лише local, не sync

Автоматизація через розширення: реалістична взаємодія

Розширення може симулювати реалістичні дії користувача, що виглядають більш природно, ніж CDP-команди.

// content.js - реалістичні дії

function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

// Реалістичне введення тексту посимвольно
async function humanType(element, text) {
  element.focus();
  
  for (const char of text) {
    // Варіація затримки між символами (30-150ms)
    const delay = 30 + Math.random() * 120;
    await sleep(delay);
    
    // Dispatch keydown → keypress → input → keyup
    const keydownEvent = new KeyboardEvent('keydown', {
      key: char, bubbles: true, cancelable: true
    });
    element.dispatchEvent(keydownEvent);
    
    // Вставка символу
    element.value += char;
    element.dispatchEvent(new InputEvent('input', {
      data: char, inputType: 'insertText', bubbles: true
    }));
    
    const keyupEvent = new KeyboardEvent('keyup', {
      key: char, bubbles: true
    });
    element.dispatchEvent(keyupEvent);
  }
  
  // Фінальний change event
  element.dispatchEvent(new Event('change', { bubbles: true }));
}

// Реалістичний клік з рухом миші
async function humanClick(element) {
  const rect = element.getBoundingClientRect();
  
  // Клік в середині елемента з невеликим відхиленням
  const x = rect.left + rect.width * (0.4 + Math.random() * 0.2);
  const y = rect.top + rect.height * (0.4 + Math.random() * 0.2);
  
  // Mouseover → mousemove → mousedown → mouseup → click
  ['mouseover', 'mousemove'].forEach(type => {
    element.dispatchEvent(new MouseEvent(type, {
      clientX: x, clientY: y, bubbles: true
    }));
  });
  
  await sleep(20 + Math.random() * 50);
  
  element.dispatchEvent(new MouseEvent('mousedown', {
    clientX: x, clientY: y, bubbles: true, button: 0
  }));
  
  await sleep(50 + Math.random() * 100);
  
  element.dispatchEvent(new MouseEvent('mouseup', {
    clientX: x, clientY: y, bubbles: true, button: 0
  }));
  
  element.dispatchEvent(new MouseEvent('click', {
    clientX: x, clientY: y, bubbles: true, button: 0
  }));
}

Установка в профілі антидетект-браузера

Розширення встановлюється в браузерний профіль через завантаження packed або unpacked розширення.

Unpacked (для розробки і тестування):

  1. Відкрийте chrome://extensions або about:debugging (Firefox)
  2. Увімкніть “Developer mode”
  3. “Load unpacked” → вкажіть папку з manifest.json

Packed розширення (.crx для Chrome, .xpi для Firefox):

  • Chrome: chrome.exe --load-extension=path/to/extension при запуску
  • Firefox: підписане розширення або дозволений devmode

Для антидетект-браузерів — більшість підтримують завантаження розширень через налаштування профілю. Шлях до теки розширення або .crx файл вказується при створенні або редагуванні профілю.

Для автоматичного розгортання розширення у всіх профілях через API:

# Python скрипт для масового розгортання
import requests
import os

ANTIDETECT_API = "http://localhost:7891/api"
EXTENSION_PATH = "/opt/my-extension"

profiles = requests.get(f"{ANTIDETECT_API}/profiles").json()

for profile in profiles['data']:
    profile_id = profile['id']
    
    # Оновлення профілю з шляхом до розширення
    requests.patch(f"{ANTIDETECT_API}/profiles/{profile_id}", json={
        "extensions": [EXTENSION_PATH]
    })
    
print(f"Extension deployed to {len(profiles['data'])} profiles")

Безпека: що потрібно знати

Розширення — це виконуваний код з широким доступом до браузера. Кілька правил безпеки:

Мінімальні дозволи — запитуйте тільки ті permissions, що реально потрібні. Зайві дозволи привертають увагу систем виявлення і розширяють attack surface.

Не зберігайте секрети в коді розширення — розширення може бути видобуте з профілю. Секрети передавайте через chrome.storage.local після завантаження.

CSP для popup та options сторінок — не використовуйте inline JS в HTML.

Не використовуйте eval — це заблоковано в MV3 і є ознакою небезпечного коду.

Кастомні розширення — потужний інструмент, що відкриває можливості недоступні через зовнішню автоматизацію. Правильно спроектоване розширення стає невід’ємною частиною операційного стека для масштабних задач із антидетект-браузерами.

Готові захистити свою цифрову особистість?

Оберіть тариф і запускайте непомітні профілі вже сьогодні.

Отримуйте 15% довічну комісію з кожного реферала.

Стати партнером →