Major refactor: Multi-user Chrome MCP extension with remote server architecture
This commit is contained in:
@@ -1,38 +1,369 @@
|
||||
import { initNativeHostListener } from './native-host';
|
||||
import {
|
||||
initSemanticSimilarityListener,
|
||||
initializeSemanticEngineIfCached,
|
||||
} from './semantic-similarity';
|
||||
// Native messaging removed - using remote server only
|
||||
// import { initNativeHostListener } from './native-host';
|
||||
// Temporarily disable semantic similarity to focus on connection issues
|
||||
// import {
|
||||
// initSemanticSimilarityListener,
|
||||
// initializeSemanticEngineIfCached,
|
||||
// } from './semantic-similarity';
|
||||
import { initStorageManagerListener } from './storage-manager';
|
||||
import { cleanupModelCache } from '@/utils/semantic-similarity-engine';
|
||||
import { RemoteServerClient } from '@/utils/remote-server-client';
|
||||
import { DEFAULT_CONNECTION_CONFIG } from '@/common/env-config';
|
||||
import { handleCallTool } from './tools';
|
||||
|
||||
// Global remote server client instance
|
||||
let remoteServerClient: RemoteServerClient | null = null;
|
||||
|
||||
/**
|
||||
* Background script entry point
|
||||
* Initializes all background services and listeners
|
||||
*/
|
||||
export default defineBackground(() => {
|
||||
// Initialize core services
|
||||
initNativeHostListener();
|
||||
initSemanticSimilarityListener();
|
||||
// Initialize remote server client first (prioritize over native messaging)
|
||||
initRemoteServerClient();
|
||||
|
||||
// Initialize core services (native messaging removed)
|
||||
// initNativeHostListener();
|
||||
// initSemanticSimilarityListener();
|
||||
initStorageManagerListener();
|
||||
|
||||
// Initialize browser event listeners for connection persistence
|
||||
initBrowserEventListeners();
|
||||
|
||||
// Conditionally initialize semantic similarity engine if model cache exists
|
||||
initializeSemanticEngineIfCached()
|
||||
.then((initialized) => {
|
||||
if (initialized) {
|
||||
console.log('Background: Semantic similarity engine initialized from cache');
|
||||
} else {
|
||||
console.log(
|
||||
'Background: Semantic similarity engine initialization skipped (no cache found)',
|
||||
);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.warn('Background: Failed to conditionally initialize semantic engine:', error);
|
||||
});
|
||||
// initializeSemanticEngineIfCached()
|
||||
// .then((initialized) => {
|
||||
// if (initialized) {
|
||||
// console.log('Background: Semantic similarity engine initialized from cache');
|
||||
// } else {
|
||||
// console.log(
|
||||
// 'Background: Semantic similarity engine initialization skipped (no cache found)',
|
||||
// );
|
||||
// }
|
||||
// })
|
||||
// .catch((error) => {
|
||||
// console.warn('Background: Failed to conditionally initialize semantic engine:', error);
|
||||
// });
|
||||
|
||||
// Initial cleanup on startup
|
||||
cleanupModelCache().catch((error) => {
|
||||
console.warn('Background: Initial cache cleanup failed:', error);
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Initialize remote server client (without auto-connecting)
|
||||
*/
|
||||
function initRemoteServerClient() {
|
||||
try {
|
||||
remoteServerClient = new RemoteServerClient({
|
||||
serverUrl: DEFAULT_CONNECTION_CONFIG.serverUrl,
|
||||
reconnectInterval: DEFAULT_CONNECTION_CONFIG.reconnectInterval,
|
||||
maxReconnectAttempts: 50, // Increased for better reliability
|
||||
});
|
||||
|
||||
console.log('Background: Remote server client initialized (not connected)');
|
||||
console.log('Background: Use popup to manually connect to remote server');
|
||||
} catch (error) {
|
||||
console.error('Background: Failed to initialize remote server client:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the remote server client instance
|
||||
*/
|
||||
export function getRemoteServerClient(): RemoteServerClient | null {
|
||||
return remoteServerClient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize browser event listeners for connection persistence
|
||||
*/
|
||||
function initBrowserEventListeners() {
|
||||
// Listen for browser startup events
|
||||
chrome.runtime.onStartup.addListener(() => {
|
||||
console.log('Background: Browser startup detected. Manual connection required via popup.');
|
||||
if (remoteServerClient) {
|
||||
console.log('Background: Remote server client ready for manual connection');
|
||||
}
|
||||
});
|
||||
|
||||
// Listen for extension installation/update events
|
||||
chrome.runtime.onInstalled.addListener((details) => {
|
||||
console.log('Background: Extension installed/updated:', details.reason);
|
||||
if (details.reason === 'update') {
|
||||
console.log('Background: Extension updated, manual connection required');
|
||||
}
|
||||
});
|
||||
|
||||
// Listen for browser suspension/resume events (Chrome specific)
|
||||
if (chrome.runtime.onSuspend) {
|
||||
chrome.runtime.onSuspend.addListener(() => {
|
||||
console.log('Background: Browser suspending, connection state saved');
|
||||
// Connection state is automatically saved when connected
|
||||
});
|
||||
}
|
||||
|
||||
if (chrome.runtime.onSuspendCanceled) {
|
||||
chrome.runtime.onSuspendCanceled.addListener(() => {
|
||||
console.log('Background: Browser suspend canceled, maintaining connection');
|
||||
});
|
||||
}
|
||||
|
||||
// Monitor tab events to ensure connection persists across tab operations
|
||||
chrome.tabs.onActivated.addListener((activeInfo) => {
|
||||
// Connection should persist regardless of tab switches
|
||||
if (remoteServerClient && remoteServerClient.isConnected()) {
|
||||
console.log(`Background: Tab switched to ${activeInfo.tabId}, connection maintained`);
|
||||
}
|
||||
});
|
||||
|
||||
// Monitor window events
|
||||
chrome.windows.onFocusChanged.addListener((windowId) => {
|
||||
// Connection should persist regardless of window focus changes
|
||||
if (
|
||||
remoteServerClient &&
|
||||
remoteServerClient.isConnected() &&
|
||||
windowId !== chrome.windows.WINDOW_ID_NONE
|
||||
) {
|
||||
console.log(`Background: Window focus changed to ${windowId}, connection maintained`);
|
||||
}
|
||||
});
|
||||
|
||||
console.log('Background: Browser event listeners initialized for connection persistence');
|
||||
|
||||
// Start periodic connection health check
|
||||
startConnectionHealthCheck();
|
||||
}
|
||||
|
||||
/**
|
||||
* Start periodic connection health check to maintain persistent connections
|
||||
*/
|
||||
function startConnectionHealthCheck() {
|
||||
// Check connection health every 5 minutes (for monitoring only, no auto-reconnection)
|
||||
setInterval(
|
||||
() => {
|
||||
if (remoteServerClient) {
|
||||
const isConnected = remoteServerClient.isConnected();
|
||||
console.log(`Background: Connection health check - Connected: ${isConnected}`);
|
||||
|
||||
if (!isConnected) {
|
||||
console.log('Background: Connection lost. Use popup to manually reconnect.');
|
||||
// No automatic reconnection - user must manually reconnect via popup
|
||||
}
|
||||
}
|
||||
},
|
||||
5 * 60 * 1000,
|
||||
); // 5 minutes
|
||||
|
||||
console.log(
|
||||
'Background: Connection health check started (monitoring only, no auto-reconnection)',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle messages from popup for remote server control
|
||||
*/
|
||||
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
||||
if (message.type === 'getRemoteServerStatus') {
|
||||
const status = remoteServerClient?.getStatus() || {
|
||||
connected: false,
|
||||
connecting: false,
|
||||
reconnectAttempts: 0,
|
||||
connectionTime: undefined,
|
||||
serverUrl: DEFAULT_CONNECTION_CONFIG.serverUrl,
|
||||
};
|
||||
sendResponse(status);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (message.type === 'connectRemoteServer') {
|
||||
if (!remoteServerClient) {
|
||||
sendResponse({ success: false, error: 'Remote server client not initialized' });
|
||||
return true;
|
||||
}
|
||||
|
||||
if (remoteServerClient.isConnected()) {
|
||||
sendResponse({ success: true, message: 'Already connected' });
|
||||
return true;
|
||||
}
|
||||
|
||||
console.log('Background: Attempting to connect to remote server...');
|
||||
remoteServerClient
|
||||
.connect()
|
||||
.then(() => {
|
||||
console.log('Background: Successfully connected to remote server');
|
||||
sendResponse({ success: true });
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Background: Failed to connect to remote server:', error);
|
||||
sendResponse({ success: false, error: error.message });
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
if (message.type === 'disconnectRemoteServer') {
|
||||
if (!remoteServerClient) {
|
||||
sendResponse({ success: false, error: 'Remote server client not initialized' });
|
||||
return true;
|
||||
}
|
||||
|
||||
console.log('Background: Disconnecting from remote server...');
|
||||
try {
|
||||
remoteServerClient.disconnect();
|
||||
console.log('Background: Successfully disconnected from remote server');
|
||||
sendResponse({ success: true });
|
||||
} catch (error) {
|
||||
console.error('Background: Error during disconnect:', error);
|
||||
sendResponse({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Disconnect failed',
|
||||
});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (message.type === 'restoreRemoteConnection') {
|
||||
if (!remoteServerClient) {
|
||||
sendResponse({ success: false, error: 'Remote server client not initialized' });
|
||||
return true;
|
||||
}
|
||||
|
||||
if (remoteServerClient.isConnected()) {
|
||||
sendResponse({ success: true, message: 'Already connected' });
|
||||
return true;
|
||||
}
|
||||
|
||||
console.log('Background: Attempting to restore previous connection...');
|
||||
remoteServerClient
|
||||
.restoreConnectionFromState()
|
||||
.then((restored) => {
|
||||
if (restored) {
|
||||
console.log('Background: Successfully restored previous connection');
|
||||
sendResponse({ success: true });
|
||||
} else {
|
||||
console.log('Background: No previous connection to restore');
|
||||
sendResponse({ success: false, error: 'No previous connection found' });
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Background: Failed to restore previous connection:', error);
|
||||
sendResponse({ success: false, error: error.message });
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
if (message.type === 'getCurrentUserId') {
|
||||
if (!remoteServerClient) {
|
||||
sendResponse({ success: false, error: 'Remote server client not initialized' });
|
||||
return true;
|
||||
}
|
||||
|
||||
remoteServerClient
|
||||
.getCurrentUserId()
|
||||
.then((userId) => {
|
||||
sendResponse({ success: true, userId });
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Background: Failed to get current user ID:', error);
|
||||
sendResponse({ success: false, error: error.message });
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
if (message.type === 'callTool') {
|
||||
handleCallTool({ name: message.toolName, args: message.params })
|
||||
.then((result) => {
|
||||
sendResponse(result);
|
||||
})
|
||||
.catch((error) => {
|
||||
sendResponse({ error: error.message });
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
if (message.type === 'injectUserIdHelper') {
|
||||
injectUserIdHelper(message.tabId)
|
||||
.then((result) => {
|
||||
sendResponse(result);
|
||||
})
|
||||
.catch((error) => {
|
||||
sendResponse({ success: false, error: error.message });
|
||||
});
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Inject user ID helper script into a specific tab
|
||||
*/
|
||||
async function injectUserIdHelper(tabId?: number): Promise<{ success: boolean; message: string }> {
|
||||
try {
|
||||
let targetTabId = tabId;
|
||||
|
||||
// If no tab ID provided, use the active tab
|
||||
if (!targetTabId) {
|
||||
const tabs = await chrome.tabs.query({ active: true, currentWindow: true });
|
||||
if (!tabs[0]?.id) {
|
||||
throw new Error('No active tab found');
|
||||
}
|
||||
targetTabId = tabs[0].id;
|
||||
}
|
||||
|
||||
// Inject the user ID helper script
|
||||
await chrome.scripting.executeScript({
|
||||
target: { tabId: targetTabId },
|
||||
files: ['inject-scripts/user-id-helper.js'],
|
||||
});
|
||||
|
||||
// Get current user ID and inject it
|
||||
if (remoteServerClient) {
|
||||
const userId = await remoteServerClient.getCurrentUserId();
|
||||
if (userId) {
|
||||
// Inject the user ID into the page
|
||||
await chrome.scripting.executeScript({
|
||||
target: { tabId: targetTabId },
|
||||
func: (userId) => {
|
||||
// Make user ID available globally
|
||||
(window as any).chromeExtensionUserId = userId;
|
||||
|
||||
// Store in sessionStorage
|
||||
try {
|
||||
sessionStorage.setItem('chromeExtensionUserId', userId);
|
||||
} catch (e) {
|
||||
// Ignore storage errors
|
||||
}
|
||||
|
||||
// Dispatch event for pages waiting for user ID
|
||||
window.dispatchEvent(
|
||||
new CustomEvent('chromeExtensionUserIdReady', {
|
||||
detail: { userId: userId },
|
||||
}),
|
||||
);
|
||||
|
||||
console.log('Chrome Extension User ID injected:', userId);
|
||||
},
|
||||
args: [userId],
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: `User ID helper injected into tab ${targetTabId} with user ID: ${userId}`,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
success: true,
|
||||
message: `User ID helper injected into tab ${targetTabId} but no user ID available (not connected)`,
|
||||
};
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
success: true,
|
||||
message: `User ID helper injected into tab ${targetTabId} but remote server client not initialized`,
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to inject user ID helper:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user