feat: Migrate from Socket.IO to Server-Sent Events (SSE)

- Replace Socket.IO with SSE for real-time server-to-client communication
- Add SSE service with client management and topic-based subscriptions
- Implement SSE authentication middleware and streaming endpoints
- Update all backend routes to emit SSE events instead of Socket.IO
- Create SSE context provider for frontend with EventSource API
- Update all frontend components to use SSE instead of Socket.IO
- Add comprehensive SSE tests for both backend and frontend
- Remove Socket.IO dependencies and legacy files
- Update documentation to reflect SSE architecture

Benefits:
- Simpler architecture using native browser EventSource API
- Lower bundle size (removed socket.io-client dependency)
- Better compatibility with reverse proxies and load balancers
- Reduced resource usage for Raspberry Pi deployment
- Standard HTTP-based real-time communication

🤖 Generated with [AI Assistant]

Co-Authored-By: AI Assistant <noreply@ai-assistant.com>
This commit is contained in:
William Valentin
2025-12-05 22:49:22 -08:00
parent b5ee7571c9
commit bb9c8ec1c3
571 changed files with 156739 additions and 1350 deletions
@@ -2,7 +2,7 @@ import React from 'react';
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import { BrowserRouter } from 'react-router-dom';
import { AuthContext } from '../../context/AuthContext';
import { SocketContext } from '../../context/SocketContext';
import { SSEContext } from '../../context/SSEContext';
import Events from '../Events';
import axios from 'axios';
@@ -13,13 +13,15 @@ const mockAuthContext = {
logout: jest.fn(),
};
const mockSocketContext = {
socket: null,
const mockSSEContext = {
connected: true,
notifications: [],
on: jest.fn(),
off: jest.fn(),
joinEvent: jest.fn(),
leaveEvent: jest.fn(),
subscribe: jest.fn().mockResolvedValue({ subscribed: [] }),
unsubscribe: jest.fn().mockResolvedValue({ unsubscribed: [] }),
clearNotification: jest.fn(),
clearAllNotifications: jest.fn(),
};
jest.mock('axios');
@@ -83,9 +85,9 @@ describe('Events Component', () => {
return render(
<BrowserRouter>
<AuthContext.Provider value={mockAuthContext}>
<SocketContext.Provider value={mockSocketContext}>
<SSEContext.Provider value={mockSSEContext}>
<Events />
</SocketContext.Provider>
</SSEContext.Provider>
</AuthContext.Provider>
</BrowserRouter>
);
@@ -296,19 +298,19 @@ describe('Events Component', () => {
});
it('handles real-time updates', async () => {
const { on } = mockSocketContext;
const { on } = mockSSEContext;
renderEvents();
await waitFor(() => {
// Simulate receiving a new event via socket
const socketCallback = on.mock.calls[0][1];
// Simulate receiving a new event via SSE
const sseCallback = on.mock.calls[0][1];
const newEventData = {
type: 'new_event',
data: { ...mockEvents[0], _id: 'event5' }
event: { ...mockEvents[0], _id: 'event5' }
};
socketCallback(newEventData);
sseCallback(newEventData);
// Verify new event appears in the list
expect(screen.getByText('Community Cleanup Day')).toBeInTheDocument();