# SSE Implementation Guide
## Quick Start
This guide will help you implement and test the SSE-based session architecture.
## Step 1: Integration with server.js
Add these lines to your existing `/home/uroma/obsidian-web-interface/server.js`:
```javascript
// After existing imports (around line 12)
const eventBus = require('./services/event-bus');
const sseManager = require('./services/sse-manager');
const sessionRoutes = require('./routes/session-routes');
const sseRoutes = require('./routes/sse-routes');
// After existing middleware (around line 270)
// Register new API routes
app.use('/api', sessionRoutes);
app.use('/api', sseRoutes);
// Add monitoring endpoint
app.get('/api/debug/metrics', (req, res) => {
res.json({
eventBus: eventBus.getMetrics(),
sse: sseManager.getStats(),
timestamp: Date.now()
});
});
// Update graceful shutdown (around line 2600)
const originalCleanup = /* existing cleanup logic or null */;
process.on('SIGTERM', async () => {
console.log('[Server] Starting graceful shutdown...');
// Cleanup SSE connections
sseManager.cleanup();
// ... existing cleanup ...
process.exit(0);
});
```
## Step 2: Modify ClaudeService to emit events
In `/home/uroma/obsidian-web-interface/services/claude-service.js`:
```javascript
// Add at top of file
const eventBus = require('./event-bus');
// Find the section where Claude output is handled
// Replace callback-based approach with EventBus emits
// Example: When output is received
handleSessionOutput(sessionId, output) {
// Old way:
// this.emit('session-output', { sessionId, output });
// New way:
eventBus.emit('session-output', {
sessionId,
type: 'stdout',
content: output,
timestamp: Date.now()
});
}
// Example: When operations are detected
handleOperationsDetected(sessionId, operations, response) {
eventBus.emit('operations-detected', {
sessionId,
operations,
response: response.substring(0, 500) + '...'
});
}
```
## Step 3: Test SSE Endpoint
### Test 1: Basic SSE Connection
```bash
# Terminal 1: Start server
cd /home/uroma/obsidian-web-interface
npm start
# Terminal 2: Create a test session
curl -X POST http://localhost:3010/claude/api/claude/sessions \
-H "Content-Type: application/json" \
-d '{"workingDir":"/home/uroma"}'
# Note the session ID from response
# Terminal 3: Test SSE connection (replace SESSION_ID)
curl -N http://localhost:3010/api/session/SESSION_ID/events
```
### Test 2: SSE with curl (watch events)
```bash
# Connect to SSE and watch for events
curl -N -H "Accept: text/event-stream" \
http://localhost:3010/api/session/SESSION_ID/events
```
### Test 3: Send command via REST API
```bash
# Send a command
curl -X POST http://localhost:3010/api/session/SESSION_ID/prompt \
-H "Content-Type: application/json" \
-d '{"command":"ls -la"}'
# Watch for output in your SSE connection (Test 2)
```
## Step 4: Browser Testing
Create a test HTML file `/home/uroma/obsidian-web-interface/public/test-sse.html`:
```html
SSE Test
SSE Test
```
Access it at: `http://localhost:3010/test-sse.html`
## Step 5: Monitor Metrics
```bash
# Check EventBus and SSE metrics
curl http://localhost:3010/api/debug/metrics
# Expected response:
{
"eventBus": {
"eventsEmitted": 1234,
"eventsByType": { "session-output": 800, "session-error": 5, ... },
"listenerCounts": { "session-output-session-123": 1, ... },
"activeListeners": 10
},
"sse": {
"totalSessions": 3,
"totalConnections": 5,
"sessions": { "session-123": 2, "session-456": 1, ... },
"totalCreated": 50,
"totalClosed": 45,
"activeHeartbeats": 5
},
"timestamp": 1234567890
}
```
## Step 6: Test Reconnection
1. Start SSE connection in browser
2. Kill and restart server
3. Verify automatic reconnection with exponential backoff
4. Check browser console for reconnection logs
## Step 7: Load Testing
```javascript
// In browser console, run:
const connections = [];
for (let i = 0; i < 10; i++) {
const sessionId = 'session-123'; // Use a real session ID
const stream = new SessionEventStream(sessionId);
connections.push(stream);
}
// Check metrics
fetch('/api/debug/metrics').then(r => r.json()).then(console.log);
// Cleanup
connections.forEach(s => s.disconnect());
```
## Step 8: nginx Configuration
If using nginx as reverse proxy, add this configuration:
```nginx
# In your nginx server block
location /api/session/ {
# Disable buffering for SSE
proxy_buffering off;
proxy_cache off;
# Pass to backend
proxy_pass http://localhost:3010;
proxy_http_version 1.1;
proxy_set_header Connection '';
proxy_set_header Cache-Control no-cache;
proxy_set_header X-Accel-Buffering no;
# Increase timeouts for long-lived connections
proxy_read_timeout 86400s; # 24 hours
proxy_send_timeout 86400s;
# Ensure no buffering
proxy_buffering off;
}
```
## Testing Checklist
- [ ] SSE connection established successfully
- [ ] Events received in real-time
- [ ] Multiple clients can connect to same session
- [ ] Reconnection works on connection drop
- [ ] Heartbeat prevents timeout
- [ ] Metrics endpoint returns correct data
- [ ] Session validation works (404 for invalid session)
- [ ] Command sent via REST API produces output via SSE
- [ ] No memory leaks after extended use
- [ ] Works through nginx reverse proxy
## Troubleshooting
### Issue: SSE connection closes immediately
**Solution:** Check nginx configuration, ensure `X-Accel-Buffering: no` is set.
### Issue: No events received
**Solution:** Check session ID is valid and session exists. Check browser console for errors.
### Issue: Frequent reconnections
**Solution:** Check network stability, increase `heartbeatTimeout` in client options.
### Issue: Memory usage increasing
**Solution:** Check EventBus listeners are properly unsubscribed on disconnect.
## Next Steps
After successful testing:
1. Update existing UI to use SSE instead of WebSocket
2. Add feature flag for gradual rollout
3. Monitor production metrics
4. Deprecate WebSocket endpoint
5. Remove legacy code after migration complete