Major refactor: Multi-user Chrome MCP extension with remote server architecture
This commit is contained in:
@@ -0,0 +1,184 @@
|
||||
import { BaseBrowserToolExecutor } from '../base-browser';
|
||||
import { createErrorResponse, createSuccessResponse } from '../../../../common/tool-handler';
|
||||
import { ERROR_MESSAGES } from '../../../../common/constants';
|
||||
|
||||
export class EnhancedSearchTool extends BaseBrowserToolExecutor {
|
||||
async chromeSearchGoogle(args: {
|
||||
query: string;
|
||||
openGoogle?: boolean;
|
||||
extractResults?: boolean;
|
||||
maxResults?: number;
|
||||
}) {
|
||||
const { query, openGoogle = true, extractResults = true, maxResults = 10 } = args;
|
||||
|
||||
try {
|
||||
// Step 1: Navigate to Google if requested
|
||||
if (openGoogle) {
|
||||
await this.navigateToGoogle();
|
||||
await this.sleep(3000); // Wait for page to load
|
||||
}
|
||||
|
||||
// Step 2: Find and fill search box
|
||||
const searchSuccess = await this.performGoogleSearch(query);
|
||||
if (!searchSuccess) {
|
||||
return createErrorResponse(
|
||||
'Failed to perform Google search - could not find or interact with search box',
|
||||
);
|
||||
}
|
||||
|
||||
// Step 3: Wait for results to load
|
||||
await this.sleep(3000);
|
||||
|
||||
// Step 4: Extract results if requested
|
||||
if (extractResults) {
|
||||
const results = await this.extractSearchResults(maxResults);
|
||||
return createSuccessResponse({
|
||||
query,
|
||||
searchCompleted: true,
|
||||
resultsExtracted: true,
|
||||
results,
|
||||
});
|
||||
}
|
||||
|
||||
return createSuccessResponse({
|
||||
query,
|
||||
searchCompleted: true,
|
||||
resultsExtracted: false,
|
||||
message: 'Google search completed successfully',
|
||||
});
|
||||
} catch (error) {
|
||||
return createErrorResponse(
|
||||
`Error performing Google search: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async chromeSubmitForm(args: {
|
||||
formSelector?: string;
|
||||
inputSelector?: string;
|
||||
submitMethod?: 'enter' | 'button' | 'auto';
|
||||
}) {
|
||||
const { formSelector = 'form', inputSelector, submitMethod = 'auto' } = args;
|
||||
|
||||
try {
|
||||
const tabs = await chrome.tabs.query({ active: true, currentWindow: true });
|
||||
if (!tabs[0]?.id) {
|
||||
return createErrorResponse(ERROR_MESSAGES.TAB_NOT_FOUND);
|
||||
}
|
||||
|
||||
const tabId = tabs[0].id;
|
||||
|
||||
// Inject form submission script
|
||||
await this.injectContentScript(tabId, ['inject-scripts/form-submit-helper.js']);
|
||||
|
||||
const result = await this.sendMessageToTab(tabId, {
|
||||
action: 'submitForm',
|
||||
formSelector,
|
||||
inputSelector,
|
||||
submitMethod,
|
||||
});
|
||||
|
||||
if (result.error) {
|
||||
return createErrorResponse(result.error);
|
||||
}
|
||||
|
||||
return createSuccessResponse(result);
|
||||
} catch (error) {
|
||||
return createErrorResponse(
|
||||
`Error submitting form: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private async navigateToGoogle(): Promise<void> {
|
||||
const tabs = await chrome.tabs.query({ active: true, currentWindow: true });
|
||||
if (!tabs[0]?.id) {
|
||||
throw new Error('No active tab found');
|
||||
}
|
||||
|
||||
await chrome.tabs.update(tabs[0].id, { url: 'https://www.google.com' });
|
||||
}
|
||||
|
||||
private async performGoogleSearch(query: string): Promise<boolean> {
|
||||
const tabs = await chrome.tabs.query({ active: true, currentWindow: true });
|
||||
if (!tabs[0]?.id) {
|
||||
throw new Error('No active tab found');
|
||||
}
|
||||
|
||||
const tabId = tabs[0].id;
|
||||
|
||||
// Enhanced search box selectors
|
||||
const searchSelectors = [
|
||||
'#APjFqb', // Main Google search box ID
|
||||
'textarea[name="q"]', // Google search textarea
|
||||
'input[name="q"]', // Google search input (fallback)
|
||||
'[role="combobox"]', // Role-based selector
|
||||
'.gLFyf', // Google search box class
|
||||
'textarea[aria-label*="Search"]', // Aria-label based
|
||||
'[title*="Search"]', // Title attribute
|
||||
'.gsfi', // Google search field input class
|
||||
'#lst-ib', // Alternative Google search ID
|
||||
'input[type="search"]', // Generic search input
|
||||
'textarea[role="combobox"]', // Textarea with combobox role
|
||||
];
|
||||
|
||||
// Inject search helper script
|
||||
await this.injectContentScript(tabId, ['inject-scripts/enhanced-search-helper.js']);
|
||||
|
||||
for (const selector of searchSelectors) {
|
||||
try {
|
||||
const result = await this.sendMessageToTab(tabId, {
|
||||
action: 'performGoogleSearch',
|
||||
selector,
|
||||
query,
|
||||
});
|
||||
|
||||
if (result.success) {
|
||||
return true;
|
||||
}
|
||||
} catch (error) {
|
||||
console.debug(`Search selector ${selector} failed:`, error);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private async extractSearchResults(maxResults: number): Promise<any[]> {
|
||||
const tabs = await chrome.tabs.query({ active: true, currentWindow: true });
|
||||
if (!tabs[0]?.id) {
|
||||
throw new Error('No active tab found');
|
||||
}
|
||||
|
||||
const tabId = tabs[0].id;
|
||||
|
||||
const result = await this.sendMessageToTab(tabId, {
|
||||
action: 'extractSearchResults',
|
||||
maxResults,
|
||||
});
|
||||
|
||||
return result.results || [];
|
||||
}
|
||||
|
||||
private sleep(ms: number): Promise<void> {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
}
|
||||
|
||||
// Export tool instances
|
||||
export const searchGoogleTool = new (class extends EnhancedSearchTool {
|
||||
name = 'chrome_search_google';
|
||||
|
||||
async execute(args: any) {
|
||||
return await this.chromeSearchGoogle(args);
|
||||
}
|
||||
})();
|
||||
|
||||
export const submitFormTool = new (class extends EnhancedSearchTool {
|
||||
name = 'chrome_submit_form';
|
||||
|
||||
async execute(args: any) {
|
||||
return await this.chromeSubmitForm(args);
|
||||
}
|
||||
})();
|
Reference in New Issue
Block a user