Major refactor: Multi-user Chrome MCP extension with remote server architecture
This commit is contained in:
235
test-voice-command-routing.js
Normal file
235
test-voice-command-routing.js
Normal file
@@ -0,0 +1,235 @@
|
||||
/**
|
||||
* Voice Command Routing Test
|
||||
* Tests that voice commands from LiveKit agents are routed to the correct Chrome extension
|
||||
*/
|
||||
|
||||
import WebSocket from 'ws';
|
||||
import fetch from 'node-fetch';
|
||||
|
||||
const CHROME_WS_URL = 'ws://localhost:3001/chrome';
|
||||
const MCP_HTTP_URL = 'http://localhost:3001/mcp';
|
||||
|
||||
class MockChromeExtension {
|
||||
constructor(userId) {
|
||||
this.userId = userId;
|
||||
this.chromeUserId = `user_${Date.now()}_${userId}_${Math.random().toString(36).substring(2, 8)}`;
|
||||
this.ws = null;
|
||||
this.sessionInfo = null;
|
||||
this.receivedCommands = [];
|
||||
}
|
||||
|
||||
async connect() {
|
||||
return new Promise((resolve, reject) => {
|
||||
console.log(`🔌 [Chrome ${this.userId}] Connecting with user ID: ${this.chromeUserId}`);
|
||||
|
||||
this.ws = new WebSocket(CHROME_WS_URL);
|
||||
|
||||
this.ws.on('open', () => {
|
||||
// Send connection info with user ID
|
||||
const connectionInfo = {
|
||||
type: 'connection_info',
|
||||
userId: this.chromeUserId,
|
||||
userAgent: `MockChrome-${this.userId}`,
|
||||
timestamp: Date.now(),
|
||||
extensionId: `mock-extension-${this.userId}`
|
||||
};
|
||||
|
||||
this.ws.send(JSON.stringify(connectionInfo));
|
||||
});
|
||||
|
||||
this.ws.on('message', (data) => {
|
||||
try {
|
||||
const message = JSON.parse(data.toString());
|
||||
|
||||
if (message.type === 'session_info') {
|
||||
this.sessionInfo = message.sessionInfo;
|
||||
console.log(`✅ [Chrome ${this.userId}] Session established: ${this.sessionInfo.sessionId}`);
|
||||
resolve();
|
||||
}
|
||||
|
||||
// Handle tool calls (voice commands)
|
||||
if (message.action === 'callTool') {
|
||||
this.receivedCommands.push(message);
|
||||
console.log(`🎤 [Chrome ${this.userId}] Received voice command: ${message.params.name}`);
|
||||
|
||||
// Send response
|
||||
const response = {
|
||||
id: message.id,
|
||||
success: true,
|
||||
result: `Command executed by Chrome ${this.userId}`,
|
||||
timestamp: Date.now()
|
||||
};
|
||||
|
||||
this.ws.send(JSON.stringify(response));
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error(`❌ [Chrome ${this.userId}] Error parsing message:`, error);
|
||||
}
|
||||
});
|
||||
|
||||
this.ws.on('error', reject);
|
||||
|
||||
setTimeout(() => {
|
||||
if (!this.sessionInfo) {
|
||||
reject(new Error(`Timeout waiting for session info for Chrome ${this.userId}`));
|
||||
}
|
||||
}, 5000);
|
||||
});
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
if (this.ws) {
|
||||
this.ws.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class MockLiveKitAgent {
|
||||
constructor(userId) {
|
||||
this.userId = userId;
|
||||
this.chromeUserId = userId; // This should match the Chrome extension's user ID
|
||||
}
|
||||
|
||||
async sendVoiceCommand(toolName, args) {
|
||||
console.log(`🎙️ [LiveKit ${this.userId}] Sending voice command: ${toolName}`);
|
||||
|
||||
const payload = {
|
||||
jsonrpc: '2.0',
|
||||
id: Date.now(),
|
||||
method: 'tools/call',
|
||||
params: {
|
||||
name: toolName,
|
||||
arguments: args
|
||||
}
|
||||
};
|
||||
|
||||
const headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'chrome-user-id': this.chromeUserId // Route to specific Chrome extension
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await fetch(MCP_HTTP_URL, {
|
||||
method: 'POST',
|
||||
headers: headers,
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
console.log(`📨 [LiveKit ${this.userId}] Voice command response:`, result);
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error(`❌ [LiveKit ${this.userId}] Error sending voice command:`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function testVoiceCommandRouting() {
|
||||
console.log('🎤 Starting Voice Command Routing Test...\n');
|
||||
|
||||
const chromeExtensions = [];
|
||||
const liveKitAgents = [];
|
||||
|
||||
try {
|
||||
// Step 1: Create and connect Chrome extensions
|
||||
console.log('📋 STEP 1: Setting up Chrome Extensions');
|
||||
console.log('=' .repeat(50));
|
||||
|
||||
for (let i = 1; i <= 2; i++) {
|
||||
const chrome = new MockChromeExtension(i);
|
||||
chromeExtensions.push(chrome);
|
||||
|
||||
await chrome.connect();
|
||||
console.log(`✅ Chrome ${i} connected with user ID: ${chrome.chromeUserId}`);
|
||||
|
||||
// Create corresponding LiveKit agent
|
||||
const agent = new MockLiveKitAgent(chrome.chromeUserId);
|
||||
liveKitAgents.push(agent);
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
}
|
||||
|
||||
// Step 2: Test voice command routing
|
||||
console.log('\n📋 STEP 2: Testing Voice Command Routing');
|
||||
console.log('=' .repeat(50));
|
||||
|
||||
// Send commands from each LiveKit agent
|
||||
for (let i = 0; i < liveKitAgents.length; i++) {
|
||||
const agent = liveKitAgents[i];
|
||||
const chrome = chromeExtensions[i];
|
||||
|
||||
console.log(`\n🎙️ Testing voice command from LiveKit Agent ${i + 1} to Chrome ${i + 1}`);
|
||||
|
||||
await agent.sendVoiceCommand('chrome_navigate', {
|
||||
url: `https://example.com/user${i + 1}`,
|
||||
userContext: agent.chromeUserId
|
||||
});
|
||||
|
||||
// Wait for command to be processed
|
||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||
}
|
||||
|
||||
// Step 3: Test cross-user isolation
|
||||
console.log('\n📋 STEP 3: Testing Cross-User Isolation');
|
||||
console.log('=' .repeat(50));
|
||||
|
||||
// Agent 1 sends command that should only go to Chrome 1
|
||||
console.log('\n🔒 Testing isolation: Agent 1 → Chrome 1 only');
|
||||
await liveKitAgents[0].sendVoiceCommand('chrome_click_element', {
|
||||
selector: '#test-button',
|
||||
userContext: liveKitAgents[0].chromeUserId
|
||||
});
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||
|
||||
// Step 4: Verify results
|
||||
console.log('\n📋 STEP 4: Verifying Results');
|
||||
console.log('=' .repeat(50));
|
||||
|
||||
chromeExtensions.forEach((chrome, index) => {
|
||||
console.log(`\n👤 Chrome Extension ${index + 1} (${chrome.chromeUserId}):`);
|
||||
console.log(` Session ID: ${chrome.sessionInfo?.sessionId}`);
|
||||
console.log(` Commands Received: ${chrome.receivedCommands.length}`);
|
||||
|
||||
chrome.receivedCommands.forEach((cmd, cmdIndex) => {
|
||||
console.log(` Command ${cmdIndex + 1}: ${cmd.params.name}`);
|
||||
});
|
||||
});
|
||||
|
||||
// Verify isolation
|
||||
const totalCommands = chromeExtensions.reduce((sum, chrome) => sum + chrome.receivedCommands.length, 0);
|
||||
const expectedCommands = liveKitAgents.length * 2; // 2 commands per agent
|
||||
|
||||
console.log(`\n📊 RESULTS:`);
|
||||
console.log(` Total Commands Sent: ${expectedCommands}`);
|
||||
console.log(` Total Commands Received: ${totalCommands}`);
|
||||
console.log(` Routing Success: ${totalCommands === expectedCommands ? '✅' : '❌'}`);
|
||||
|
||||
// Check that each Chrome extension received the right number of commands
|
||||
const isolationSuccess = chromeExtensions.every(chrome => chrome.receivedCommands.length === 2);
|
||||
console.log(` User Isolation: ${isolationSuccess ? '✅' : '❌'}`);
|
||||
|
||||
if (totalCommands === expectedCommands && isolationSuccess) {
|
||||
console.log('\n🎉 Voice Command Routing Test PASSED!');
|
||||
} else {
|
||||
console.log('\n❌ Voice Command Routing Test FAILED!');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Test failed:', error);
|
||||
} finally {
|
||||
// Cleanup
|
||||
console.log('\n🧹 Cleaning up...');
|
||||
chromeExtensions.forEach(chrome => chrome.disconnect());
|
||||
|
||||
setTimeout(() => {
|
||||
console.log('✅ Test completed');
|
||||
process.exit(0);
|
||||
}, 2000);
|
||||
}
|
||||
}
|
||||
|
||||
// Run the test
|
||||
testVoiceCommandRouting().catch(console.error);
|
Reference in New Issue
Block a user