UNPKG

blowback-context

Version:

MCP server that integrates with FE development server for Cursor

260 lines 8.59 kB
import pkg from 'node-sqlite3-wasm'; const { Database } = pkg; import path from 'path'; import { TMP_DIRECTORY } from '../constants.js'; import { Logger } from '../utils/logger.js'; export class ScreenshotDB { db; constructor() { const dbPath = path.join(TMP_DIRECTORY, 'screenshots.db'); this.db = new Database(dbPath); this.init(); Logger.info(`Screenshot database initialized at: ${dbPath}`); } init() { // Create screenshots table with browser support this.db.exec(` CREATE TABLE IF NOT EXISTS screenshots ( id TEXT PRIMARY KEY, hostname TEXT NOT NULL, pathname TEXT NOT NULL, query TEXT, hash TEXT, checkpoint_id TEXT, timestamp DATETIME NOT NULL, mime_type TEXT NOT NULL, description TEXT, browser_id TEXT, browser_type TEXT, session_id TEXT ) `); // Create browser_instances table for persistence this.db.exec(` CREATE TABLE IF NOT EXISTS browser_instances ( id TEXT PRIMARY KEY, type TEXT NOT NULL, display_name TEXT, metadata TEXT, created_at DATETIME NOT NULL, last_used_at DATETIME NOT NULL, is_active INTEGER DEFAULT 1 ) `); // Create indexes this.db.exec(` CREATE INDEX IF NOT EXISTS idx_screenshots_url ON screenshots(hostname, pathname); CREATE INDEX IF NOT EXISTS idx_screenshots_checkpoint ON screenshots(checkpoint_id); CREATE INDEX IF NOT EXISTS idx_screenshots_browser ON screenshots(browser_id); CREATE INDEX IF NOT EXISTS idx_browser_instances_type ON browser_instances(type); CREATE INDEX IF NOT EXISTS idx_browser_instances_active ON browser_instances(is_active); `); } // Parse URL into components parseUrl(url) { try { // Handle URLs that might not have a protocol let urlToParse = url; if (!url.startsWith('http://') && !url.startsWith('https://')) { urlToParse = 'http://' + url; } const urlObj = new URL(urlToParse); // Remove trailing slash from pathname for consistency let pathname = urlObj.pathname; if (pathname.endsWith('/') && pathname.length > 1) { pathname = pathname.slice(0, -1); } return { hostname: urlObj.hostname + (urlObj.port ? `:${urlObj.port}` : ''), pathname, query: urlObj.search, hash: urlObj.hash }; } catch (error) { Logger.error(`Failed to parse URL: ${url}`, error); throw error; } } // Find screenshot by ID findById(id) { const rows = this.db.all(` SELECT * FROM screenshots WHERE id = ? `, [id]); if (!rows || rows.length === 0) return null; const row = rows[0]; return { ...row, timestamp: new Date(row.timestamp) }; } // Find latest screenshot by URL findLatestByUrl(hostname, pathname) { Logger.info(`[findLatestByUrl] Searching for hostname: '${hostname}', pathname: '${pathname}'`); const rows = this.db.all(` SELECT * FROM screenshots WHERE hostname = ? AND pathname = ? ORDER BY timestamp DESC LIMIT 1 `, [hostname, pathname]); if (!rows || rows.length === 0) { Logger.info('[findLatestByUrl] No match found'); return null; } const row = rows[0]; Logger.info(`[findLatestByUrl] Found match with id: ${row.id}`); return { ...row, timestamp: new Date(row.timestamp) }; } // Find all screenshots by URL findAllByUrl(hostname, pathname) { const rows = this.db.all(` SELECT * FROM screenshots WHERE hostname = ? AND pathname = ? ORDER BY timestamp DESC `, [hostname, pathname]); return rows.map(row => ({ ...row, timestamp: new Date(row.timestamp) })); } // Find all screenshots findAll() { const rows = this.db.all(` SELECT * FROM screenshots ORDER BY timestamp DESC `); return rows.map(row => ({ ...row, timestamp: new Date(row.timestamp) })); } // Insert new screenshot insert(record) { Logger.info(`Inserting screenshot: ${record.id}`); Logger.info(`Hostname: ${record.hostname}`); Logger.info(`Pathname: ${record.pathname}`); Logger.info(`Query: ${record.query}`); Logger.info(`Hash: ${record.hash}`); Logger.info(`Checkpoint ID: ${record.checkpoint_id}`); Logger.info(`Browser ID: ${record.browser_id}`); Logger.info(`Timestamp: ${record.timestamp}`); this.db.run(` INSERT INTO screenshots ( id, hostname, pathname, query, hash, checkpoint_id, timestamp, mime_type, description, browser_id, browser_type, session_id ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) `, [ record.id, record.hostname, record.pathname, record.query, record.hash, record.checkpoint_id, record.timestamp instanceof Date ? record.timestamp.toISOString() : record.timestamp, record.mime_type, record.description, record.browser_id || null, record.browser_type || null, record.session_id || null ]); } // Delete screenshot by ID deleteById(id) { const result = this.db.run(` DELETE FROM screenshots WHERE id = ? `, [id]); return result.changes > 0; } // Browser persistence methods // Insert or update browser instance saveBrowserInstance(id, type, displayName, metadata) { const metadataJson = metadata ? JSON.stringify(metadata) : null; const now = new Date().toISOString(); // Use REPLACE for upsert behavior this.db.run(` REPLACE INTO browser_instances ( id, type, display_name, metadata, created_at, last_used_at, is_active ) VALUES (?, ?, ?, ?, ?, ?, 1) `, [id, type, displayName || null, metadataJson, now, now]); Logger.info(`Browser instance saved: ${id} (${type})`); } // Update browser last used timestamp updateBrowserLastUsed(id) { this.db.run(` UPDATE browser_instances SET last_used_at = ?, is_active = 1 WHERE id = ? `, [new Date().toISOString(), id]); } // Mark browser as inactive deactivateBrowser(id) { this.db.run(` UPDATE browser_instances SET is_active = 0 WHERE id = ? `, [id]); Logger.info(`Browser marked as inactive: ${id}`); } // Get browser instance from database getBrowserInstance(id) { const rows = this.db.all(` SELECT * FROM browser_instances WHERE id = ? AND is_active = 1 `, [id]); if (!rows || rows.length === 0) return null; const row = rows[0]; return { ...row, metadata: row.metadata ? JSON.parse(row.metadata) : null, created_at: new Date(row.created_at), last_used_at: new Date(row.last_used_at), is_active: Boolean(row.is_active) }; } // Get all active browser instances getAllActiveBrowsers() { const rows = this.db.all(` SELECT * FROM browser_instances WHERE is_active = 1 ORDER BY last_used_at DESC `); return rows.map(row => ({ ...row, metadata: row.metadata ? JSON.parse(row.metadata) : null, created_at: new Date(row.created_at), last_used_at: new Date(row.last_used_at), is_active: Boolean(row.is_active) })); } // Delete browser instance from database deleteBrowserInstance(id) { const result = this.db.run(` DELETE FROM browser_instances WHERE id = ? `, [id]); return result.changes > 0; } // Close database connection close() { this.db.close(); } } // Singleton instance let instance = null; export function getScreenshotDB() { if (!instance) { instance = new ScreenshotDB(); } return instance; } export function closeScreenshotDB() { if (instance) { instance.close(); instance = null; } } //# sourceMappingURL=screenshot-db.js.map