2025-11-12 18:07:42 +01:00
|
|
|
# Architecture - spotlight.cam
|
|
|
|
|
|
|
|
|
|
**Technical architecture and implementation details**
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## System Architecture
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
┌─────────────────┐
|
|
|
|
|
│ Web Browser │ (PWA - React + Vite + Tailwind)
|
|
|
|
|
│ (Android/iOS) │
|
|
|
|
|
└────────┬────────┘
|
|
|
|
|
│ HTTPS
|
|
|
|
|
▼
|
|
|
|
|
┌─────────────────┐
|
|
|
|
|
│ nginx │ (Reverse Proxy, Port 8080)
|
|
|
|
|
│ (Port 8080) │
|
|
|
|
|
└────────┬────────┘
|
|
|
|
|
│
|
|
|
|
|
┌────┴────┐
|
|
|
|
|
│ │
|
|
|
|
|
▼ ▼
|
|
|
|
|
┌────────┐ ┌────────────┐
|
|
|
|
|
│Frontend│ │ Backend │ (Node.js + Express + Socket.IO)
|
2025-11-20 22:34:05 +01:00
|
|
|
│(Vite) │ │ ✅ IMPL │
|
2025-11-12 18:07:42 +01:00
|
|
|
│:5173 │ │ :3000 │
|
|
|
|
|
└────────┘ └──────┬─────┘
|
|
|
|
|
│
|
|
|
|
|
▼
|
|
|
|
|
┌─────────────┐
|
2025-11-20 22:34:05 +01:00
|
|
|
│ PostgreSQL │ (Database with Prisma ORM)
|
|
|
|
|
│ ✅ IMPL │
|
2025-11-12 18:07:42 +01:00
|
|
|
└─────────────┘
|
|
|
|
|
|
|
|
|
|
WebRTC P2P Connection:
|
|
|
|
|
Browser A ←──────────────────────→ Browser B
|
|
|
|
|
(Direct P2P via WebRTC)
|
|
|
|
|
↑
|
|
|
|
|
│ (Signaling only via WebSocket)
|
|
|
|
|
│
|
|
|
|
|
Backend
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## Docker Compose Services
|
|
|
|
|
|
|
|
|
|
### Current Setup
|
|
|
|
|
|
|
|
|
|
**nginx:**
|
|
|
|
|
- Image: `nginx:alpine`
|
|
|
|
|
- Port: 8080 (host) → 80 (container)
|
|
|
|
|
- Volumes: nginx config files
|
|
|
|
|
- Purpose: Reverse proxy, serves frontend, will proxy `/api/*` to backend
|
|
|
|
|
|
|
|
|
|
**frontend:**
|
|
|
|
|
- Build: `./frontend/Dockerfile`
|
|
|
|
|
- Internal port: 5173 (Vite dev server)
|
|
|
|
|
- Volumes: hot reload support
|
|
|
|
|
- Environment: `NODE_ENV=development`, `VITE_HOST=0.0.0.0`
|
|
|
|
|
|
2025-11-20 22:34:05 +01:00
|
|
|
### Implemented Services
|
2025-11-12 18:07:42 +01:00
|
|
|
|
|
|
|
|
**backend:**
|
2025-11-20 22:34:05 +01:00
|
|
|
- Build: `./backend/Dockerfile` (dev) / `./backend/Dockerfile.prod` (prod)
|
2025-11-12 18:07:42 +01:00
|
|
|
- Internal port: 3000
|
2025-11-20 22:34:05 +01:00
|
|
|
- Environment: DB credentials, JWT secret, AWS SES, etc.
|
2025-11-12 18:07:42 +01:00
|
|
|
- Depends on: `db`
|
2025-11-20 22:34:05 +01:00
|
|
|
- Features: REST API, Socket.IO, JWT auth, Prisma ORM
|
|
|
|
|
- Tests: 223/223 passing (71% coverage)
|
2025-11-12 18:07:42 +01:00
|
|
|
|
|
|
|
|
**db:**
|
|
|
|
|
- Image: `postgres:15-alpine`
|
2025-11-20 22:34:05 +01:00
|
|
|
- Port: 5432 (exposed in dev, internal in prod)
|
2025-11-12 18:07:42 +01:00
|
|
|
- Volumes: PostgreSQL data persistence
|
|
|
|
|
- Environment: POSTGRES_USER, POSTGRES_PASSWORD, POSTGRES_DB
|
2025-11-20 22:34:05 +01:00
|
|
|
- Managed by: Prisma migrations
|
2025-11-12 18:07:42 +01:00
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## nginx Configuration
|
|
|
|
|
|
|
|
|
|
**File:** `nginx/conf.d/default.conf`
|
|
|
|
|
|
|
|
|
|
### Upstream
|
|
|
|
|
```nginx
|
|
|
|
|
upstream frontend {
|
|
|
|
|
server frontend:5173;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
upstream backend {
|
|
|
|
|
server backend:3000; # planned
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Routes
|
|
|
|
|
- `/` → frontend (React app)
|
|
|
|
|
- `/api/*` → backend (REST API) - planned
|
|
|
|
|
- `/socket.io/*` → backend (WebSocket) - planned
|
|
|
|
|
|
|
|
|
|
### Features
|
|
|
|
|
- WebSocket support (Upgrade headers for Vite HMR and future Socket.IO)
|
|
|
|
|
- Client max body size: 500M (for large video uploads via link)
|
|
|
|
|
- Proxy headers: X-Real-IP, X-Forwarded-For, etc.
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## Frontend Architecture
|
|
|
|
|
|
|
|
|
|
### Tech Stack
|
|
|
|
|
- **React 18** - UI library
|
|
|
|
|
- **Vite** - Build tool, dev server
|
|
|
|
|
- **Tailwind CSS v3.4.0** - Styling (NOT v4 - compatibility issues)
|
|
|
|
|
- **React Router** - Client-side routing
|
|
|
|
|
- **Context API** - State management (will add Socket.IO client, WebRTC logic)
|
|
|
|
|
|
|
|
|
|
### Folder Structure
|
|
|
|
|
```
|
|
|
|
|
frontend/
|
|
|
|
|
├── public/ # Static assets
|
|
|
|
|
├── src/
|
|
|
|
|
│ ├── main.jsx # Entry point
|
|
|
|
|
│ ├── App.jsx # Router setup
|
|
|
|
|
│ ├── pages/ # Page components
|
|
|
|
|
│ │ ├── LoginPage.jsx
|
|
|
|
|
│ │ ├── RegisterPage.jsx
|
|
|
|
|
│ │ ├── EventsPage.jsx
|
|
|
|
|
│ │ ├── EventChatPage.jsx
|
|
|
|
|
│ │ ├── MatchChatPage.jsx # ← Main WebRTC mockup
|
|
|
|
|
│ │ ├── RatePartnerPage.jsx
|
|
|
|
|
│ │ └── HistoryPage.jsx
|
|
|
|
|
│ ├── components/
|
|
|
|
|
│ │ └── layout/
|
|
|
|
|
│ │ ├── Layout.jsx
|
|
|
|
|
│ │ └── Navbar.jsx
|
|
|
|
|
│ ├── contexts/
|
|
|
|
|
│ │ └── AuthContext.jsx # Mock auth (will become real)
|
|
|
|
|
│ ├── mocks/ # Mock data (will be replaced by API calls)
|
|
|
|
|
│ │ ├── users.js
|
|
|
|
|
│ │ ├── events.js
|
|
|
|
|
│ │ ├── messages.js
|
|
|
|
|
│ │ ├── matches.js
|
|
|
|
|
│ │ └── ratings.js
|
|
|
|
|
│ └── index.css # Tailwind imports
|
|
|
|
|
├── tailwind.config.js # Tailwind v3.4.0 config
|
|
|
|
|
├── vite.config.js # Vite config
|
|
|
|
|
└── package.json
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### State Management
|
|
|
|
|
|
|
|
|
|
**Current (Mock):**
|
|
|
|
|
- AuthContext: mock login/register/logout with localStorage
|
|
|
|
|
- Local state in components
|
|
|
|
|
|
|
|
|
|
**Planned:**
|
|
|
|
|
- Socket.IO client for real-time chat
|
|
|
|
|
- WebRTC connection state
|
|
|
|
|
- Redux/Zustand (if needed for complex state)
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## Backend Architecture (Planned)
|
|
|
|
|
|
|
|
|
|
### Tech Stack
|
|
|
|
|
- **Node.js** - Runtime
|
|
|
|
|
- **Express** - Web framework
|
|
|
|
|
- **Socket.IO** - WebSocket for chat and WebRTC signaling
|
|
|
|
|
- **PostgreSQL** - Database
|
|
|
|
|
- **Prisma/Knex** - ORM/Query builder
|
|
|
|
|
- **bcrypt** - Password hashing
|
|
|
|
|
- **JWT** - Authentication tokens
|
|
|
|
|
|
|
|
|
|
### Folder Structure (Proposed)
|
|
|
|
|
```
|
|
|
|
|
backend/
|
|
|
|
|
├── src/
|
|
|
|
|
│ ├── server.js # Entry point
|
|
|
|
|
│ ├── app.js # Express app setup
|
|
|
|
|
│ ├── routes/ # REST API routes
|
|
|
|
|
│ │ ├── auth.js # POST /api/auth/register, /login
|
|
|
|
|
│ │ ├── users.js # GET /api/users/me, etc.
|
|
|
|
|
│ │ ├── events.js # GET /api/events, etc.
|
|
|
|
|
│ │ ├── matches.js # POST /api/matches, etc.
|
|
|
|
|
│ │ └── ratings.js # POST /api/ratings, etc.
|
|
|
|
|
│ ├── controllers/ # Business logic
|
|
|
|
|
│ ├── models/ # Database models (Prisma schema or Knex queries)
|
|
|
|
|
│ ├── middleware/ # Auth, validation, error handling
|
|
|
|
|
│ ├── socket/ # Socket.IO event handlers
|
|
|
|
|
│ │ ├── chat.js # Event chat, private chat
|
|
|
|
|
│ │ └── webrtc.js # WebRTC signaling (SDP/ICE)
|
|
|
|
|
│ └── utils/ # Helpers
|
|
|
|
|
├── prisma/ # (if using Prisma)
|
|
|
|
|
│ └── schema.prisma # Database schema
|
|
|
|
|
├── migrations/ # Database migrations
|
|
|
|
|
├── .env.example
|
|
|
|
|
└── package.json
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### API Endpoints (Planned)
|
|
|
|
|
|
|
|
|
|
**Auth:**
|
|
|
|
|
- `POST /api/auth/register` - Create account
|
|
|
|
|
- `POST /api/auth/login` - Login, get JWT
|
|
|
|
|
- `POST /api/auth/logout` - Logout (optional, JWT is stateless)
|
|
|
|
|
|
|
|
|
|
**Users:**
|
|
|
|
|
- `GET /api/users/me` - Get current user
|
|
|
|
|
- `PATCH /api/users/me` - Update profile
|
|
|
|
|
- `GET /api/users/:id` - Get user profile
|
|
|
|
|
|
|
|
|
|
**Events:**
|
|
|
|
|
- `GET /api/events` - List all events
|
|
|
|
|
- `GET /api/events/:id` - Event details
|
|
|
|
|
- `POST /api/events/:id/join` - Join event
|
|
|
|
|
|
|
|
|
|
**Matches:**
|
|
|
|
|
- `GET /api/matches` - User's match history
|
|
|
|
|
- `POST /api/matches` - Create match
|
|
|
|
|
- `PATCH /api/matches/:id` - Update match status
|
|
|
|
|
|
|
|
|
|
**Ratings:**
|
|
|
|
|
- `POST /api/ratings` - Rate partner
|
|
|
|
|
- `GET /api/ratings/user/:id` - User's ratings
|
|
|
|
|
- `GET /api/ratings/stats/:id` - Rating statistics
|
|
|
|
|
|
|
|
|
|
**Reports:**
|
|
|
|
|
- `POST /api/reports` - Report user
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## Database Schema
|
|
|
|
|
|
|
|
|
|
### Tables
|
|
|
|
|
|
|
|
|
|
**users:**
|
|
|
|
|
```sql
|
|
|
|
|
CREATE TABLE users (
|
|
|
|
|
id SERIAL PRIMARY KEY,
|
|
|
|
|
username VARCHAR(50) UNIQUE NOT NULL,
|
|
|
|
|
email VARCHAR(255) UNIQUE NOT NULL,
|
|
|
|
|
password_hash VARCHAR(255) NOT NULL,
|
|
|
|
|
avatar VARCHAR(255),
|
|
|
|
|
created_at TIMESTAMP DEFAULT NOW(),
|
|
|
|
|
updated_at TIMESTAMP DEFAULT NOW()
|
|
|
|
|
);
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
**events:**
|
|
|
|
|
```sql
|
|
|
|
|
CREATE TABLE events (
|
|
|
|
|
id SERIAL PRIMARY KEY,
|
|
|
|
|
name VARCHAR(255) NOT NULL,
|
|
|
|
|
location VARCHAR(255) NOT NULL,
|
|
|
|
|
start_date DATE NOT NULL,
|
|
|
|
|
end_date DATE NOT NULL,
|
|
|
|
|
worldsdc_id VARCHAR(100) UNIQUE,
|
|
|
|
|
participants_count INT DEFAULT 0,
|
|
|
|
|
description TEXT,
|
|
|
|
|
created_at TIMESTAMP DEFAULT NOW()
|
|
|
|
|
);
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
**chat_rooms:**
|
|
|
|
|
```sql
|
|
|
|
|
CREATE TABLE chat_rooms (
|
|
|
|
|
id SERIAL PRIMARY KEY,
|
|
|
|
|
event_id INT REFERENCES events(id),
|
|
|
|
|
type VARCHAR(20) NOT NULL, -- 'event' or 'private'
|
|
|
|
|
created_at TIMESTAMP DEFAULT NOW()
|
|
|
|
|
);
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
**messages:**
|
|
|
|
|
```sql
|
|
|
|
|
CREATE TABLE messages (
|
|
|
|
|
id SERIAL PRIMARY KEY,
|
|
|
|
|
room_id INT REFERENCES chat_rooms(id) ON DELETE CASCADE,
|
|
|
|
|
user_id INT REFERENCES users(id),
|
|
|
|
|
content TEXT NOT NULL,
|
|
|
|
|
type VARCHAR(20) NOT NULL, -- 'text', 'link', 'video'
|
|
|
|
|
created_at TIMESTAMP DEFAULT NOW()
|
|
|
|
|
);
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
**matches:**
|
|
|
|
|
```sql
|
|
|
|
|
CREATE TABLE matches (
|
|
|
|
|
id SERIAL PRIMARY KEY,
|
|
|
|
|
user1_id INT REFERENCES users(id),
|
|
|
|
|
user2_id INT REFERENCES users(id),
|
|
|
|
|
event_id INT REFERENCES events(id),
|
|
|
|
|
room_id INT REFERENCES chat_rooms(id),
|
|
|
|
|
status VARCHAR(20) DEFAULT 'pending', -- 'pending', 'accepted', 'completed'
|
|
|
|
|
created_at TIMESTAMP DEFAULT NOW(),
|
|
|
|
|
CONSTRAINT unique_match UNIQUE (user1_id, user2_id, event_id)
|
|
|
|
|
);
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
**ratings:**
|
|
|
|
|
```sql
|
|
|
|
|
CREATE TABLE ratings (
|
|
|
|
|
id SERIAL PRIMARY KEY,
|
|
|
|
|
match_id INT REFERENCES matches(id),
|
|
|
|
|
rater_id INT REFERENCES users(id),
|
|
|
|
|
rated_id INT REFERENCES users(id),
|
|
|
|
|
score INT CHECK (score >= 1 AND score <= 5),
|
|
|
|
|
comment TEXT,
|
|
|
|
|
would_collaborate_again BOOLEAN DEFAULT FALSE,
|
|
|
|
|
created_at TIMESTAMP DEFAULT NOW(),
|
|
|
|
|
CONSTRAINT unique_rating UNIQUE (match_id, rater_id, rated_id)
|
|
|
|
|
);
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Indexes (Performance)
|
|
|
|
|
```sql
|
|
|
|
|
CREATE INDEX idx_messages_room_id ON messages(room_id);
|
|
|
|
|
CREATE INDEX idx_messages_created_at ON messages(created_at);
|
|
|
|
|
CREATE INDEX idx_matches_user1_id ON matches(user1_id);
|
|
|
|
|
CREATE INDEX idx_matches_user2_id ON matches(user2_id);
|
|
|
|
|
CREATE INDEX idx_matches_event_id ON matches(event_id);
|
|
|
|
|
CREATE INDEX idx_ratings_rated_id ON ratings(rated_id);
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## WebSocket (Socket.IO) Events
|
|
|
|
|
|
|
|
|
|
### Client → Server
|
|
|
|
|
|
|
|
|
|
**Connection:**
|
|
|
|
|
- `connect` - Client connects
|
|
|
|
|
- `disconnect` - Client disconnects
|
|
|
|
|
|
|
|
|
|
**Event Chat:**
|
|
|
|
|
- `join_event_room` - Join event chat room
|
|
|
|
|
- Payload: `{ eventId: number }`
|
|
|
|
|
- `leave_event_room` - Leave event chat room
|
|
|
|
|
- Payload: `{ eventId: number }`
|
|
|
|
|
- `send_event_message` - Send message to event chat
|
|
|
|
|
- Payload: `{ eventId: number, content: string }`
|
|
|
|
|
|
|
|
|
|
**Private Chat:**
|
|
|
|
|
- `join_private_room` - Join private match room
|
|
|
|
|
- Payload: `{ matchId: number }`
|
|
|
|
|
- `send_private_message` - Send message to match partner
|
|
|
|
|
- Payload: `{ matchId: number, content: string }`
|
|
|
|
|
|
|
|
|
|
**Matchmaking:**
|
|
|
|
|
- `send_match_request` - Request match with user
|
|
|
|
|
- Payload: `{ targetUserId: number, eventId: number }`
|
|
|
|
|
- `accept_match_request` - Accept match request
|
|
|
|
|
- Payload: `{ matchId: number }`
|
|
|
|
|
|
|
|
|
|
**WebRTC Signaling:**
|
|
|
|
|
- `webrtc_offer` - Send SDP offer
|
|
|
|
|
- Payload: `{ matchId: number, offer: RTCSessionDescriptionInit }`
|
|
|
|
|
- `webrtc_answer` - Send SDP answer
|
|
|
|
|
- Payload: `{ matchId: number, answer: RTCSessionDescriptionInit }`
|
|
|
|
|
- `webrtc_ice_candidate` - Send ICE candidate
|
|
|
|
|
- Payload: `{ matchId: number, candidate: RTCIceCandidate }`
|
|
|
|
|
|
|
|
|
|
### Server → Client
|
|
|
|
|
|
|
|
|
|
**Event Chat:**
|
|
|
|
|
- `event_message` - New message in event chat
|
|
|
|
|
- Payload: `{ id, roomId, userId, username, avatar, content, createdAt }`
|
|
|
|
|
- `active_users` - List of active users in event
|
|
|
|
|
- Payload: `{ users: Array }`
|
|
|
|
|
|
|
|
|
|
**Private Chat:**
|
|
|
|
|
- `private_message` - New message from partner
|
|
|
|
|
- Payload: `{ id, matchId, userId, username, content, createdAt }`
|
|
|
|
|
|
|
|
|
|
**Matchmaking:**
|
|
|
|
|
- `match_request_received` - Someone wants to match
|
|
|
|
|
- Payload: `{ matchId, fromUser }`
|
|
|
|
|
- `match_accepted` - Match request accepted
|
|
|
|
|
- Payload: `{ matchId, partner }`
|
|
|
|
|
|
|
|
|
|
**WebRTC Signaling:**
|
|
|
|
|
- `webrtc_offer` - Received SDP offer from partner
|
|
|
|
|
- `webrtc_answer` - Received SDP answer from partner
|
|
|
|
|
- `webrtc_ice_candidate` - Received ICE candidate from partner
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## WebRTC P2P Architecture
|
|
|
|
|
|
|
|
|
|
### Components
|
|
|
|
|
|
|
|
|
|
**RTCPeerConnection:**
|
|
|
|
|
- Manages P2P connection between two browsers
|
|
|
|
|
- Handles ICE candidate gathering
|
|
|
|
|
- Manages connection state (new, connecting, connected, failed)
|
|
|
|
|
|
|
|
|
|
**RTCDataChannel:**
|
|
|
|
|
- Binary data transfer channel
|
|
|
|
|
- Ordered, reliable delivery (like TCP)
|
|
|
|
|
- Max message size: 16KB-64KB (browser dependent)
|
|
|
|
|
- Used for video file chunks
|
|
|
|
|
|
|
|
|
|
**STUN Server:**
|
|
|
|
|
- Public STUN server (e.g., Google: `stun:stun.l.google.com:19302`)
|
|
|
|
|
- Discovers public IP address
|
|
|
|
|
- Helps with NAT traversal
|
|
|
|
|
|
|
|
|
|
**TURN Server (Optional):**
|
|
|
|
|
- Relay server for difficult NAT/firewall scenarios
|
|
|
|
|
- Only ~10% of connections need TURN
|
|
|
|
|
- Can use free services or self-host
|
|
|
|
|
|
|
|
|
|
### File Transfer Flow
|
|
|
|
|
|
|
|
|
|
**1. Initiate Connection:**
|
|
|
|
|
```javascript
|
|
|
|
|
// Sender creates offer
|
|
|
|
|
const pc = new RTCPeerConnection({ iceServers: [{ urls: 'stun:stun.l.google.com:19302' }] });
|
|
|
|
|
const dataChannel = pc.createDataChannel('fileTransfer');
|
|
|
|
|
const offer = await pc.createOffer();
|
|
|
|
|
await pc.setLocalDescription(offer);
|
|
|
|
|
// Send offer to receiver via Socket.IO
|
|
|
|
|
socket.emit('webrtc_offer', { matchId, offer });
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
**2. Exchange SDP/ICE:**
|
|
|
|
|
```javascript
|
|
|
|
|
// Receiver receives offer via Socket.IO
|
|
|
|
|
socket.on('webrtc_offer', async ({ offer }) => {
|
|
|
|
|
await pc.setRemoteDescription(offer);
|
|
|
|
|
const answer = await pc.createAnswer();
|
|
|
|
|
await pc.setLocalDescription(answer);
|
|
|
|
|
socket.emit('webrtc_answer', { matchId, answer });
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Both peers exchange ICE candidates
|
|
|
|
|
pc.onicecandidate = (event) => {
|
|
|
|
|
if (event.candidate) {
|
|
|
|
|
socket.emit('webrtc_ice_candidate', { matchId, candidate: event.candidate });
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
**3. Transfer File:**
|
|
|
|
|
```javascript
|
|
|
|
|
// Chunk file and send
|
|
|
|
|
const CHUNK_SIZE = 16384; // 16KB
|
|
|
|
|
const file = selectedFile;
|
|
|
|
|
const totalChunks = Math.ceil(file.size / CHUNK_SIZE);
|
|
|
|
|
|
|
|
|
|
// Send metadata first
|
|
|
|
|
dataChannel.send(JSON.stringify({
|
|
|
|
|
type: 'metadata',
|
|
|
|
|
name: file.name,
|
|
|
|
|
size: file.size,
|
|
|
|
|
mimeType: file.type,
|
|
|
|
|
totalChunks
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
// Send chunks
|
|
|
|
|
for (let i = 0; i < totalChunks; i++) {
|
|
|
|
|
const start = i * CHUNK_SIZE;
|
|
|
|
|
const end = Math.min(start + CHUNK_SIZE, file.size);
|
|
|
|
|
const chunk = file.slice(start, end);
|
|
|
|
|
const arrayBuffer = await chunk.arrayBuffer();
|
|
|
|
|
dataChannel.send(arrayBuffer);
|
|
|
|
|
|
|
|
|
|
// Update progress
|
|
|
|
|
const progress = ((i + 1) / totalChunks) * 100;
|
|
|
|
|
setTransferProgress(progress);
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
**4. Receive File:**
|
|
|
|
|
```javascript
|
|
|
|
|
// Receiver assembles chunks
|
|
|
|
|
let receivedChunks = [];
|
|
|
|
|
let metadata = null;
|
|
|
|
|
|
|
|
|
|
dataChannel.onmessage = (event) => {
|
|
|
|
|
if (typeof event.data === 'string') {
|
|
|
|
|
// Metadata
|
|
|
|
|
metadata = JSON.parse(event.data);
|
|
|
|
|
} else {
|
|
|
|
|
// Chunk
|
|
|
|
|
receivedChunks.push(event.data);
|
|
|
|
|
|
|
|
|
|
if (receivedChunks.length === metadata.totalChunks) {
|
|
|
|
|
// All chunks received, create file
|
|
|
|
|
const blob = new Blob(receivedChunks, { type: metadata.mimeType });
|
|
|
|
|
const url = URL.createObjectURL(blob);
|
|
|
|
|
// Trigger download
|
|
|
|
|
const a = document.createElement('a');
|
|
|
|
|
a.href = url;
|
|
|
|
|
a.download = metadata.name;
|
|
|
|
|
a.click();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Error Handling
|
|
|
|
|
|
|
|
|
|
**Connection Failed:**
|
|
|
|
|
- Fallback to link sharing (Google Drive, Dropbox)
|
|
|
|
|
- Show user-friendly error message
|
|
|
|
|
- Log error for debugging
|
|
|
|
|
|
|
|
|
|
**Transfer Interrupted:**
|
|
|
|
|
- Detect connection state change
|
|
|
|
|
- Offer retry option
|
|
|
|
|
- Consider implementing resume capability (future)
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## Security
|
|
|
|
|
|
|
|
|
|
### Backend Security
|
|
|
|
|
|
|
|
|
|
**Authentication:**
|
|
|
|
|
- bcrypt for password hashing (salt rounds: 10-12)
|
|
|
|
|
- JWT for session management (httpOnly cookies recommended)
|
|
|
|
|
- Token expiration (e.g., 24 hours)
|
|
|
|
|
- Refresh token mechanism (optional)
|
|
|
|
|
|
|
|
|
|
**API Security:**
|
|
|
|
|
- Rate limiting (express-rate-limit)
|
|
|
|
|
- Helmet.js for security headers
|
|
|
|
|
- CORS configuration (allow only frontend origin)
|
|
|
|
|
- Input validation (express-validator)
|
|
|
|
|
- SQL injection prevention (prepared statements, ORM)
|
|
|
|
|
- XSS prevention (sanitize user input)
|
|
|
|
|
|
|
|
|
|
### Frontend Security
|
|
|
|
|
|
|
|
|
|
**Storage:**
|
|
|
|
|
- JWT in httpOnly cookie (recommended) OR secure localStorage
|
|
|
|
|
- Never store passwords
|
|
|
|
|
- Clear sensitive data on logout
|
|
|
|
|
|
|
|
|
|
**Input Validation:**
|
|
|
|
|
- Client-side validation for UX
|
|
|
|
|
- Server-side validation for security
|
|
|
|
|
- Sanitize before displaying user content
|
|
|
|
|
|
|
|
|
|
### WebRTC Security
|
|
|
|
|
|
|
|
|
|
**Native Encryption:**
|
|
|
|
|
- DTLS (Datagram Transport Layer Security) for data channels
|
|
|
|
|
- SRTP (Secure Real-time Transport Protocol) for media
|
|
|
|
|
- Enabled by default, no configuration needed
|
|
|
|
|
|
|
|
|
|
**Additional Measures:**
|
|
|
|
|
- Verify signaling server (HTTPS + WSS)
|
|
|
|
|
- Optional: end-to-end encryption for chat (WebCrypto API, libsodium)
|
|
|
|
|
- No video files stored on server
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## Deployment Architecture (Future)
|
|
|
|
|
|
|
|
|
|
### Production Stack
|
|
|
|
|
|
|
|
|
|
**Option 1: Single VPS**
|
|
|
|
|
- Ubuntu/Debian server
|
|
|
|
|
- Docker Compose
|
|
|
|
|
- nginx as reverse proxy + SSL (Let's Encrypt)
|
|
|
|
|
- PostgreSQL on same server
|
|
|
|
|
- Backup strategy
|
|
|
|
|
|
|
|
|
|
**Option 2: Cloud (Recommended)**
|
|
|
|
|
- Frontend: Vercel/Netlify (CDN, auto-scaling)
|
|
|
|
|
- Backend: AWS EC2/DigitalOcean/Render
|
|
|
|
|
- Database: AWS RDS/DigitalOcean Managed PostgreSQL
|
|
|
|
|
- nginx on backend for API
|
|
|
|
|
- Redis for session storage (optional)
|
|
|
|
|
|
|
|
|
|
**Option 3: Kubernetes (Overkill for MVP)**
|
|
|
|
|
- GKE/EKS/AKS
|
|
|
|
|
- Helm charts
|
|
|
|
|
- Auto-scaling
|
|
|
|
|
- Load balancing
|
|
|
|
|
|
|
|
|
|
### SSL/TLS
|
|
|
|
|
|
|
|
|
|
**Let's Encrypt:**
|
|
|
|
|
- Free SSL certificates
|
|
|
|
|
- Auto-renewal with Certbot
|
|
|
|
|
- Required for WebRTC (getUserMedia requires HTTPS)
|
|
|
|
|
|
|
|
|
|
### Environment Variables
|
|
|
|
|
|
|
|
|
|
**Backend:**
|
|
|
|
|
```env
|
|
|
|
|
NODE_ENV=production
|
|
|
|
|
PORT=3000
|
|
|
|
|
DATABASE_URL=postgresql://user:pass@host:5432/dbname
|
|
|
|
|
JWT_SECRET=random-secret-key
|
|
|
|
|
CORS_ORIGIN=https://spotlight.cam
|
|
|
|
|
STUN_SERVER=stun:stun.l.google.com:19302
|
|
|
|
|
TURN_SERVER=turn:your-turn-server.com (optional)
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
**Frontend:**
|
|
|
|
|
```env
|
|
|
|
|
VITE_API_URL=https://api.spotlight.cam
|
|
|
|
|
VITE_WS_URL=wss://api.spotlight.cam
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## Performance Considerations
|
|
|
|
|
|
|
|
|
|
### Frontend
|
|
|
|
|
- Code splitting (React.lazy)
|
|
|
|
|
- Image optimization
|
|
|
|
|
- Service worker caching (PWA)
|
|
|
|
|
- Minimize bundle size
|
|
|
|
|
|
|
|
|
|
### Backend
|
|
|
|
|
- Database connection pooling
|
|
|
|
|
- Query optimization (indexes)
|
|
|
|
|
- Caching (Redis for frequently accessed data)
|
|
|
|
|
- Compression (gzip/brotli)
|
|
|
|
|
|
|
|
|
|
### WebRTC
|
|
|
|
|
- Adaptive chunk size based on bandwidth
|
|
|
|
|
- Buffer management (avoid overwhelming DataChannel)
|
|
|
|
|
- Connection quality monitoring
|
|
|
|
|
- Automatic reconnection on failure
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## Monitoring & Logging (Future)
|
|
|
|
|
|
|
|
|
|
**Backend:**
|
|
|
|
|
- Winston/Pino for logging
|
|
|
|
|
- Log levels: error, warn, info, debug
|
|
|
|
|
- Structured logging (JSON format)
|
|
|
|
|
|
|
|
|
|
**Database:**
|
|
|
|
|
- Query performance monitoring
|
|
|
|
|
- Slow query log
|
|
|
|
|
- Connection pool metrics
|
|
|
|
|
|
|
|
|
|
**WebRTC:**
|
|
|
|
|
- Connection success rate
|
|
|
|
|
- Transfer speed metrics
|
|
|
|
|
- Failure reasons
|
|
|
|
|
- Browser compatibility stats
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
2025-11-20 22:34:05 +01:00
|
|
|
**Last Updated:** 2025-11-20
|
|
|
|
|
|
|
|
|
|
**Status:** ✅ All architecture components implemented and production-ready
|