@sofianedjerbi/knowledge-tree-mcp
Version:
MCP server for hierarchical project knowledge management
23 lines • 10.1 kB
JSON
{
"title": "WebSocket Connection Recovery Edge Cases",
"priority": "EDGE-CASE",
"problem": "WebSocket connections in the Knowledge Tree MCP web interface can fail in specific browser scenarios, network conditions, or system states that require specialized recovery handling.",
"solution": "## Browser-Specific Edge Cases\n\n### 1. Safari Background Tab Throttling\n```typescript\n// EDGE CASE: Safari aggressively throttles background tabs\n// WebSocket ping/pong fails, connection appears dead but isn't closed\n\nlet isBackgroundTab = false;\nlet pingInterval: NodeJS.Timeout;\n\ndocument.addEventListener('visibilitychange', () => {\n isBackgroundTab = document.hidden;\n \n if (isBackgroundTab) {\n // Reduce ping frequency in background\n clearInterval(pingInterval);\n pingInterval = setInterval(sendPing, 30000); // 30s instead of 5s\n } else {\n // Resume normal ping frequency\n clearInterval(pingInterval);\n pingInterval = setInterval(sendPing, 5000);\n \n // Force reconnect if connection seems stale\n if (lastPongTime < Date.now() - 35000) {\n forceReconnect();\n }\n }\n});\n```\n\n### 2. Firefox Private Mode WebSocket Limits\n```typescript\n// EDGE CASE: Firefox private mode has lower WebSocket connection limits\nlet connectionAttempts = 0;\nconst MAX_FIREFOX_PRIVATE_ATTEMPTS = 3;\n\nfunction connectWithFirefoxWorkaround() {\n const isFirefoxPrivate = isFirefoxPrivateMode();\n \n if (isFirefoxPrivate && connectionAttempts >= MAX_FIREFOX_PRIVATE_ATTEMPTS) {\n // Fall back to polling mode\n console.warn('Firefox private mode: switching to HTTP polling');\n switchToPollingMode();\n return;\n }\n \n connectionAttempts++;\n attemptWebSocketConnection();\n}\n\nfunction isFirefoxPrivateMode(): boolean {\n try {\n return navigator.userAgent.includes('Firefox') && \n !window.indexedDB;\n } catch {\n return false;\n }\n}\n```\n\n### 3. Mobile Network Switching\n```typescript\n// EDGE CASE: Mobile browsers switching between WiFi/cellular\nlet networkType = getNetworkType();\n\nif ('connection' in navigator) {\n (navigator as any).connection.addEventListener('change', () => {\n const newNetworkType = getNetworkType();\n \n if (newNetworkType !== networkType) {\n console.log(`Network changed: ${networkType} -> ${newNetworkType}`);\n networkType = newNetworkType;\n \n // Force immediate reconnection on network change\n if (ws && ws.readyState === WebSocket.OPEN) {\n ws.close(1000, 'Network change');\n }\n \n // Wait for network to stabilize\n setTimeout(connectWebSocket, 2000);\n }\n });\n}\n\nfunction getNetworkType(): string {\n if ('connection' in navigator) {\n return (navigator as any).connection.effectiveType || 'unknown';\n }\n return 'unknown';\n}\n```\n\n## System-Level Edge Cases\n\n### 4. System Sleep/Resume Recovery\n```typescript\n// EDGE CASE: Computer goes to sleep, WebSocket becomes zombie connection\nlet lastActivityTime = Date.now();\nlet suspendedConnection = false;\n\n// Detect potential system suspend\nsetInterval(() => {\n const now = Date.now();\n const timeDiff = now - lastActivityTime;\n \n // If more than 2 minutes passed since last check, system likely suspended\n if (timeDiff > 120000) {\n console.warn('Potential system suspend detected');\n suspendedConnection = true;\n \n if (ws && ws.readyState === WebSocket.OPEN) {\n // Don't trust the connection state after suspend\n ws.close(1000, 'System suspend recovery');\n }\n \n setTimeout(connectWebSocket, 1000);\n }\n \n lastActivityTime = now;\n}, 60000); // Check every minute\n```\n\n### 5. Corporate Proxy WebSocket Blocking\n```typescript\n// EDGE CASE: Corporate proxies that block WebSocket upgrades\nlet proxyDetected = false;\nlet fallbackToPolling = false;\n\nfunction attemptConnectionWithProxyDetection() {\n const connectionTimeout = setTimeout(() => {\n if (!ws || ws.readyState === WebSocket.CONNECTING) {\n console.warn('WebSocket connection timeout - possible proxy blocking');\n proxyDetected = true;\n \n if (ws) {\n ws.close();\n }\n \n // Try with different WebSocket subprotocols\n tryAlternativeProtocols();\n }\n }, 5000);\n\n ws = new WebSocket(wsUrl);\n \n ws.onopen = () => {\n clearTimeout(connectionTimeout);\n proxyDetected = false;\n };\n\n ws.onerror = (error) => {\n clearTimeout(connectionTimeout);\n \n if (!proxyDetected) {\n // First proxy detection\n console.warn('WebSocket error - attempting proxy workarounds');\n tryProxyWorkarounds();\n } else {\n // Proxy confirmed, switch to polling\n console.warn('Confirmed proxy blocking - switching to HTTP polling');\n switchToPollingMode();\n }\n };\n}\n\nfunction tryAlternativeProtocols() {\n const protocols = ['ws', 'wss', 'chat', 'echo-protocol'];\n \n protocols.forEach((protocol, index) => {\n setTimeout(() => {\n try {\n const testWs = new WebSocket(wsUrl, protocol);\n testWs.onopen = () => {\n console.log(`Protocol ${protocol} works`);\n ws = testWs;\n setupWebSocketHandlers();\n };\n testWs.onerror = () => {\n if (index === protocols.length - 1) {\n switchToPollingMode();\n }\n };\n } catch (e) {\n if (index === protocols.length - 1) {\n switchToPollingMode();\n }\n }\n }, index * 1000);\n });\n}\n```\n\n### 6. Memory Pressure Connection Drops\n```typescript\n// EDGE CASE: Browser drops WebSocket under memory pressure\nlet memoryPressureDetected = false;\n\nif ('memory' in performance) {\n setInterval(() => {\n const memInfo = (performance as any).memory;\n const memoryUsage = memInfo.usedJSHeapSize / memInfo.jsHeapSizeLimit;\n \n if (memoryUsage > 0.9) {\n memoryPressureDetected = true;\n console.warn('High memory usage detected');\n \n // Reduce message frequency and clean up data\n if (ws && ws.readyState === WebSocket.OPEN) {\n // Request server to reduce update frequency\n ws.send(JSON.stringify({ \n type: 'setUpdateFrequency', \n frequency: 'low' \n }));\n }\n \n // Clean up old data\n cleanupOldKnowledgeData();\n } else if (memoryPressureDetected && memoryUsage < 0.7) {\n memoryPressureDetected = false;\n \n // Resume normal operation\n if (ws && ws.readyState === WebSocket.OPEN) {\n ws.send(JSON.stringify({ \n type: 'setUpdateFrequency', \n frequency: 'normal' \n }));\n }\n }\n }, 30000);\n}\n```",
"tags": [
"websocket",
"connection",
"recovery",
"edge-case",
"browser-specific"
],
"context": "These edge cases typically occur with specific browser versions, corporate proxies, mobile browsers switching networks, or when the system goes to sleep. Standard reconnection logic doesn't handle these scenarios.",
"examples": [
{
"title": "Complete Edge-Case Resistant WebSocket Manager",
"code": "class RobustWebSocketManager {\n private ws: WebSocket | null = null;\n private reconnectAttempts = 0;\n private maxReconnectAttempts = 10;\n private reconnectDelay = 1000;\n private isBackgroundTab = false;\n private lastPongTime = Date.now();\n private pollingMode = false;\n private networkType = 'unknown';\n\n constructor(private url: string) {\n this.setupBrowserEventListeners();\n }\n\n private setupBrowserEventListeners() {\n // Handle background tab throttling\n document.addEventListener('visibilitychange', () => {\n this.isBackgroundTab = document.hidden;\n this.adjustForBackgroundState();\n });\n\n // Handle network changes\n if ('connection' in navigator) {\n (navigator as any).connection.addEventListener('change', () => {\n this.handleNetworkChange();\n });\n }\n\n // Handle page unload\n window.addEventListener('beforeunload', () => {\n if (this.ws) {\n this.ws.close(1000, 'Page unload');\n }\n });\n }\n\n connect() {\n if (this.pollingMode) {\n this.startPolling();\n return;\n }\n\n try {\n this.ws = new WebSocket(this.url);\n this.setupWebSocketHandlers();\n \n // Connection timeout for proxy detection\n const timeout = setTimeout(() => {\n if (this.ws?.readyState === WebSocket.CONNECTING) {\n console.warn('Connection timeout - possible proxy issue');\n this.ws.close();\n this.handleConnectionFailure();\n }\n }, 5000);\n\n this.ws.addEventListener('open', () => {\n clearTimeout(timeout);\n this.reconnectAttempts = 0;\n console.log('WebSocket connected');\n });\n\n } catch (error) {\n console.error('WebSocket creation failed:', error);\n this.handleConnectionFailure();\n }\n }\n\n private handleConnectionFailure() {\n this.reconnectAttempts++;\n \n if (this.reconnectAttempts >= this.maxReconnectAttempts) {\n console.warn('Max reconnect attempts reached - switching to polling');\n this.switchToPolling();\n return;\n }\n\n const delay = Math.min(this.reconnectDelay * Math.pow(2, this.reconnectAttempts), 30000);\n console.log(`Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts})`);\n \n setTimeout(() => this.connect(), delay);\n }\n\n private switchToPolling() {\n this.pollingMode = true;\n this.startPolling();\n }\n\n private startPolling() {\n // Implement HTTP polling fallback\n setInterval(() => {\n fetch('/api/poll')\n .then(response => response.json())\n .then(data => this.handleMessage(data))\n .catch(error => console.error('Polling error:', error));\n }, this.isBackgroundTab ? 30000 : 5000);\n }\n}",
"language": "typescript"
}
],
"created_at": "2025-08-03T17:09:54.426Z",
"updated_at": "2025-08-04T11:13:48.159Z"
}