ble-mcp-test
Version:
Complete BLE testing stack: WebSocket bridge server, MCP observability layer, and Web Bluetooth API mock. Test real BLE devices in Playwright/E2E tests without browser support.
96 lines (95 loc) • 3.5 kB
JavaScript
import { LogBuffer } from './log-buffer.js';
/**
* Shared state between bridge and observability services
*
* This provides a clean interface for:
* - Bridge to write packet logs and connection state
* - Observability to read logs and state without coupling
*/
export class SharedState {
logBuffer;
connectionState = {
connected: false,
deviceName: null,
recovering: false,
connectedAt: null
};
originalConsole = {
log: console.log,
warn: console.warn,
error: console.error
};
constructor(interceptConsole = true) {
this.logBuffer = new LogBuffer();
if (interceptConsole) {
this.setupConsoleInterceptor();
}
}
setupConsoleInterceptor() {
// Intercept console methods to capture bridge logs
console.log = (...args) => {
this.originalConsole.log.apply(console, args);
const message = args.join(' ');
if (message.includes('[Bridge]')) {
this.logBuffer.pushSystemLog('INFO', message);
}
};
console.warn = (...args) => {
this.originalConsole.warn.apply(console, args);
const message = args.join(' ');
this.logBuffer.pushSystemLog('WARN', message);
};
console.error = (...args) => {
this.originalConsole.error.apply(console, args);
const message = args.join(' ');
this.logBuffer.pushSystemLog('ERROR', message);
};
}
// === Write Interface (for Bridge) ===
logPacket(direction, data) {
this.logBuffer.logPacket(direction, data);
}
setConnectionState(state) {
const before = { ...this.connectionState };
this.connectionState = {
...this.connectionState,
...state,
connectedAt: state.connected ? new Date().toISOString() :
state.connected === false ? null :
this.connectionState.connectedAt
};
// Force this log to appear in the main console (not intercepted)
const originalLog = this.originalConsole.log;
originalLog(`[SharedState] 📊 State updated: ${JSON.stringify(before)} → ${JSON.stringify(this.connectionState)}`);
// Also log to the buffer directly
this.logBuffer.pushSystemLog('INFO', `[SharedState] 📊 State updated: ${JSON.stringify(before)} → ${JSON.stringify(this.connectionState)}`);
// File-based logging fallback
try {
// Dynamic import for Node.js fs module
import('fs').then(fs => {
const timestamp = new Date().toISOString();
const logEntry = `${timestamp} [SharedState] 📊 State updated: ${JSON.stringify(before)} → ${JSON.stringify(this.connectionState)}\n`;
fs.appendFileSync('/tmp/ble-state.log', logEntry);
});
}
catch {
// Ignore file logging errors
}
}
// === Read Interface (for Observability) ===
getLogBuffer() {
return this.logBuffer;
}
getConnectionState() {
return { ...this.connectionState };
}
getConnectionStats() {
return this.logBuffer.getConnectionStats();
}
// Restore original console methods
restoreConsole() {
console.log = this.originalConsole.log;
console.warn = this.originalConsole.warn;
console.error = this.originalConsole.error;
}
}