open-brainy/examples/brainy-service-template/src/augmentations/websocketAugmentation.js
David Snelling ee003a9473 feat: add zero-configuration Brainy service template with augmentation-first architecture
- Implement WebSocket augmentation for real-time communication
- Implement WebRTC augmentation for peer-to-peer connections
- Implement HTTP augmentation as minimal REST fallback
- Add auto-discovery augmentation for data pattern analysis
- Add adaptive storage augmentation for intelligent resource management
- Add environment adapter augmentation for universal compatibility
- Template auto-detects environment (browser, Node.js, serverless, containers)
- Intelligent transport selection (WebRTC → WebSocket → HTTP)
- Automatic storage optimization (memory → filesystem → S3)
- Zero configuration required - just npm start
- Includes intelligent verb scoring by default
- Works in any environment without configuration
- Full documentation and examples included
2025-08-06 18:17:32 -07:00

165 lines
No EOL
8.8 KiB
JavaScript

import { WebSocket, WebSocketServer } from 'ws'
import { logger } from '../utils/logger.js'
/**
* WebSocket Augmentation - Real-time communication using Brainy's native augmentation system
* Handles real-time queries, updates, and bidirectional communication
*/
export class WebSocketAugmentation {
constructor(options = {}) {
this.type = 'WEBSOCKET'
this.priority = 2
this.options = {
port: options.port || 3001,
enableRealtime: options.enableRealtime !== false,
enableBroadcast: options.enableBroadcast !== false,
heartbeatInterval: options.heartbeatInterval || 30000,
...options
}
this.server = null
this.clients = new Set()
this.db = null
this.heartbeatTimer = null
}
async augment(brainyData, context) {
this.db = brainyData
try {
// Create WebSocket server
this.server = new WebSocketServer({
port: this.options.port,
perMessageDeflate: true
})
this.server.on('connection', (ws, request) => {
this.handleConnection(ws, request)
})
// Start heartbeat for connection management
if (this.options.heartbeatInterval > 0) {
this.startHeartbeat()
}
logger.info('WebSocket augmentation initialized', {
port: this.options.port,
features: {
realtime: this.options.enableRealtime,
broadcast: this.options.enableBroadcast
}
})
// Hook into Brainy events if real-time is enabled
if (this.options.enableRealtime) {
this.setupRealtimeHooks()
}
} catch (error) {
logger.error('Failed to initialize WebSocket augmentation:', error)
throw error
}
}
handleConnection(ws, request) {
const clientId = this.generateClientId()
ws.clientId = clientId
this.clients.add(ws)
logger.debug('WebSocket client connected', {
clientId,
ip: request.socket.remoteAddress,
totalClients: this.clients.size
})
// Send welcome message
this.send(ws, {
type: 'welcome',
clientId,
capabilities: {
realtime: this.options.enableRealtime,
broadcast: this.options.enableBroadcast,
commands: ['search', 'add', 'addVerb', 'get', 'subscribe', 'unsubscribe']
}
})
ws.on('message', (data) => {
this.handleMessage(ws, data)
})
ws.on('close', () => {
this.clients.delete(ws)
logger.debug('WebSocket client disconnected', {
clientId,
totalClients: this.clients.size
})
})
ws.on('error', (error) => {
logger.warn('WebSocket client error:', { clientId, error: error.message })
})
// Keep connection alive
ws.isAlive = true
ws.on('pong', () => {
ws.isAlive = true
})
}
async handleMessage(ws, data) {
try {
const message = JSON.parse(data.toString())
const { id, type, payload } = message
let result
switch (type) {
case 'search':
result = await this.handleSearch(payload)
break
case 'add':
result = await this.handleAdd(payload)
break
case 'addVerb':
result = await this.handleAddVerb(payload)
break
case 'get':
result = await this.handleGet(payload)
break
case 'subscribe':
result = await this.handleSubscribe(ws, payload)
break
case 'unsubscribe':
result = await this.handleUnsubscribe(ws, payload)
break
case 'ping':
result = { pong: Date.now() }
break
default:\n throw new Error(`Unknown message type: ${type}`)
}
// Send response
this.send(ws, {\n id,\n type: 'response',\n payload: result\n })
} catch (error) {\n logger.warn('WebSocket message handling error:', error)\n this.send(ws, {\n id: message?.id,\n type: 'error',\n payload: {\n message: error.message,\n code: error.code || 'UNKNOWN_ERROR'\n }\n })\n }\n }
async handleSearch(payload) {\n const { query, limit = 10, threshold = 0.7, options = {} } = payload\n \n if (!query) {\n throw new Error('Query is required for search')\n }\n\n const results = await this.db.search(query, limit, { threshold, ...options })\n \n return {\n query,\n results,\n count: results.length,\n timestamp: new Date().toISOString()\n }\n }
async handleAdd(payload) {\n const { data, metadata = {}, options = {} } = payload\n \n if (!data) {\n throw new Error('Data is required for add operation')\n }\n\n const enhancedMetadata = {\n ...metadata,\n source: 'websocket',\n timestamp: new Date().toISOString()\n }\n\n const id = await this.db.add(data, enhancedMetadata, options)\n \n const result = {\n id,\n data,\n metadata: enhancedMetadata\n }\n\n // Broadcast to subscribers if enabled\n if (this.options.enableBroadcast) {\n this.broadcast({\n type: 'entityAdded',\n payload: result\n })\n }\n\n return result\n }
async handleAddVerb(payload) {\n const { sourceId, targetId, verbType, weight, metadata = {}, options = {} } = payload\n \n if (!sourceId || !targetId || !verbType) {\n throw new Error('sourceId, targetId, and verbType are required')\n }\n\n const enhancedMetadata = {\n ...metadata,\n source: 'websocket',\n timestamp: new Date().toISOString()\n }\n\n const verbId = await this.db.addVerb(sourceId, targetId, verbType, {\n weight,\n metadata: enhancedMetadata,\n ...options\n })\n \n const result = {\n id: verbId,\n sourceId,\n targetId,\n verbType,\n weight,\n metadata: enhancedMetadata\n }\n\n // Broadcast to subscribers if enabled\n if (this.options.enableBroadcast) {\n this.broadcast({\n type: 'verbAdded',\n payload: result\n })\n }\n\n return result\n }
async handleGet(payload) {\n const { id } = payload\n \n if (!id) {\n throw new Error('ID is required for get operation')\n }\n\n const entity = await this.db.get(id)\n \n if (!entity) {\n throw new Error(`Entity with ID ${id} not found`)\n }\n\n return entity\n }
async handleSubscribe(ws, payload) {\n const { events = ['entityAdded', 'verbAdded'] } = payload\n \n if (!ws.subscriptions) {\n ws.subscriptions = new Set()\n }\n \n events.forEach(event => ws.subscriptions.add(event))\n \n return {\n subscribed: Array.from(ws.subscriptions),\n message: 'Successfully subscribed to events'\n }\n }
async handleUnsubscribe(ws, payload) {\n const { events = [] } = payload\n \n if (!ws.subscriptions) {\n return { message: 'No active subscriptions' }\n }\n \n if (events.length === 0) {\n // Unsubscribe from all\n ws.subscriptions.clear()\n } else {\n events.forEach(event => ws.subscriptions.delete(event))\n }\n \n return {\n subscribed: Array.from(ws.subscriptions),\n message: 'Successfully unsubscribed from events'\n }\n }
setupRealtimeHooks() {\n // Hook into Brainy's internal events for real-time updates\n // This would integrate with Brainy's event system once available\n logger.debug('Real-time hooks configured for WebSocket augmentation')\n }\n\n broadcast(message, filter = null) {\n if (!this.options.enableBroadcast) return\n \n const data = JSON.stringify(message)\n \n this.clients.forEach(client => {\n if (client.readyState === WebSocket.OPEN) {\n // Check if client is subscribed to this event type\n if (client.subscriptions && client.subscriptions.has(message.type)) {\n // Apply filter if provided\n if (!filter || filter(client)) {\n client.send(data)\n }\n }\n }\n })\n }\n\n send(ws, message) {\n if (ws.readyState === WebSocket.OPEN) {\n ws.send(JSON.stringify(message))\n }\n }\n\n startHeartbeat() {\n this.heartbeatTimer = setInterval(() => {\n this.clients.forEach(ws => {\n if (!ws.isAlive) {\n ws.terminate()\n return\n }\n \n ws.isAlive = false\n ws.ping()\n })\n }, this.options.heartbeatInterval)\n }\n\n generateClientId() {\n return `ws_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`\n }\n\n async cleanup() {\n if (this.heartbeatTimer) {\n clearInterval(this.heartbeatTimer)\n }\n \n if (this.server) {\n this.server.close()\n }\n \n // Close all client connections\n this.clients.forEach(client => {\n client.close()\n })\n this.clients.clear()\n \n logger.info('WebSocket augmentation cleaned up')\n }\n}