browser-agent-mcp
Version:
Chrome extension and MCP server for comprehensive browser automation with AI agents
314 lines (312 loc) • 11.3 kB
JavaScript
/******/ (() => { // webpackBootstrap
/******/ "use strict";
/*!****************************************!*\
!*** ./src/extension/content/index.ts ***!
\****************************************/
// Content script for DOM manipulation and monitoring
// Runs in the context of web pages
class ContentScript {
constructor() {
this.isInitialized = false;
this.initialize();
}
initialize() {
if (this.isInitialized)
return;
console.log("Browser Agent MCP: Content script initializing...");
// Set up message listeners
this.setupMessageListeners();
// Monitor console logs
this.setupConsoleMonitoring();
// Monitor network requests
this.setupNetworkMonitoring();
this.isInitialized = true;
console.log("Browser Agent MCP: Content script initialized");
}
setupMessageListeners() {
// Listen for messages from background script
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
this.handleMessage(message, sender, sendResponse);
return true; // Keep message channel open for async response
});
}
async handleMessage(message, sender, sendResponse) {
try {
switch (message.type) {
case "DOM_QUERY":
const result = await this.handleDOMQuery(message.payload);
sendResponse({ success: true, data: result });
break;
case "DOM_CLICK":
await this.handleDOMClick(message.payload);
sendResponse({ success: true });
break;
case "DOM_TYPE":
await this.handleDOMType(message.payload);
sendResponse({ success: true });
break;
case "GET_PAGE_INFO":
const pageInfo = await this.getPageInfo();
sendResponse({ success: true, data: pageInfo });
break;
default:
sendResponse({ success: false, error: "Unknown message type" });
}
}
catch (error) {
console.error("Content script error:", error);
sendResponse({ success: false, error: error.message });
}
}
async handleDOMQuery(payload) {
const { selector, action = "query" } = payload;
switch (action) {
case "query":
return this.queryElements(selector);
case "getText":
return this.getElementText(selector);
case "getAttributes":
return this.getElementAttributes(selector);
case "getHTML":
return this.getElementHTML(selector);
default:
throw new Error(`Unknown DOM action: ${action}`);
}
}
queryElements(selector) {
try {
const elements = document.querySelectorAll(selector);
return Array.from(elements).map((el, index) => ({
index,
tagName: el.tagName.toLowerCase(),
textContent: el.textContent?.trim() || "",
innerHTML: el.innerHTML,
outerHTML: el.outerHTML,
attributes: this.getElementAttributesMap(el),
boundingRect: el.getBoundingClientRect(),
visible: this.isElementVisible(el),
clickable: this.isElementClickable(el),
}));
}
catch (error) {
throw new Error(`DOM query failed: ${error.message}`);
}
}
getElementText(selector) {
const element = document.querySelector(selector);
if (!element)
throw new Error(`Element not found: ${selector}`);
return element.textContent?.trim() || "";
}
getElementAttributes(selector) {
const element = document.querySelector(selector);
if (!element)
throw new Error(`Element not found: ${selector}`);
return this.getElementAttributesMap(element);
}
getElementHTML(selector) {
const element = document.querySelector(selector);
if (!element)
throw new Error(`Element not found: ${selector}`);
return {
innerHTML: element.innerHTML,
outerHTML: element.outerHTML,
};
}
getElementAttributesMap(element) {
const attributes = {};
for (let i = 0; i < element.attributes.length; i++) {
const attr = element.attributes[i];
attributes[attr.name] = attr.value;
}
return attributes;
}
isElementVisible(element) {
const rect = element.getBoundingClientRect();
const style = window.getComputedStyle(element);
return (rect.width > 0 &&
rect.height > 0 &&
style.visibility !== "hidden" &&
style.display !== "none" &&
parseFloat(style.opacity) > 0);
}
isElementClickable(element) {
const clickableTags = ["a", "button", "input", "select", "textarea"];
const tagName = element.tagName.toLowerCase();
return (clickableTags.includes(tagName) ||
element.hasAttribute("onclick") ||
element.hasAttribute("role") ||
window.getComputedStyle(element).cursor === "pointer");
}
async handleDOMClick(payload) {
const { selector } = payload;
const element = document.querySelector(selector);
if (!element) {
throw new Error(`Element not found: ${selector}`);
}
// Scroll element into view
element.scrollIntoView({ behavior: "smooth", block: "center" });
// Wait a bit for scroll to complete
await new Promise((resolve) => setTimeout(resolve, 100));
// Click the element
element.click();
}
async handleDOMType(payload) {
const { selector, text, clear = true } = payload;
const element = document.querySelector(selector);
if (!element) {
throw new Error(`Element not found: ${selector}`);
}
// Focus the element
element.focus();
// Clear existing text if requested
if (clear) {
element.value = "";
}
// Type the text
element.value += text;
// Trigger input events
element.dispatchEvent(new Event("input", { bubbles: true }));
element.dispatchEvent(new Event("change", { bubbles: true }));
}
async getPageInfo() {
return {
url: window.location.href,
title: document.title,
readyState: document.readyState,
timestamp: Date.now(),
viewport: {
width: window.innerWidth,
height: window.innerHeight,
},
scroll: {
x: window.scrollX,
y: window.scrollY,
},
forms: this.getForms(),
links: this.getLinks(),
images: this.getImages(),
};
}
getForms() {
const forms = document.querySelectorAll("form");
return Array.from(forms).map((form, index) => ({
index,
action: form.action,
method: form.method,
fields: Array.from(form.elements).map((el) => ({
name: el.name,
type: el.type,
tagName: el.tagName.toLowerCase(),
})),
}));
}
getLinks() {
const links = document.querySelectorAll("a[href]");
return Array.from(links).map((link, index) => ({
index,
href: link.href,
text: link.textContent?.trim() || "",
target: link.target,
}));
}
getImages() {
const images = document.querySelectorAll("img");
return Array.from(images).map((img, index) => ({
index,
src: img.src,
alt: img.alt,
width: img.width,
height: img.height,
}));
}
setupConsoleMonitoring() {
// Override console methods to capture logs
const originalLog = console.log;
const originalError = console.error;
const originalWarn = console.warn;
const originalInfo = console.info;
const sendConsoleMessage = (level, args) => {
chrome.runtime
.sendMessage({
type: "CONSOLE_LOG",
payload: {
level,
message: args
.map((arg) => typeof arg === "object" ? JSON.stringify(arg) : String(arg))
.join(" "),
timestamp: Date.now(),
},
})
.catch(() => {
// Ignore errors if background script is not available
});
};
console.log = (...args) => {
originalLog.apply(console, args);
sendConsoleMessage("log", args);
};
console.error = (...args) => {
originalError.apply(console, args);
sendConsoleMessage("error", args);
};
console.warn = (...args) => {
originalWarn.apply(console, args);
sendConsoleMessage("warn", args);
};
console.info = (...args) => {
originalInfo.apply(console, args);
sendConsoleMessage("info", args);
};
}
setupNetworkMonitoring() {
// Override fetch to monitor network requests
const originalFetch = window.fetch;
window.fetch = async (...args) => {
const startTime = Date.now();
const url = typeof args[0] === "string" ? args[0] : args[0].url;
const method = args[1]?.method || "GET";
try {
const response = await originalFetch.apply(window, args);
chrome.runtime
.sendMessage({
type: "NETWORK_REQUEST",
payload: {
url,
method,
status: response.status,
timestamp: startTime,
duration: Date.now() - startTime,
},
})
.catch(() => {
// Ignore errors if background script is not available
});
return response;
}
catch (error) {
chrome.runtime
.sendMessage({
type: "NETWORK_REQUEST",
payload: {
url,
method,
status: 0,
error: error.message,
timestamp: startTime,
duration: Date.now() - startTime,
},
})
.catch(() => {
// Ignore errors if background script is not available
});
throw error;
}
};
}
}
// Initialize content script
new ContentScript();
/******/ })()
;
//# sourceMappingURL=content-script.js.map