mirror of
https://github.com/QwenLM/qwen-code.git
synced 2025-12-22 17:57:46 +00:00
366 lines
10 KiB
JavaScript
366 lines
10 KiB
JavaScript
/**
|
|
* Background Service Worker for Qwen CLI Bridge
|
|
* Handles communication between extension components and native host
|
|
*/
|
|
|
|
// Native messaging host name
|
|
const NATIVE_HOST_NAME = 'com.qwen.cli.bridge';
|
|
|
|
// Connection state
|
|
let nativePort = null;
|
|
let isConnected = false;
|
|
let qwenCliStatus = 'disconnected';
|
|
let pendingRequests = new Map();
|
|
let requestId = 0;
|
|
|
|
// Connection management
|
|
function connectToNativeHost() {
|
|
if (nativePort) {
|
|
return Promise.resolve(nativePort);
|
|
}
|
|
|
|
return new Promise((resolve, reject) => {
|
|
try {
|
|
console.log('Attempting to connect to Native Host:', NATIVE_HOST_NAME);
|
|
nativePort = chrome.runtime.connectNative(NATIVE_HOST_NAME);
|
|
|
|
// Check for immediate errors
|
|
if (chrome.runtime.lastError) {
|
|
console.error('Chrome runtime error:', chrome.runtime.lastError);
|
|
reject(new Error(chrome.runtime.lastError.message));
|
|
return;
|
|
}
|
|
|
|
nativePort.onMessage.addListener((message) => {
|
|
console.log('Native message received:', message);
|
|
handleNativeMessage(message);
|
|
});
|
|
|
|
nativePort.onDisconnect.addListener(() => {
|
|
const error = chrome.runtime.lastError;
|
|
console.log('Native host disconnected');
|
|
if (error) {
|
|
console.error('Disconnect error:', error);
|
|
}
|
|
nativePort = null;
|
|
isConnected = false;
|
|
qwenCliStatus = 'disconnected';
|
|
|
|
// Reject all pending requests
|
|
for (const [id, handler] of pendingRequests) {
|
|
handler.reject(new Error('Native host disconnected'));
|
|
}
|
|
pendingRequests.clear();
|
|
|
|
// Notify popup of disconnection
|
|
chrome.runtime.sendMessage({
|
|
type: 'STATUS_UPDATE',
|
|
status: 'disconnected'
|
|
}).catch(() => {}); // Ignore errors if popup is closed
|
|
});
|
|
|
|
// Send initial handshake
|
|
console.log('Sending handshake...');
|
|
nativePort.postMessage({ type: 'handshake', version: '1.0.0' });
|
|
|
|
// Set timeout for handshake response
|
|
const handshakeTimeout = setTimeout(() => {
|
|
console.error('Handshake timeout - no response from Native Host');
|
|
if (nativePort) {
|
|
nativePort.disconnect();
|
|
}
|
|
reject(new Error('Handshake timeout'));
|
|
}, 5000);
|
|
|
|
// Store timeout so we can clear it when we get response
|
|
nativePort._handshakeTimeout = handshakeTimeout;
|
|
|
|
isConnected = true;
|
|
qwenCliStatus = 'connected';
|
|
|
|
resolve(nativePort);
|
|
} catch (error) {
|
|
console.error('Failed to connect to native host:', error);
|
|
reject(error);
|
|
}
|
|
});
|
|
}
|
|
|
|
// Handle messages from native host
|
|
function handleNativeMessage(message) {
|
|
if (message.type === 'handshake_response') {
|
|
console.log('Handshake successful:', message);
|
|
|
|
// Clear handshake timeout
|
|
if (nativePort && nativePort._handshakeTimeout) {
|
|
clearTimeout(nativePort._handshakeTimeout);
|
|
delete nativePort._handshakeTimeout;
|
|
}
|
|
|
|
qwenCliStatus = message.qwenStatus || 'connected';
|
|
|
|
// Notify popup of connection
|
|
chrome.runtime.sendMessage({
|
|
type: 'STATUS_UPDATE',
|
|
status: qwenCliStatus,
|
|
capabilities: message.capabilities
|
|
}).catch(() => {});
|
|
} else if (message.type === 'response' && message.id !== undefined) {
|
|
// Handle response to a specific request
|
|
const handler = pendingRequests.get(message.id);
|
|
if (handler) {
|
|
if (message.error) {
|
|
handler.reject(new Error(message.error));
|
|
} else {
|
|
handler.resolve(message.data);
|
|
}
|
|
pendingRequests.delete(message.id);
|
|
}
|
|
} else if (message.type === 'event') {
|
|
// Handle events from Qwen CLI
|
|
handleQwenEvent(message);
|
|
}
|
|
}
|
|
|
|
// Send request to native host
|
|
async function sendToNativeHost(message) {
|
|
if (!nativePort || !isConnected) {
|
|
await connectToNativeHost();
|
|
}
|
|
|
|
return new Promise((resolve, reject) => {
|
|
const id = ++requestId;
|
|
pendingRequests.set(id, { resolve, reject });
|
|
|
|
nativePort.postMessage({
|
|
...message,
|
|
id
|
|
});
|
|
|
|
// Set timeout for request
|
|
setTimeout(() => {
|
|
if (pendingRequests.has(id)) {
|
|
pendingRequests.delete(id);
|
|
reject(new Error('Request timeout'));
|
|
}
|
|
}, 30000); // 30 second timeout
|
|
});
|
|
}
|
|
|
|
// Handle events from Qwen CLI
|
|
function handleQwenEvent(event) {
|
|
console.log('Qwen event:', event);
|
|
|
|
// Forward event to content scripts and popup
|
|
chrome.tabs.query({}, (tabs) => {
|
|
tabs.forEach(tab => {
|
|
chrome.tabs.sendMessage(tab.id, {
|
|
type: 'QWEN_EVENT',
|
|
event: event.data
|
|
}).catch(() => {}); // Ignore errors for tabs without content script
|
|
});
|
|
});
|
|
|
|
chrome.runtime.sendMessage({
|
|
type: 'QWEN_EVENT',
|
|
event: event.data
|
|
}).catch(() => {});
|
|
}
|
|
|
|
// Message handlers from extension components
|
|
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
|
|
console.log('Message received:', request, 'from:', sender);
|
|
|
|
if (request.type === 'CONNECT') {
|
|
// Connect to native host
|
|
connectToNativeHost()
|
|
.then(() => {
|
|
sendResponse({ success: true, status: qwenCliStatus });
|
|
})
|
|
.catch(error => {
|
|
sendResponse({ success: false, error: error.message });
|
|
});
|
|
return true; // Will respond asynchronously
|
|
}
|
|
|
|
if (request.type === 'GET_STATUS') {
|
|
// Get current connection status
|
|
sendResponse({
|
|
connected: isConnected,
|
|
status: qwenCliStatus
|
|
});
|
|
return false;
|
|
}
|
|
|
|
if (request.type === 'EXTRACT_PAGE_DATA') {
|
|
// Request page data from content script
|
|
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
|
|
if (tabs[0]) {
|
|
chrome.tabs.sendMessage(tabs[0].id, {
|
|
type: 'EXTRACT_DATA'
|
|
}, (response) => {
|
|
if (chrome.runtime.lastError) {
|
|
sendResponse({
|
|
success: false,
|
|
error: chrome.runtime.lastError.message
|
|
});
|
|
} else {
|
|
sendResponse(response);
|
|
}
|
|
});
|
|
} else {
|
|
sendResponse({
|
|
success: false,
|
|
error: 'No active tab found'
|
|
});
|
|
}
|
|
});
|
|
return true; // Will respond asynchronously
|
|
}
|
|
|
|
if (request.type === 'SEND_TO_QWEN') {
|
|
// Send data to Qwen CLI via native host
|
|
sendToNativeHost({
|
|
type: 'qwen_request',
|
|
action: request.action,
|
|
data: request.data
|
|
})
|
|
.then(response => {
|
|
sendResponse({ success: true, data: response });
|
|
})
|
|
.catch(error => {
|
|
sendResponse({ success: false, error: error.message });
|
|
});
|
|
return true; // Will respond asynchronously
|
|
}
|
|
|
|
if (request.type === 'START_QWEN_CLI') {
|
|
// Request native host to start Qwen CLI
|
|
sendToNativeHost({
|
|
type: 'start_qwen',
|
|
config: request.config || {}
|
|
})
|
|
.then(response => {
|
|
qwenCliStatus = 'running';
|
|
sendResponse({ success: true, data: response });
|
|
})
|
|
.catch(error => {
|
|
sendResponse({ success: false, error: error.message });
|
|
});
|
|
return true; // Will respond asynchronously
|
|
}
|
|
|
|
if (request.type === 'STOP_QWEN_CLI') {
|
|
// Request native host to stop Qwen CLI
|
|
sendToNativeHost({
|
|
type: 'stop_qwen'
|
|
})
|
|
.then(response => {
|
|
qwenCliStatus = 'stopped';
|
|
sendResponse({ success: true, data: response });
|
|
})
|
|
.catch(error => {
|
|
sendResponse({ success: false, error: error.message });
|
|
});
|
|
return true; // Will respond asynchronously
|
|
}
|
|
|
|
if (request.type === 'CAPTURE_SCREENSHOT') {
|
|
// Capture screenshot of active tab
|
|
chrome.tabs.captureVisibleTab(null, { format: 'png' }, (dataUrl) => {
|
|
if (chrome.runtime.lastError) {
|
|
sendResponse({
|
|
success: false,
|
|
error: chrome.runtime.lastError.message
|
|
});
|
|
} else {
|
|
sendResponse({
|
|
success: true,
|
|
data: dataUrl
|
|
});
|
|
}
|
|
});
|
|
return true; // Will respond asynchronously
|
|
}
|
|
|
|
if (request.type === 'GET_NETWORK_LOGS') {
|
|
// Get network logs (requires debugger API)
|
|
getNetworkLogs(sender.tab?.id)
|
|
.then(logs => {
|
|
sendResponse({ success: true, data: logs });
|
|
})
|
|
.catch(error => {
|
|
sendResponse({ success: false, error: error.message });
|
|
});
|
|
return true; // Will respond asynchronously
|
|
}
|
|
});
|
|
|
|
// Network logging using debugger API
|
|
const debuggerTargets = new Map();
|
|
|
|
async function getNetworkLogs(tabId) {
|
|
if (!tabId) {
|
|
const tabs = await chrome.tabs.query({ active: true, currentWindow: true });
|
|
tabId = tabs[0]?.id;
|
|
if (!tabId) throw new Error('No active tab found');
|
|
}
|
|
|
|
// Check if debugger is already attached
|
|
if (!debuggerTargets.has(tabId)) {
|
|
await chrome.debugger.attach({ tabId }, '1.3');
|
|
await chrome.debugger.sendCommand({ tabId }, 'Network.enable');
|
|
|
|
// Store network logs
|
|
debuggerTargets.set(tabId, { logs: [] });
|
|
|
|
// Listen for network events
|
|
chrome.debugger.onEvent.addListener((source, method, params) => {
|
|
if (source.tabId === tabId) {
|
|
const target = debuggerTargets.get(tabId);
|
|
if (target && method.startsWith('Network.')) {
|
|
target.logs.push({
|
|
method,
|
|
params,
|
|
timestamp: Date.now()
|
|
});
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
const target = debuggerTargets.get(tabId);
|
|
return target?.logs || [];
|
|
}
|
|
|
|
// Clean up debugger on tab close
|
|
chrome.tabs.onRemoved.addListener((tabId) => {
|
|
if (debuggerTargets.has(tabId)) {
|
|
chrome.debugger.detach({ tabId });
|
|
debuggerTargets.delete(tabId);
|
|
}
|
|
});
|
|
|
|
// Listen for extension installation or update
|
|
chrome.runtime.onInstalled.addListener((details) => {
|
|
console.log('Extension installed/updated:', details);
|
|
|
|
if (details.reason === 'install') {
|
|
// Just log the installation, don't auto-open options
|
|
console.log('Extension installed for the first time');
|
|
// Users can access options from popup menu
|
|
}
|
|
});
|
|
|
|
// Open side panel when extension icon is clicked
|
|
chrome.action.onClicked.addListener((tab) => {
|
|
chrome.sidePanel.open({ windowId: tab.windowId });
|
|
});
|
|
|
|
// Export for testing
|
|
if (typeof module !== 'undefined' && module.exports) {
|
|
module.exports = {
|
|
connectToNativeHost,
|
|
sendToNativeHost
|
|
};
|
|
} |