A VNC-like collaborative web browsing system where multiple users can control the same browser tab in real-time.
This system allows multiple users to join a session and collaboratively browse websites together. Unlike traditional screen sharing, all participants can actively control the browser - clicking, scrolling, typing, and navigating.
- Express Server: HTTP server for static files and REST endpoints
- Socket.IO: WebSocket server for real-time communication
- Puppeteer: Headless Chrome browser automation
- Session Manager: Tracks active sessions and participants
// One browser instance per session (not per user)
const browserInstances = new Map(); // sessionId -> { browser, page, screenshotInterval }
// When first user joins session
await puppeteer.launch({
headless: false,
defaultViewport: { width: 1920, height: 1080 }
});- Screenshot Capture: 10 FPS JPEG screenshots (100ms intervals)
- Broadcast: Screenshots sent to all session participants via WebSocket
- Quality: 80% JPEG compression for performance
- Mouse Events: Click, move, scroll → Puppeteer mouse API
- Keyboard Events: Keydown, keyup, type → Puppeteer keyboard API
- Real-time: All user inputs immediately applied to browser
- Socket.IO Client: WebSocket communication with server
- Screen Display: HTML
<img>element showing browser screenshots - Input Capture: Mouse/keyboard event listeners
- Cursor Sync: Multi-user cursor visualization
// Mouse events captured and sent to server
sharedScreen.addEventListener('click', (e) => {
const rect = sharedScreen.getBoundingClientRect();
const x = (e.clientX - rect.left) * (1920 / rect.width);
const y = (e.clientY - rect.top) * (1080 / rect.height);
socket.emit('mouse-event', { type: 'click', x, y });
});- Client → Server: Scale from display size to browser viewport (1920x1080)
- Server → Client: Scale cursor positions for different screen sizes
User 1 joins session "ABC123"
├── Session doesn't exist
├── Create new session object
├── Launch Puppeteer browser instance
├── Start screenshot streaming (10 FPS)
└── User joins session room
User 2 joins same session "ABC123"
├── Session exists
├── Reuse existing browser instance
├── User joins session room
└── Receives current screenshot stream
User clicks on page
├── Client captures click coordinates
├── Scales coordinates to browser viewport
├── Sends via WebSocket to server
├── Server applies click via Puppeteer
├── Browser updates
├── Screenshot captured
└── Broadcast to all session participants
Last user leaves session
├── Remove user from session
├── Stop screenshot streaming
├── Close Puppeteer browser
└── Delete session object
// User input events
socket.emit('mouse-event', { type: 'click', x, y, button });
socket.emit('keyboard-event', { type: 'keydown', key });
socket.emit('scroll-event', { deltaX, deltaY });
socket.emit('navigate-to', { url });// Screen updates
socket.emit('screen-update', { screenshot: 'data:image/jpeg;base64,...' });
socket.emit('cursor-update', { userId, userName, x, y });
socket.emit('navigate', { url });GET / # Main application
GET /session-screenshot/:id # Get current session screenshot
POST /join-session # Join existing session
{
id: "ABC123",
teacher: { id: "socket1", name: "John" },
students: [
{ id: "socket2", name: "Jane" },
{ id: "socket3", name: "Bob" }
],
currentUrl: "https://example.com",
interactions: []
}{
browser: puppeteerBrowser,
page: puppeteerPage,
screenshotInterval: setInterval(...)
}- Format: JPEG (smaller than PNG)
- Quality: 80% compression
- Frequency: 10 FPS (balance between smooth experience and bandwidth)
- Resolution: Fixed 1920x1080 viewport
- Browser instances automatically cleaned up when sessions end
- Screenshot intervals cleared on session close
- Socket rooms properly managed
- Concurrent Sessions: Limited by server memory (each browser ~100MB RAM)
- Users per Session: No hard limit, but performance degrades with many cursors
- Network: ~200KB/s per user for screenshot streaming
// Puppeteer security flags
args: ['--no-sandbox', '--disable-setuid-sandbox']- Coordinate bounds checking
- URL validation for navigation
- Session ID validation
- CORS enabled for specific origins
- WebSocket origin validation
- No sensitive data in screenshots
├── Node.js server (port 3000)
├── Puppeteer browsers (dynamic)
└── Static files served from /public
- Headless Mode: Set
headless: truefor production - Resource Limits: Container memory limits for browser instances
- Load Balancing: Session affinity required (users must hit same server)
- CDN: Serve static assets from CDN
- Backend: Node.js, Express, Socket.IO, Puppeteer
- Frontend: Vanilla JavaScript, HTML5, CSS3
- Real-time: WebSocket communication
- Browser Automation: Chrome/Chromium via Puppeteer
- Image Processing: JPEG compression, Base64 encoding
| Approach | Pros | Cons |
|---|---|---|
| VNC-like (Current) | Universal website support, true collaboration | Higher bandwidth, server resources |
| WebRTC Screen Share | Low latency, P2P | View-only, complex signaling |
| DOM Synchronization | Lightweight, fast | Limited website compatibility |
| iframe Proxying | Simple implementation | Many sites block iframes |
- Video Streaming: H.264 encoding instead of JPEG screenshots
- Delta Compression: Only send screen regions that changed
- CDN Integration: Serve screenshots via CDN edge locations
- Multi-tab Support: Multiple browser tabs per session
- Recording: Session playback functionality
- Voice Chat: Integrated WebRTC audio communication
- Permissions: Granular user permissions (view-only, click-only, etc.)
- Kubernetes: Container orchestration for browser instances
- Redis: Distributed session storage
- Load Balancer: Sticky session routing
MIT License - see LICENSE file for details.